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 mkgmap-dev@lists.mkgmap.org.uk http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev