Moin,
finally I got my eclipse environment running and was able to build
mkgmap from source. So the next step was my first extension of mkgmap.
Until now a single OSM element was converted into a single garmin
element, i.e. the first one in the style file with the matching expression.
I have extended the style definition by two commands (stop and
continue), to allow the
generation of multiple elements.
A classic conversion rule has a syntax like:
amenity=restaurant [0x2a00 resolution 20]
With my extension this is equal to
amenity=restaurant [0x2a00 resolution 20 stop]
and has the meaning: If this rule is used, no further rules will be
applied to the element.
If this line is changed now to
amenity=restaurant [0x2a00 resolution 20 continue]
mkgmap will convert a matching OSM element into a garmin Element type
0x2a00, but afterwards it will check, whether another rule is matching
for the same osm element. So this will allow, that for the same item
additionally a
tourism=hotel [0x2b01 resolution 20]
rule might be applied.
My modification is working on points as well as on lines or polygons,
i.e. it will also help with barrier=fence problem.
There are two problems with my modification:
1. The POI generation for an area will not work, with multiple
conversion rules.
2. I do not know, how to provide a proper patch for my modification, so
I just attached the modified java-files (based on mkgmap-r1087).
I hope you can incorporate the modification into your mkgmap and give it
a try. Without any change to the style, the modification should not
change anything. As a try, you could add the continue command to all
point rules and all barrier rules.
What do you think about it?
Gruss
Torsten
/*
* Copyright (C) 2008 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: 15-Nov-2008
*/
package uk.me.parabola.mkgmap.osmstyle;
import java.util.List;
import uk.me.parabola.mkgmap.osmstyle.actions.Action;
import uk.me.parabola.mkgmap.osmstyle.eval.Op;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Rule;
/**
* An action rule modifies the tags on the incoming element.
*
* It can also have an expression, and does not need to have a Type. If
* there is no type then the resolve method always returns false. The tags
* on the element may have been modified however.
*
* @author Steve Ratcliffe
*/
public class ActionRule implements Rule {
private final Op expression;
private final List<Action> actions;
private final GType type;
public ActionRule(Op expression, List<Action> actions, GType type) {
assert actions != null;
this.expression = expression;
this.actions = actions;
this.type = type;
}
public ActionRule(Op expression, List<Action> actions) {
assert actions != null;
this.expression = expression;
this.actions = actions;
this.type = null;
}
public GType resolveType(Element el, GType pre) {
if (expression == null || expression.eval(el)) {
for (Action a : actions)
a.perform(el);
return type;
}
return null;
}
public String toString() {
StringBuilder fmt = new StringBuilder();
if (expression != null)
fmt.append(expression);
fmt.append("\n\t{");
for (Action a : actions)
fmt.append(a);
fmt.append("}\n");
if (type != null) {
fmt.append('\t');
fmt.append(type);
}
return fmt.toString();
}
}
/*
* Copyright (C) 2008 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: 07-Nov-2008
*/
package uk.me.parabola.mkgmap.osmstyle;
import uk.me.parabola.mkgmap.osmstyle.eval.Op;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Rule;
/**
* A rule that contains a condition. If the condition is matched by the
* element then the held gtype is returned.
*
* @author Steve Ratcliffe
*/
public class ExpressionRule implements Rule {
private final Op exression;
private final GType gtype;
public ExpressionRule(Op exression, GType gtype) {
this.exression = exression;
this.gtype = gtype;
}
public GType resolveType(Element el, GType pre) {
if (exression.eval(el))
return gtype;
return null;
}
public String toString() {
return exression.toString() + ' ' + gtype;
}
}
/*
* Copyright (C) 2008 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: 07-Nov-2008
*/
package uk.me.parabola.mkgmap.osmstyle;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Rule;
/**
* A rule that always returns the same gtype.
*
* @author Steve Ratcliffe
*/
public class FixedRule implements Rule {
private final GType gtype;
public FixedRule(GType gtype) {
this.gtype = gtype;
}
public GType resolveType(Element el, GType pre) {
return gtype;
}
public String toString() {
return gtype.toString();
}
}
/*
* Copyright (C) 2008 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: Apr 25, 2008
*/
package uk.me.parabola.mkgmap.reader.osm;
import java.util.Formatter;
import uk.me.parabola.imgfmt.ExitException;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.LevelInfo;
/**
* Holds the garmin type of an element and all the information that
* will be needed to represent it on the map. So we have a range of
* resolutions at which it will be present.
*
* @author Steve Ratcliffe
*/
public class GType {
private static final Logger log = Logger.getLogger(GType.class);
public static final int POINT = 1;
public static final int POLYLINE = 2;
public static final int POLYGON = 3;
private static int nextPriority = 1;
private static final int PRIORITY_PUSH = 100000;
private final int featureKind;
private final int type;
private int minResolution = 24;
private int maxResolution = 24;
private int maxLevel = -1;
private int minLevel;
private String defaultName;
// road class and speed will be set on roads.
private int roadClass;
private int roadSpeed;
private final int priority;
private boolean road;
// control flag, whether this element defines
// the final conversion, or whether we shall search
// for further matching elements
private boolean FinalElement = true;
public GType(int featureKind, String type) {
priority = nextPriority();
this.featureKind = featureKind;
try {
this.type = Integer.decode(type);
} catch (NumberFormatException e) {
log.error("not numeric " + type);
throw new ExitException("non-numeric type in map-features file");
}
}
private static int nextPriority() {
return nextPriority++;
}
public GType(int featureKind, String type, String subtype) {
priority = nextPriority();
this.featureKind = featureKind;
try {
this.type = (Integer.decode(type) << 8) + Integer.decode(subtype);
} catch (NumberFormatException e) {
log.error("not numeric " + type + ' ' + subtype);
throw new ExitException("non-numeric type in map-features file");
}
}
public int getFeatureKind() {
return featureKind;
}
public int getType() {
return type;
}
public int getMinResolution() {
return minResolution;
}
public void setMinResolution(int minResolution) {
this.minResolution = minResolution;
}
public int getMaxResolution() {
return maxResolution;
}
public void setMaxResolution(int maxResolution) {
this.maxResolution = maxResolution;
}
public String getDefaultName() {
return defaultName;
}
public void setDefaultName(String defaultName) {
this.defaultName = defaultName;
}
/**
* Is the priority of this type better than that of other?
* Lower priorities are better and win out.
*/
public boolean isBetterPriority(GType other) {
return this.priority < other.priority;
}
/**
* Set minLevel and maxLevel based on the resolution values set and
* the given levels info. We do this because we used to work only
* on resolution, but we want to move more towards working with
* levels.
*/
public void fixLevels(LevelInfo[] levels) {
for (LevelInfo info : levels) {
if (info.getBits() <= minResolution)
maxLevel = info.getLevel();
if (info.getBits() <= maxResolution)
minLevel = info.getLevel();
}
}
public String toString() {
StringBuilder sb = new StringBuilder();
Formatter fmt = new Formatter(sb);
sb.append('[');
fmt.format("%#x", type);
if (maxLevel == -1) {
if (maxResolution == 24)
fmt.format(" resolution %d", minResolution);
else
fmt.format(" resolution %d-%d", maxResolution, minResolution);
} else {
if (minLevel == 0)
fmt.format(" level %d", maxLevel);
else
fmt.format(" level %d-%d", minLevel, maxLevel);
}
sb.append(']');
return sb.toString();
}
public int getMinLevel() {
return minLevel;
}
public int getMaxLevel() {
return maxLevel;
}
public int getRoadClass() {
return roadClass;
}
public void setRoadClass(int roadClass) {
road = true;
this.roadClass = roadClass;
}
public int getRoadSpeed() {
return roadSpeed;
}
public void setRoadSpeed(int roadSpeed) {
road = true;
this.roadSpeed = roadSpeed;
}
public boolean isRoad() {
return road;
}
public void setFinal() {
FinalElement = true;
}
public void setContinue() {
FinalElement = false;
}
public boolean isFinal() {
return FinalElement;
}
public static void push() {
nextPriority += PRIORITY_PUSH;
}
public static void pop() {
nextPriority -= PRIORITY_PUSH;
}
}
/*
* Copyright (C) 2008 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: 07-Nov-2008
*/
package uk.me.parabola.mkgmap.reader.osm;
/**
* A rule takes an element and returns the correct garmin type for it.
* Immplementations can be simple or complex as needed.
*
* @author Steve Ratcliffe
*/
public interface Rule {
/**
* Given the element return the garmin type that should be used to
* represent it.
*
* @param el The element as read from an OSM xml file in 'tag' format.
* @param pre The previous garmin type generated from the element.
* @return Enough information to represent this as a garmin type.
*/
public GType resolveType(Element el, GType pre);
}
/*
* Copyright (C) 2008 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: 08-Nov-2008
*/
package uk.me.parabola.mkgmap.osmstyle;
import java.util.Formatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Rule;
/**
* A group of rules. Basically just a map of a tag=value strings that is used
* as an index and the rule that applies for that tag,value pair.
*
* The main purpose is to separate out the code to add a new rule
* which is moderately complex.
*
* @author Steve Ratcliffe
*/
public class RuleSet implements Rule {
private final Map<String, Rule> rules = new LinkedHashMap<String, Rule>();
public void add(String s, Rule rule) {
Rule existingRule = rules.get(s);
if (existingRule == null) {
rules.put(s, rule);
} else {
if (existingRule instanceof SequenceRule) {
((SequenceRule) existingRule).add(rule);
} else {
// There was already a single rule there. Create a sequence
// rule and add the existing and the new rule to it.
SequenceRule sr = new SequenceRule();
sr.add(existingRule);
sr.add(rule);
rules.put(s, sr);
}
}
}
public Map<String, Rule> getMap() {
return rules;
}
public Set<Map.Entry<String,Rule>> entrySet() {
return rules.entrySet();
}
public GType resolveType(Element el, GType pre) {
GType foundType = null;
for (String tagKey : el) {
Rule rule = rules.get(tagKey);
if (rule != null) {
GType type = rule.resolveType(el, pre);
if (type != null) {
if ((foundType == null || type.isBetterPriority(foundType)) && (pre == null || pre.isBetterPriority(type))) {
foundType = type;
}
}
}
}
return foundType;
}
public void addAll(RuleSet rs) {
for (Map.Entry<String, Rule> ent : rs.entrySet())
add(ent.getKey(), ent.getValue());
}
/**
* Format the rule set. Warning: this doesn't produce a valid input
* rule file.
*/
public String toString() {
Formatter fmt = new Formatter();
for (Map.Entry<String, Rule> ent: rules.entrySet()) {
String first = ent.getKey();
Rule r = ent.getValue();
if (r instanceof FixedRule)
fmt.format("%s %s\n", first, r);
else
fmt.format("%s & %s\n", first, r);
}
return fmt.toString();
}
}
/*
* Copyright (C) 2008 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: 07-Nov-2008
*/
package uk.me.parabola.mkgmap.osmstyle;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Rule;
/**
* This holds a list of rules. Each of them is tried out in order and
* the first one that matches is the result of this rule.
*
* @author Steve Ratcliffe
*/
public class SequenceRule implements Rule, Iterable<Rule> {
private final List<Rule> ruleList = new ArrayList<Rule>();
public GType resolveType(Element el, GType pre) {
for (Rule r : ruleList) {
GType type = r.resolveType(el, pre);
if (type != null)
return type;
}
return null;
}
/**
* Add a rule to this sequence. We do a quick check for impossible
* situations: if a FixedRule is added, then any rule added afterwards
* would never be called (because a fixed rule always returns an answer).
*/
public void add(Rule rule) {
//if (blocked && !(rule instanceof FixedRule))
// System.err.println("Warning: Unreachable rule (" + rule + "), more general rules should be later in the file");
ruleList.add(rule);
//boolean blocked;
//if (rule instanceof FixedRule)
// blocked = true;
}
public Iterator<Rule> iterator() {
return ruleList.listIterator();
}
public String toString() {
Formatter fmt = new Formatter(new StringBuilder());
fmt.format("(");
for (Rule r : ruleList) {
fmt.format("%s | ", r);
}
fmt.format(")");
return fmt.toString();
}
}
/*
* Copyright (C) 2007 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: Feb 17, 2008
*/
package uk.me.parabola.mkgmap.osmstyle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.CoordNode;
import uk.me.parabola.imgfmt.app.Exit;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.AreaClipper;
import uk.me.parabola.mkgmap.general.Clipper;
import uk.me.parabola.mkgmap.general.LineAdder;
import uk.me.parabola.mkgmap.general.LineClipper;
import uk.me.parabola.mkgmap.general.MapCollector;
import uk.me.parabola.mkgmap.general.MapElement;
import uk.me.parabola.mkgmap.general.MapExitPoint;
import uk.me.parabola.mkgmap.general.MapLine;
import uk.me.parabola.mkgmap.general.MapPoint;
import uk.me.parabola.mkgmap.general.MapRoad;
import uk.me.parabola.mkgmap.general.MapShape;
import uk.me.parabola.mkgmap.general.RoadNetwork;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Node;
import uk.me.parabola.mkgmap.reader.osm.OsmConverter;
import uk.me.parabola.mkgmap.reader.osm.Relation;
import uk.me.parabola.mkgmap.reader.osm.RestrictionRelation;
import uk.me.parabola.mkgmap.reader.osm.Rule;
import uk.me.parabola.mkgmap.reader.osm.Style;
import uk.me.parabola.mkgmap.reader.osm.Way;
/**
* Convert from OSM to the mkgmap intermediate format using a style.
* A style is a collection of files that describe the mappings to be used
* when converting.
*
* @author Steve Ratcliffe
*/
public class StyledConverter implements OsmConverter {
private static final Logger log = Logger.getLogger(StyledConverter.class);
private final String[] nameTagList;
private final MapCollector collector;
private Clipper clipper = Clipper.NULL_CLIPPER;
private Area bbox;
private Set<Coord> boundaryCoords = new HashSet<Coord>();
// restrictions associates lists of turn restrictions with the
// Coord corresponding to the restrictions' 'via' node
private final Map<Coord, List<RestrictionRelation>> restrictions = new IdentityHashMap<Coord, List<RestrictionRelation>>();
// originalWay associates Ways that have been created due to
// splitting or clipping with the Ways that they were derived
// from
private final Map<Way, Way> originalWay = new HashMap<Way, Way>();
// limit arc lengths to what can currently be handled by RouteArc
private final int MAX_ARC_LENGTH = 75000;
private final int MAX_POINTS_IN_WAY = 200;
private final int MAX_NODES_IN_WAY = 16;
private final double MIN_DISTANCE_BETWEEN_NODES = 5.5;
// nodeIdMap maps a Coord into a nodeId
private final Map<Coord, Integer> nodeIdMap = new IdentityHashMap<Coord, Integer>();
private int nextNodeId = 1;
private final Rule wayRules;
private final Rule nodeRules;
private final Rule relationRules;
private boolean ignoreMaxspeeds;
class AccessMapping {
private final String type;
private final int index;
AccessMapping(String type, int index) {
this.type = type;
this.index = index;
}
}
private final AccessMapping[] accessMap = {
new AccessMapping("access", RoadNetwork.NO_MAX), // must be first in list
new AccessMapping("bicycle", RoadNetwork.NO_BIKE),
new AccessMapping("foot", RoadNetwork.NO_FOOT),
new AccessMapping("hgv", RoadNetwork.NO_TRUCK),
new AccessMapping("motorcar", RoadNetwork.NO_CAR),
new AccessMapping("motorcycle", RoadNetwork.NO_CAR),
new AccessMapping("psv", RoadNetwork.NO_BUS),
new AccessMapping("taxi", RoadNetwork.NO_TAXI),
new AccessMapping("emergency", RoadNetwork.NO_EMERGENCY),
new AccessMapping("delivery", RoadNetwork.NO_DELIVERY),
new AccessMapping("goods", RoadNetwork.NO_DELIVERY),
};
private LineAdder lineAdder = new LineAdder() {
public void add(MapLine element) {
if (element instanceof MapRoad)
collector.addRoad((MapRoad) element);
else
collector.addLine(element);
}
};
public StyledConverter(Style style, MapCollector collector, Properties props) {
this.collector = collector;
nameTagList = style.getNameTagList();
wayRules = style.getWayRules();
nodeRules = style.getNodeRules();
relationRules = style.getRelationRules();
ignoreMaxspeeds = props.getProperty("ignore-maxspeeds") != null;
LineAdder overlayAdder = style.getOverlays(lineAdder);
if (overlayAdder != null)
lineAdder = overlayAdder;
}
/**
* This takes the way and works out what kind of map feature it is and makes
* the relevant call to the mapper callback.
* <p>
* As a few examples we might want to check for the 'highway' tag, work out
* if it is an area of a park etc.
*
* @param way The OSM way.
*/
public void convertWay(Way way) {
if (way.getPoints().size() < 2)
return;
preConvertRules(way);
GType foundType = null;
do {
foundType = wayRules.resolveType(way, foundType);
if (foundType == null)
return;
postConvertRules(way, foundType);
if (foundType.getFeatureKind() == GType.POLYLINE) {
if(foundType.isRoad())
addRoad(way, foundType);
else
addLine(way, foundType);
}
else
addShape(way, foundType);
} while (!foundType.isFinal());
}
/**
* Takes a node (that has its own identity) and converts it from the OSM
* type to the Garmin map type.
*
* @param node The node to convert.
*/
public void convertNode(Node node) {
preConvertRules(node);
GType foundType = null;
do {
foundType = nodeRules.resolveType(node, foundType);
if (foundType == null)
return;
postConvertRules(node, foundType);
addPoint(node, foundType);
} while (!foundType.isFinal());
}
/**
* Rules to run before converting the element.
*/
private void preConvertRules(Element el) {
if (nameTagList == null)
return;
for (String t : nameTagList) {
String val = el.getTag(t);
if (val != null) {
el.addTag("name", val);
break;
}
}
}
/**
* Built in rules to run after converting the element.
*/
private void postConvertRules(Element el, GType type) {
// Set the name from the 'name' tag or failing that from
// the default_name.
el.setName(el.getTag("name"));
if (el.getName() == null)
el.setName(type.getDefaultName());
}
/**
* Set the bounding box for this map. This should be set before any other
* elements are converted if you want to use it. All elements that are added
* are clipped to this box, new points are added as needed at the boundry.
*
* If a node or a way falls completely outside the boundry then it would be
* ommited. This would not normally happen in the way this option is typically
* used however.
*
* @param bbox The bounding area.
*/
public void setBoundingBox(Area bbox) {
this.clipper = new AreaClipper(bbox);
this.bbox = bbox;
}
/**
* Run the rules for this relation. As this is not an end object, then
* the only useful rules are action rules that set tags on the contained
* ways or nodes. Every rule should probably start with 'type=".."'.
*
* @param relation The relation to convert.
*/
public void convertRelation(Relation relation) {
// Relations never resolve to a GType and so we ignore the return
// value.
relationRules.resolveType(relation, null);
if(relation instanceof RestrictionRelation) {
RestrictionRelation rr = (RestrictionRelation)relation;
if(rr.isValid()) {
List<RestrictionRelation> lrr = restrictions.get(rr.getViaCoord());
if(lrr == null) {
lrr = new ArrayList<RestrictionRelation>();
restrictions.put(rr.getViaCoord(), lrr);
}
lrr.add(rr);
}
}
}
private void addLine(Way way, GType gt) {
MapLine line = new MapLine();
elementSetup(line, gt, way);
line.setPoints(way.getPoints());
if (way.isBoolTag("oneway"))
line.setDirection(true);
clipper.clipLine(line, lineAdder);
}
private void addShape(Way way, GType gt) {
MapShape shape = new MapShape();
elementSetup(shape, gt, way);
shape.setPoints(way.getPoints());
clipper.clipShape(shape, collector);
GType pointType = nodeRules.resolveType(way, null);
if(pointType != null)
shape.setPoiType(pointType.getType());
}
private void addPoint(Node node, GType gt) {
if (!clipper.contains(node.getLocation()))
return;
// to handle exit points we use a subclass of MapPoint
// to carry some extra info (a reference to the
// motorway associated with the exit)
MapPoint mp;
int type = gt.getType();
if(type >= 0x2000 && type < 0x2800) {
String ref = node.getTag(Exit.TAG_ROAD_REF);
String id = node.getTag("osm:id");
if(ref != null) {
String to = node.getTag(Exit.TAG_TO);
MapExitPoint mep = new MapExitPoint(ref, to);
String fd = node.getTag(Exit.TAG_FACILITY);
if(fd != null)
mep.setFacilityDescription(fd);
if(id != null)
mep.setOSMId(id);
mp = mep;
}
else {
mp = new MapPoint();
log.warn("Motorway exit " + node.getName() + " (OSM id " + id + ") located at " + node.getLocation().toDegreeString() + " has no motorway! (either make the exit share a node with the motorway or specify the motorway ref with a " + Exit.TAG_ROAD_REF + " tag)");
}
}
else {
mp = new MapPoint();
}
elementSetup(mp, gt, node);
mp.setLocation(node.getLocation());
collector.addPoint(mp);
}
private String combineRefs(Element element) {
String ref = element.getTag("ref");
String int_ref = element.getTag("int_ref");
if(int_ref != null) {
if(ref == null)
ref = int_ref;
else
ref += ";" + int_ref;
}
String nat_ref = element.getTag("nat_ref");
if(nat_ref != null) {
if(ref == null)
ref = nat_ref;
else
ref += ";" + nat_ref;
}
String reg_ref = element.getTag("reg_ref");
if(reg_ref != null) {
if(ref == null)
ref = reg_ref;
else
ref += ";" + reg_ref;
}
return ref;
}
private void elementSetup(MapElement ms, GType gt, Element element) {
String name = element.getName();
String refs = combineRefs(element);
if(name == null && refs != null) {
// use first ref as name
name = refs.split(";")[0].trim();
}
if(name != null)
ms.setName(name);
if(refs != null)
ms.setRef(refs);
ms.setType(gt.getType());
ms.setMinResolution(gt.getMinResolution());
ms.setMaxResolution(gt.getMaxResolution());
// Now try to get some address info for POIs
String city = element.getTag("addr:city");
String zip = element.getTag("addr:postcode");
String street = element.getTag("addr:street");
String houseNumber = element.getTag("addr:housenumber");
String phone = element.getTag("phone");
String isIn = element.getTag("is_in");
String country = element.getTag("is_in:country");
String region = element.getTag("is_in:county");
if(country != null)
country = element.getTag("addr:country");
if(zip == null)
zip = element.getTag("openGeoDB:postal_codes");
if(city == null)
city = element.getTag("openGeoDB:sort_name");
if(city != null)
ms.setCity(city);
if(zip != null)
ms.setZip(zip);
if(street != null)
ms.setStreet(street);
if(houseNumber != null)
ms.setHouseNumber(houseNumber);
if(isIn != null)
ms.setIsIn(isIn);
if(phone != null)
ms.setPhone(phone);
if(country != null)
ms.setCountry(country);
if(region != null)
ms.setRegion(region);
}
void addRoad(Way way, GType gt) {
if("roundabout".equals(way.getTag("junction"))) {
String frigFactorTag = way.getTag("mkgmap:frig_roundabout");
if(frigFactorTag != null) {
// do special roundabout frigging to make gps
// routing prompt use the correct exit number
double frigFactor = 0.25; // default
try {
frigFactor = Double.parseDouble(frigFactorTag);
}
catch (NumberFormatException nfe) {
// relax, tag was probably not a number anyway
}
frigRoundabout(way, frigFactor);
}
}
// if there is a bounding box, clip the way with it
List<Way> clippedWays = null;
if(bbox != null) {
List<List<Coord>> lineSegs = LineClipper.clip(bbox, way.getPoints());
boundaryCoords = new HashSet<Coord>();
if (lineSegs != null) {
clippedWays = new ArrayList<Way>();
for (List<Coord> lco : lineSegs) {
Way nWay = new Way(way.getId());
nWay.setName(way.getName());
nWay.copyTags(way);
for(Coord co : lco) {
nWay.addPoint(co);
if(co.getHighwayCount() == 0) {
boundaryCoords.add(co);
co.incHighwayCount();
}
}
clippedWays.add(nWay);
// associate the original Way
// to the new Way
Way origWay = originalWay.get(way);
if(origWay == null)
origWay = way;
originalWay.put(nWay, origWay);
}
}
}
if(clippedWays != null) {
for(Way cw : clippedWays) {
while(cw.getPoints().size() > MAX_POINTS_IN_WAY) {
Way tail = splitWayAt(cw, MAX_POINTS_IN_WAY - 1);
addRoadAfterSplittingLoops(cw, gt);
cw = tail;
}
addRoadAfterSplittingLoops(cw, gt);
}
}
else {
// no bounding box or way was not clipped
while(way.getPoints().size() > MAX_POINTS_IN_WAY) {
Way tail = splitWayAt(way, MAX_POINTS_IN_WAY - 1);
addRoadAfterSplittingLoops(way, gt);
way = tail;
}
addRoadAfterSplittingLoops(way, gt);
}
}
void addRoadAfterSplittingLoops(Way way, GType gt) {
// check if the way is a loop or intersects with itself
boolean wayWasSplit = true; // aka rescan required
while(wayWasSplit) {
List<Coord> wayPoints = way.getPoints();
int numPointsInWay = wayPoints.size();
wayWasSplit = false; // assume way won't be split
// check each point in the way to see if it is the same
// point as a following point in the way (actually the
// same object not just the same coordinates)
for(int p1I = 0; !wayWasSplit && p1I < (numPointsInWay - 1); p1I++) {
Coord p1 = wayPoints.get(p1I);
for(int p2I = p1I + 1; !wayWasSplit && p2I < numPointsInWay; p2I++) {
if(p1 == wayPoints.get(p2I)) {
// way is a loop or intersects itself
// attempt to split it into two ways
// start at point before intersection point
// check that splitting there will not produce
// a zero length arc - if it does try the
// previous point(s)
int splitI = p2I - 1;
while(splitI > p1I && p1.equals(wayPoints.get(splitI)))
--splitI;
if(splitI == p1I) {
log.warn("Looped way " + getDebugName(way) + " has zero length arc - deleting node[" + p2I + "] to remove it");
wayPoints.remove(p2I);
// next point to inspect has same index
--p2I;
// but number of points has reduced
--numPointsInWay;
}
else {
// split the way before the second point
log.info("Splitting looped way " + getDebugName(way) + " at node[" + splitI + "] - it has " + (numPointsInWay - splitI - 1 ) + " following segment(s).");
Way loopTail = splitWayAt(way, splitI);
// recursively check (shortened) head for
// more loops
addRoadAfterSplittingLoops(way, gt);
// now process the tail of the way
way = loopTail;
wayWasSplit = true;
}
}
}
}
if(!wayWasSplit) {
// no split required so make road from way
addRoadWithoutLoops(way, gt);
}
}
}
String getDebugName(Way way) {
String name = way.getName();
if(name == null)
name = way.getTag("ref");
if(name == null)
name = "";
else
name += " ";
return name + "(OSM id " + way.getId() + ")";
}
void addRoadWithoutLoops(Way way, GType gt) {
List<Integer> nodeIndices = new ArrayList<Integer>();
List<Coord> points = way.getPoints();
Way trailingWay = null;
String debugWayName = getDebugName(way);
// make sure the way has nodes at each end
points.get(0).incHighwayCount();
points.get(points.size() - 1).incHighwayCount();
// collect the Way's nodes and also split the way if any
// inter-node arc length becomes excessive
double arcLength = 0;
for(int i = 0; i < points.size(); ++i) {
Coord p = points.get(i);
// check if we should split the way at this point to limit
// the arc length between nodes
if((i + 1) < points.size()) {
double d = p.distance(points.get(i + 1));
if(d > MAX_ARC_LENGTH) {
double fraction = 0.99 * MAX_ARC_LENGTH / d;
Coord extrap = p.makeBetweenPoint(points.get(i + 1), fraction);
extrap.incHighwayCount();
points.add(i + 1, extrap);
double newD = p.distance(extrap);
log.warn("Way " + debugWayName + " contains a segment that is " + (int)d + "m long so I am adding a point to reduce its length to " + (int)newD + "m");
d = newD;
}
if((arcLength + d) > MAX_ARC_LENGTH) {
assert i > 0;
trailingWay = splitWayAt(way, i);
// this will have truncated the current Way's
// points so the loop will now terminate
log.warn("Splitting way " + debugWayName + " at " + points.get(i).toDegreeString() + " to limit arc length to " + (long)arcLength + "m");
}
else {
if(p.getHighwayCount() > 1)
// point is a node so zero arc length
arcLength = 0;
arcLength += d;
}
}
if(p.getHighwayCount() > 1) {
// this point is a node connecting highways
Integer nodeId = nodeIdMap.get(p);
if(nodeId == null) {
// assign a node id
nodeIdMap.put(p, nextNodeId++);
}
nodeIndices.add(i);
if((i + 1) < points.size() &&
nodeIndices.size() == MAX_NODES_IN_WAY) {
// this isn't the last point in the way so split
// it here to avoid exceeding the max nodes in way
// limit
trailingWay = splitWayAt(way, i);
// this will have truncated the current Way's
// points so the loop will now terminate
log.info("Splitting way " + debugWayName + " at " + points.get(i).toDegreeString() + " as it has at least " + MAX_NODES_IN_WAY + " nodes");
}
}
}
MapLine line = new MapLine();
elementSetup(line, gt, way);
line.setPoints(points);
MapRoad road = new MapRoad(way.getId(), line);
// set road parameters.
road.setRoadClass(gt.getRoadClass());
if (way.isBoolTag("oneway")) {
road.setDirection(true);
road.setOneway();
}
int speedIdx = -1;
if(!ignoreMaxspeeds) {
// maxspeed attribute overrides default for road type
String maxSpeed = way.getTag("maxspeed");
if(maxSpeed != null) {
speedIdx = getSpeedIdx(maxSpeed);
log.info(debugWayName + " maxspeed=" + maxSpeed + ", speedIndex=" + speedIdx);
}
}
road.setSpeed(speedIdx >= 0? speedIdx : gt.getRoadSpeed());
boolean[] noAccess = new boolean[RoadNetwork.NO_MAX];
String highwayType = way.getTag("highway");
if(highwayType == null) {
// it's a routable way but not a highway (e.g. a ferry)
// use the value of the route tag as the highwayType for
// the purpose of testing for access restrictions
highwayType = way.getTag("route");
}
for (AccessMapping anAccessMap : accessMap) {
int index = anAccessMap.index;
String type = anAccessMap.type;
String accessTagValue = way.getTag(type);
if (accessTagValue == null)
continue;
if (accessExplicitlyDenied(accessTagValue)) {
if (index == RoadNetwork.NO_MAX) {
// everything is denied access
for (int j = 1; j < accessMap.length; ++j)
noAccess[accessMap[j].index] = true;
} else {
// just the specific vehicle class is denied
// access
noAccess[index] = true;
}
log.info(type + " is not allowed in " + highwayType + " " + debugWayName);
} else if (accessExplicitlyAllowed(accessTagValue)) {
if (index == RoadNetwork.NO_MAX) {
// everything is allowed access
for (int j = 1; j < accessMap.length; ++j)
noAccess[accessMap[j].index] = false;
} else {
// just the specific vehicle class is allowed
// access
noAccess[index] = false;
}
log.info(type + " is allowed in " + highwayType + " " + debugWayName);
}
else if (accessTagValue.equalsIgnoreCase("destination")) {
if (type.equals("motorcar") ||
type.equals("motorcycle")) {
road.setNoThroughRouting();
} else if (type.equals("access")) {
log.warn("access=destination only affects routing for cars in " + highwayType + " " + debugWayName);
road.setNoThroughRouting();
} else {
log.warn(type + "=destination ignored in " + highwayType + " " + debugWayName);
}
} else if (accessTagValue.equalsIgnoreCase("unknown")) {
// implicitly allow access
} else {
log.warn("Ignoring unsupported access tag value " + type + "=" + accessTagValue + " in " + highwayType + " " + debugWayName);
}
}
road.setAccess(noAccess);
if(way.isBoolTag("toll"))
road.setToll();
Way origWay = originalWay.get(way);
if(origWay == null)
origWay = way;
int numNodes = nodeIndices.size();
road.setNumNodes(numNodes);
if(numNodes > 0) {
// replace Coords that are nodes with CoordNodes
boolean hasInternalNodes = false;
CoordNode lastCoordNode = null;
List<RestrictionRelation> lastRestrictions = null;
for(int i = 0; i < numNodes; ++i) {
int n = nodeIndices.get(i);
if(n > 0 && n < points.size() - 1)
hasInternalNodes = true;
Coord coord = points.get(n);
Integer nodeId = nodeIdMap.get(coord);
boolean boundary = boundaryCoords.contains(coord);
if(boundary) {
log.info("Way " + debugWayName + "'s point #" + n + " at " + points.get(n).toDegreeString() + " is a boundary node");
}
CoordNode thisCoordNode = new CoordNode(coord.getLatitude(), coord.getLongitude(), nodeId, boundary);
points.set(n, thisCoordNode);
// see if this node plays a role in any turn
// restrictions
if(lastRestrictions != null) {
// the previous node was the location of one or
// more restrictions
for(RestrictionRelation rr : lastRestrictions) {
if(rr.getToWay().equals(origWay)) {
rr.setToNode(thisCoordNode);
}
else if(rr.getFromWay().equals(origWay)) {
rr.setFromNode(thisCoordNode);
}
else {
rr.addOtherNode(thisCoordNode);
}
}
}
List<RestrictionRelation> theseRestrictions = restrictions.get(coord);
if(theseRestrictions != null) {
// this node is the location of one or more
// restrictions
for(RestrictionRelation rr : theseRestrictions) {
rr.setViaNode(thisCoordNode);
if(rr.getToWay().equals(origWay)) {
if(lastCoordNode != null)
rr.setToNode(lastCoordNode);
}
else if(rr.getFromWay().equals(origWay)) {
if(lastCoordNode != null)
rr.setFromNode(lastCoordNode);
}
else if(lastCoordNode != null) {
rr.addOtherNode(lastCoordNode);
}
}
}
lastRestrictions = theseRestrictions;
lastCoordNode = thisCoordNode;
}
road.setStartsWithNode(nodeIndices.get(0) == 0);
road.setInternalNodes(hasInternalNodes);
}
lineAdder.add(road);
if(trailingWay != null)
addRoadWithoutLoops(trailingWay, gt);
}
// split a Way at the specified point and return the new Way (the
// original Way is truncated)
Way splitWayAt(Way way, int index) {
Way trailingWay = new Way(way.getId());
List<Coord> wayPoints = way.getPoints();
int numPointsInWay = wayPoints.size();
for(int i = index; i < numPointsInWay; ++i)
trailingWay.addPoint(wayPoints.get(i));
// ensure split point becomes a node
wayPoints.get(index).incHighwayCount();
// copy the way's name and tags to the new way
trailingWay.setName(way.getName());
trailingWay.copyTags(way);
// remove the points after the split from the original way
// it's probably more efficient to remove from the end first
for(int i = numPointsInWay - 1; i > index; --i)
wayPoints.remove(i);
// associate the original Way to the new Way
Way origWay = originalWay.get(way);
if(origWay == null)
origWay = way;
originalWay.put(trailingWay, origWay);
return trailingWay;
}
// function to add points between adjacent nodes in a roundabout
// to make gps use correct exit number in routing instructions
void frigRoundabout(Way way, double frigFactor) {
List<Coord> wayPoints = way.getPoints();
int origNumPoints = wayPoints.size();
if(origNumPoints < 3) {
// forget it!
return;
}
int[] highWayCounts = new int[origNumPoints];
int middleLat = 0;
int middleLon = 0;
highWayCounts[0] = wayPoints.get(0).getHighwayCount();
for(int i = 1; i < origNumPoints; ++i) {
Coord p = wayPoints.get(i);
middleLat += p.getLatitude();
middleLon += p.getLongitude();
highWayCounts[i] = p.getHighwayCount();
}
middleLat /= origNumPoints - 1;
middleLon /= origNumPoints - 1;
Coord middleCoord = new Coord(middleLat, middleLon);
// account for fact that roundabout joins itself
--highWayCounts[0];
--highWayCounts[origNumPoints - 1];
for(int i = origNumPoints - 2; i >= 0; --i) {
Coord p1 = wayPoints.get(i);
Coord p2 = wayPoints.get(i + 1);
if(highWayCounts[i] > 1 && highWayCounts[i + 1] > 1) {
// both points will be nodes so insert a new point
// between them that (approximately) falls on the
// roundabout's perimeter
int newLat = (p1.getLatitude() + p2.getLatitude()) / 2;
int newLon = (p1.getLongitude() + p2.getLongitude()) / 2;
// new point has to be "outside" of existing line
// joining p1 and p2 - how far outside is determined
// by the ratio of the distance between p1 and p2
// compared to the distance of p1 from the "middle" of
// the roundabout (aka, the approx radius of the
// roundabout) - the higher the value of frigFactor,
// the further out the point will be
double scale = 1 + frigFactor * p1.distance(p2) / p1.distance(middleCoord);
newLat = (int)((newLat - middleLat) * scale) + middleLat;
newLon = (int)((newLon - middleLon) * scale) + middleLon;
Coord newPoint = new Coord(newLat, newLon);
double d1 = p1.distance(newPoint);
double d2 = p2.distance(newPoint);
double maxDistance = 100;
if(d1 >= MIN_DISTANCE_BETWEEN_NODES && d1 <= maxDistance &&
d2 >= MIN_DISTANCE_BETWEEN_NODES && d2 <= maxDistance) {
newPoint.incHighwayCount();
wayPoints.add(i + 1, newPoint);
}
}
}
}
int getSpeedIdx(String tag)
{
double kmh;
double factor = 1.0;
String speedTag = tag.toLowerCase().trim();
if(speedTag.matches(".*mph")) // Check if it is a limit in mph
{
speedTag = speedTag.replaceFirst("mph", "");
factor = 1.61;
}
else
speedTag = speedTag.replaceFirst("kmh", ""); // get rid of kmh just in case
try {
kmh = Integer.parseInt(speedTag) * factor;
}
catch (Exception e)
{
return -1;
}
if(kmh > 110)
return 7;
if(kmh > 90)
return 6;
if(kmh > 80)
return 5;
if(kmh > 60)
return 4;
if(kmh > 40)
return 3;
if(kmh > 20)
return 2;
if(kmh > 10)
return 1;
else
return 0;
}
protected boolean accessExplicitlyAllowed(String val) {
if (val == null)
return false;
return (val.equalsIgnoreCase("yes") ||
val.equalsIgnoreCase("designated") ||
val.equalsIgnoreCase("permissive"));
}
protected boolean accessExplicitlyDenied(String val) {
if (val == null)
return false;
return (val.equalsIgnoreCase("no") ||
val.equalsIgnoreCase("private"));
}
}
package uk.me.parabola.mkgmap.osmstyle;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.LevelInfo;
import uk.me.parabola.mkgmap.osmstyle.eval.SyntaxException;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.scan.TokType;
import uk.me.parabola.mkgmap.scan.Token;
import uk.me.parabola.mkgmap.scan.TokenScanner;
/**
* Read a type description from a style file.
*/
public class TypeReader {
private static final Logger log = Logger.getLogger(TypeReader.class);
private final int kind;
private final LevelInfo[] levels;
public TypeReader(int kind, LevelInfo[] levels) {
this.kind = kind;
this.levels = levels;
}
public GType readType(TokenScanner ts) {
// We should have a '[' to start with
Token t = ts.nextToken();
if (t == null || t.getType() == TokType.EOF)
throw new SyntaxException(ts, "No garmin type information given");
if (!t.getValue().equals("[")) {
throw new SyntaxException(ts, "No type definition");
}
ts.skipSpace();
String type = ts.nextValue();
if (!Character.isDigit(type.charAt(0)))
throw new SyntaxException(ts, "Garmin type number must be first. Saw '" + type + '\'');
log.debug("gtype", type);
GType gt = new GType(kind, type);
while (!ts.isEndOfFile()) {
ts.skipSpace();
String w = ts.nextValue();
if (w.equals("]"))
break;
if (w.equals("level")) {
setLevel(ts, gt);
} else if (w.equals("resolution")) {
setResolution(ts, gt);
} else if (w.equals("default_name")) {
gt.setDefaultName(nextValue(ts));
} else if (w.equals("road_class")) {
gt.setRoadClass(nextIntValue(ts));
} else if (w.equals("road_speed")) {
gt.setRoadSpeed(nextIntValue(ts));
} else if (w.equals("copy")) {
// reserved word. not currently used
} else if (w.equals("continue")) {
gt.setContinue();
} else if (w.equals("stop")) {
gt.setFinal();
} else {
throw new SyntaxException(ts, "Unrecognised type command '" + w + '\'');
}
}
gt.fixLevels(levels);
return gt;
}
private int nextIntValue(TokenScanner ts) {
if (ts.checkToken("="))
ts.nextToken();
try {
return ts.nextInt();
} catch (NumberFormatException e) {
throw new SyntaxException(ts, "Expecting numeric value");
}
}
/**
* Get the value in a 'name=value' pair.
*/
private String nextValue(TokenScanner ts) {
if (ts.checkToken("="))
ts.nextToken();
return ts.nextWord();
}
private void setResolution(TokenScanner ts, GType gt) {
String str = ts.nextWord();
log.debug("res word value", str);
try {
if (str.indexOf('-') >= 0) {
String[] minmax = str.split("-", 2);
gt.setMaxResolution(Integer.parseInt(minmax[0]));
gt.setMinResolution(Integer.parseInt(minmax[1]));
} else {
gt.setMinResolution(Integer.parseInt(str));
}
} catch (NumberFormatException e) {
gt.setMinResolution(24);
}
}
private void setLevel(TokenScanner ts, GType gt) {
String str = ts.nextWord();
try {
if (str.indexOf('-') >= 0) {
String[] minmax = str.split("-", 2);
gt.setMaxResolution(toResolution(Integer.parseInt(minmax[0])));
gt.setMinResolution(toResolution(Integer.parseInt(minmax[1])));
} else {
gt.setMinResolution(toResolution(Integer.parseInt(str)));
}
} catch (NumberFormatException e) {
gt.setMinResolution(24);
}
}
private int toResolution(int level) {
int max = levels.length - 1;
if (level > max)
throw new SyntaxException("Level number too large, max=" + max);
return levels[max - level].getBits();
}
}
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev