Codebase list mkgmap / 503a171
Imported Upstream version 0.0.0+svn3649 Bas Couwenberg 8 years ago
21 changed file(s) with 602 addition(s) and 384 deletion(s). Raw diff Collapse all Expand all
00 <?xml version="1.0"?>
11 <!--
2 File: build.xml
2 File: build.xml
33
44 Copyright (C) 2006, 2012 mkgmap contributors
55
433433
434434
435435 ;--adjust-turn-headings[=BITMASK]
436 : Where possible, ensure that turns off to side roads change
436 : now ignored, former explanation:
437 Where possible, ensure that turns off to side roads change
437438 heading sufficiently so that the GPS believes that a turn is
438439 required rather than a fork. This also avoids spurious
439440 instructions to "keep right/left" when the road doesn't
444445 :* 1 = increase angle between side road and outgoing main road
445446 :* 2 = increase angle between side road and incoming main road
446447
448 ;--cycle-map
449 : Tells mkgmap that the map is for cyclists. This assumes that
450 different vehicles are different kinds of bicycles, e.g. a way
451 with mkgmap:car=yes and mkgmap:bicycle=no may be a road that is
452 good for racing bikes, but not for other cyclists.
453 This allows to optimise sharp angles at junctions of those roads.
454 Don't use with the default style as that is a general style!
447455
448456 ;--report-similar-arcs
449457 : Issue a warning when more than one arc connects two nodes and
146146 <variant>BV</variant>
147147 <variant>BVT</variant>
148148 </country>
149 <country name="Brazil" abr="BRA" streetBeforeHousenumber="true">
149 <country name="Brasil" abr="BRA" streetBeforeHousenumber="true">
150150 <variant>BR</variant>
151151 <variant>BRA</variant>
152 <variant>Brazil</variant>
152153 </country>
153154 <country name="British Indian Ocean Territory" abr="IOT">
154155 <variant>IO</variant>
436436 specified, only zero-length arcs will be removed.
437437
438438 --adjust-turn-headings[=BITMASK]
439 Now ignored, former usage:
439440 Where possible, ensure that turns off to side roads change
440441 heading sufficiently so that the GPS believes that a turn is
441442 required rather than a fork. This also avoids spurious
448449 1 = increase angle between side road and outgoing main road
449450 2 = increase angle between side road and incoming main road
450451
452 --cycle-map
453 Tells mkgmap that the map is for cyclists. This assumes that
454 different vehicles are different kinds of bicycles, e.g. a way
455 with mkgmap:car=yes and mkgmap:bicycle=no may be a road that is
456 good for racing bikes, but not for other cyclists.
457 This allows to optimise sharp angles at junctions of those roads.
458 Don't use with the default style as that is a general style!
459
451460 --report-similar-arcs
452461 Issue a warning when more than one arc connects two nodes and
453462 the ways that the arcs are derived from contain identical
0 svn.version: 3643
1 build.timestamp: 2015-09-19T08:13:00+0100
0 svn.version: 3649
1 build.timestamp: 2015-10-27T06:51:19+0000
7171 # Check for carpool lane (they are not really supported yet so these lines are commented)
7272 # hov=* { add carpool='${hov}' }
7373 # (carpool=yes | carpool=designated | carpool=permissive | carpool=official) { set mkgmap:carpool=yes }
74
75 # Don't route through highway=construction, they are considered unusable
76 highway=construction {setaccess no}
77
1717 # Mark highways with the toll flag
1818 highway=* & (toll=yes|toll=true) { set mkgmap:toll=yes }
1919
20 # Hide proposed ways
21 highway=proposed {delete highway;delete junction}
20 # Hide proposed ways
21 (highway=proposed | highway=proposal | highway=planned | highway ~ '.*proposed.*') {delete highway;delete junction}
2222 # Hide removed ways
23 highway=razed {deletealltags}
23 (highway=razed | highway=dismantled) {deletealltags}
24 # Hide other non-existent ways
25 (highway=unbuilt | highway=neverbuilt | highway=rejected | highway ~ 'x-.*') {delete highway;delete junction}
26
2427 # Hide unaccessible tunnels
2528 highway=* & tunnel=yes & (access=private|access=no)
2629 & foot!=* & bicycle!=* {delete highway;delete junction}
105108
106109 # Ways that may or may not be useable
107110
108 # Treat ways under construction almost as highway=path
111 # Treat ways under construction almost as highway=path, see also extra rule in inc/access
109112 highway=construction { add mkgmap:dead-end-check = false; }
110113 [0x16 road_class=0 road_speed=0 resolution 23]
111114
4848 private final static short PART_OF_SHAPE2 = 0x0100; // use only in ShapeMerger
4949 private final static short END_OF_WAY = 0x0200; // use only in WrongAngleFixer
5050 private final static short HOUSENUMBER_NODE = 0x0400; // start/end of house number interval
51 private final static short ADDED_HOUSENUMBER_NODE = 0x0800; // node was added for house numbers
5152
5253 public final static int HIGH_PREC_BITS = 30;
5354 public final static int DELTA_SHIFT = 6;
8788 int lon30 = toBit30(longitude);
8889 this.latDelta = (byte) ((this.latitude << 6) - lat30);
8990 this.lonDelta = (byte) ((this.longitude << 6) - lon30);
90
91
9192 // verify math
9293 assert (this.latitude << 6) - latDelta == lat30;
9394 assert (this.longitude << 6) - lonDelta == lon30;
339340 }
340341
341342 /**
342 * Set or unset flag for {@link WrongAngleFixer}
343343 * @param b true or false
344344 */
345345 public void setNumberNode(boolean b) {
347347 this.flags |= HOUSENUMBER_NODE;
348348 else
349349 this.flags &= ~HOUSENUMBER_NODE;
350 }
351
352 /**
353 * @return if this is the beginning/end of a house number interval
354 */
355 public boolean isAddedNumberNode(){
356 return (flags & ADDED_HOUSENUMBER_NODE) != 0;
357 }
358
359 /**
360 * @param b true or false
361 */
362 public void setAddedNumberNode(boolean b) {
363 if (b)
364 this.flags |= ADDED_HOUSENUMBER_NODE;
365 else
366 this.flags &= ~ADDED_HOUSENUMBER_NODE;
350367 }
351368
352369 public int hashCode() {
0 /*
1 * Copyright (C) 2015
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 3 or
5 * version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * General Public License for more details.
11 */
12 package uk.me.parabola.imgfmt.app.net;
13
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.Comparator;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Map;
21
22 import uk.me.parabola.log.Logger;
23 import uk.me.parabola.util.EnhancedProperties;
24
25 /**
26 * Find sharp angles at junctions. The Garmin routing algorithm doesn't
27 * like to route on roads building a sharp angle. It adds a time penalty
28 * from 30 to 150 seconds and often prefers small detours instead.
29 * The penalty depends on the road speed and the vehicle, for pedestrian
30 * mode it is zero, for bicycles it is rather small, for cars it is high.
31 * The sharp angles typically don't exist in the real world, they are
32 * caused by the simplifications done by mappers.
33 *
34 * Maps created for cyclists typically "abuse" the car routing for racing
35 * bikes, but in this scenario the time penalties are much too high,
36 * and detours are likely.
37 *
38 * This method tries to modify the initial heading values of the arcs
39 * which are used to calculate the angles. Where possible, the values are
40 * changed so that angles appear larger.
41 *
42 * @author Gerd Petermann
43 *
44 */
45 public class AngleChecker {
46 private static final Logger log = Logger.getLogger(AngleChecker.class);
47
48 private boolean ignoreSharpAngles;
49 private boolean cycleMap;
50 // private final Coord test = new Coord(48.074815,16.272771);
51
52 private final int MIN_ANGLE = 0x10;
53 private final int MIN_LOW_SPEED_ANGLE = 0x20;
54
55 private int mask;
56
57 // helper class to collect multiple arcs with (nearly) the same initial headings
58 private class ArcGroup {
59 float initialHeading;
60 byte imgHeading;
61 int isOneWayTrueCount;
62 int isForwardTrueCount;
63 int maxRoadSpeed;
64 byte orAccessMask;
65 HashSet<RoadDef> roadDefs = new HashSet<>();
66
67 List<RouteArc> arcs = new ArrayList<>();
68 public void addArc(RouteArc arc) {
69 arcs.add(arc);
70 if (arc.getRoadDef().isOneway())
71 isOneWayTrueCount++;
72 if (arc.isForward())
73 isForwardTrueCount++;
74 if (arc.getRoadDef().getRoadSpeed() > maxRoadSpeed)
75 maxRoadSpeed = arc.getRoadDef().getRoadSpeed();
76 orAccessMask |= arc.getRoadDef().getAccess();
77 roadDefs.add(arc.getRoadDef());
78 }
79 public float getInitialHeading() {
80 return initialHeading;
81 }
82 public boolean isOneway() {
83 return isOneWayTrueCount == arcs.size();
84 }
85 public boolean isForward() {
86 return isForwardTrueCount == arcs.size();
87 }
88 /**
89 * @return
90 */
91 public void setInitialHeading(float modIH) {
92 while (modIH > 180)
93 modIH -= 360;
94 while (modIH < -180)
95 modIH += 360;
96 initialHeading = modIH;
97 imgHeading = (byte) (RouteArc.directionFromDegrees(initialHeading) & mask);
98
99 for (RouteArc arc : arcs){
100 arc.setInitialHeading(modIH);
101 }
102 }
103
104 public String toString(){
105 return arcs.get(0).toString();
106 }
107 }
108
109 public void config(EnhancedProperties props) {
110 // undocumented option - usually used for debugging only
111 ignoreSharpAngles = props.getProperty("ignore-sharp-angles", false);
112 cycleMap = props.getProperty("cycle-map", false);
113 // float a = 0;
114 // for (int i = 0; i <= 1440; i++){
115 // int ar = (int) Math.round(a * 256.0 / 360);
116 // int am = ar & 0xf0;
117 // log.error(a,ar,"0x" + Integer.toHexString(am));
118 // a +=0.25;
119 // if (a >= 180)
120 // a -= 360;
121 // }
122 return;
123 }
124
125 public void check(Map<Integer, RouteNode> nodes) {
126 if (!ignoreSharpAngles){
127 byte sharpAnglesCheckMask = cycleMap ? (byte) (0xff & ~AccessTagsAndBits.FOOT) : AccessTagsAndBits.BIKE;
128
129 for (RouteNode node : nodes.values()){
130 mask = 0xf0; // we assume compacted format
131 fixSharpAngles(node, sharpAnglesCheckMask);
132 }
133 }
134 }
135
136 public void fixSharpAngles(RouteNode node, byte sharpAnglesCheckMask) {
137
138 // get direct arcs leaving the node
139 List<ArcGroup> arcGroups = buildArcGroups(node);
140
141 int n = arcGroups.size();
142 if (n <= 1)
143 return;
144 // sort the arcs by initial heading
145 Collections.sort(arcGroups, new Comparator<ArcGroup>() {
146 public int compare(ArcGroup ag1, ArcGroup ag2) {
147 if (ag1.initialHeading < ag2.initialHeading)
148 return -1;
149 if (ag1.initialHeading > ag2.initialHeading)
150 return 1;
151 return 0;
152 }
153 });
154
155 class AngleAttr {
156 int angle;
157 int maskedAngle;
158 int maskedMinAngle = MIN_ANGLE;
159 boolean noAccess;
160
161 int maskedDeltaToMin(){
162 return maskedAngle - maskedMinAngle;
163 }
164 void setMaskedMinAngle(int maskedMinAngle){
165 this.maskedMinAngle = maskedMinAngle;
166 }
167
168 public String toString(){
169 return angle + "° " + maskedAngle + " " + maskedMinAngle + " " + noAccess;
170 }
171 }
172
173 // step one: calculate the existing angles
174 AngleAttr[] angles = new AngleAttr[n];
175 for (int i = 0; i < n; i++){
176 ArcGroup ag1 = arcGroups.get(i);
177 ArcGroup ag2 = arcGroups.get(i+1 < n ? i+1 : 0);
178 AngleAttr angleAttr = new AngleAttr();
179 angles[i] = angleAttr;
180 angleAttr.angle = Math.round(ag2.getInitialHeading() - ag1.getInitialHeading());
181 angleAttr.maskedAngle = ag2.imgHeading - ag1.imgHeading;
182 if (i + 1 >= n){
183 angleAttr.angle += 360;
184 }
185 if (angleAttr.maskedAngle < 0)
186 angleAttr.maskedAngle += 256;
187
188 if (ag1.isOneway() && ag1.isForward()){
189 // the "incoming" arc is a wrong direction oneway
190 angleAttr.noAccess = true;
191 } else if (ag2.isOneway() && ag2.isForward() == false){
192 // the "outgoing" arc is a wrong direction oneway
193 angleAttr.noAccess = true;
194 }
195
196 // if (node.getCoord().distance(test) < 2){
197 // if (angleAttr.angle == 20){
198 // angleAttr.maskedMinAngle = 0x30;
199 // continue;
200 // }
201 // }
202 int sumSpeeds = ag1.maxRoadSpeed + ag2.maxRoadSpeed;
203 if (sumSpeeds <= 1)
204 continue;
205 byte pathAccessMask = (byte) (ag1.orAccessMask & ag2.orAccessMask);
206 if (pathAccessMask == 0){
207 // no common vehicle allowed on both arcs
208 angleAttr.noAccess = true;
209 }
210 if (angleAttr.noAccess)
211 continue;
212 int maskedMinAngle = MIN_LOW_SPEED_ANGLE;
213 // the Garmin algorithm sees rounded values, so the thresholds are probably
214 // near 22.5 (0x10), 45(0x20), 67.5 (0x30), 90, 112.5 (0x40)
215
216 // the following code doesn't seem to improve anything, I leave it as comment
217 // for further experiments.
218 // if (cycleMap){
219 // if (sumSpeeds >= 14)
220 // maskedMinAngle = 0x80;
221 // if (sumSpeeds >= 12)
222 // maskedMinAngle = 0x70;
223 // if (sumSpeeds >= 10)
224 // maskedMinAngle = 0x60;
225 // if (sumSpeeds >= 8)
226 // maskedMinAngle = 0x50;
227 // else if (sumSpeeds >= 6)
228 // maskedMinAngle = 0x40;
229 // else if (sumSpeeds >= 4)
230 // maskedMinAngle = 0x30;
231 // }
232 angleAttr.setMaskedMinAngle(maskedMinAngle);
233
234 if (angleAttr.maskedDeltaToMin() >= 0)
235 continue;
236
237 String ignoredReason = null;
238 if (pathAccessMask == AccessTagsAndBits.FOOT)
239 ignoredReason = "because it can only be used by pedestrians";
240 else if ((pathAccessMask & sharpAnglesCheckMask) == 0)
241 ignoredReason = "because it can not be used by bike";
242 else if (ag1.isOneway() && ag2.isOneway()){
243 // both arcs are one-ways, probably the road splits here
244 // to avoid the sharp angles we are looking for
245 ignoredReason = "because it seems to be a flare road";
246 }
247 else if (ag1.roadDefs.size() == 1 && ag2.roadDefs.size() == 1 && ag1.roadDefs.containsAll(ag2.roadDefs)){
248 ignoredReason = "because both arcs belong to the same road";
249 }
250 if (ignoredReason != null){
251 if (log.isInfoEnabled()){
252 String sharpAngle = "sharp angle " + angleAttr.angle + "° at " + node.getCoord().toDegreeString();
253 log.info(sharpAngle, "headings",getCompassBearing(ag1.getInitialHeading()) , getCompassBearing(ag2.getInitialHeading()),"speeds",ag1.maxRoadSpeed, ag2.maxRoadSpeed);
254 log.info("ignoring", sharpAngle, ignoredReason);
255 }
256 angleAttr.setMaskedMinAngle(MIN_ANGLE);
257 angleAttr.noAccess = true;
258 }
259 }
260
261 for (int i = 0; i < n; i++){
262 AngleAttr aa = angles[i];
263 if (aa.maskedAngle >= aa.maskedMinAngle || aa.noAccess)
264 continue;
265 int oldAngle = aa.angle;
266 ArcGroup ag1 = arcGroups.get(i);
267 ArcGroup ag2 = arcGroups.get(i+1 < n ? i+1 : 0);
268 String sharpAngle = "";
269 if (log.isInfoEnabled()){
270 sharpAngle = "sharp angle " + aa.angle + "° at " + node.getCoord().toDegreeString();
271 log.info(sharpAngle, "headings",getCompassBearing(ag1.getInitialHeading()) , getCompassBearing(ag2.getInitialHeading()),"speeds",ag1.maxRoadSpeed, ag2.maxRoadSpeed);
272 }
273
274 // XXX restrictions ?
275 boolean fixed = false;
276 int wantedIncrement = Math.abs(aa.maskedDeltaToMin()) ;
277 AngleAttr predAA = angles[i == 0 ? n - 1 : i - 1];
278 AngleAttr nextAA = angles[i >= n - 1 ? 0 : i + 1];
279
280 // we can increase the angle by changing the heading values of one or both arcs
281 // find out which one to change first
282 byte origImgDir1 = ag1.imgHeading;
283 byte origImgDir2 = ag2.imgHeading;
284 int origImgAngle = getImgAngle(ag1.imgHeading, ag2.imgHeading);
285
286 int deltaPred = predAA.maskedDeltaToMin();
287 int deltaNext = nextAA.maskedDeltaToMin();
288
289 if (deltaNext > 0 && (deltaNext > deltaPred || deltaPred < wantedIncrement)){
290 int usedIncrement = Math.min(wantedIncrement, deltaNext);
291 float oldIH = ag2.getInitialHeading();
292 int modIH = ag2.imgHeading + usedIncrement;
293 if (modIH > 128)
294 modIH -= 256;
295 ag2.setInitialHeading(modIH * 360/256);
296 int modAngle = Math.round(ag2.getInitialHeading() - ag1.getInitialHeading());
297 if (modAngle < 0)
298 modAngle += 360;
299 int modImgAngle = getImgAngle(ag1.imgHeading, ag2.imgHeading);
300 if (modImgAngle >= aa.maskedMinAngle)
301 fixed = true;
302 log.info(sharpAngle, "changing arc with heading", getCompassBearing(oldIH), "->",getCompassBearing(ag2.getInitialHeading()),
303 "angle is now",modAngle+"°, in img format:",origImgDir2,"->",ag2.imgHeading, "img angle (0-255)",origImgAngle, "->", modImgAngle);
304 aa.angle = modAngle;
305 nextAA.angle -= usedIncrement;
306 }
307 if (!fixed && deltaPred > 0){
308 wantedIncrement = Math.abs(aa.maskedDeltaToMin());
309 int usedIncrement = Math.min(wantedIncrement, deltaPred);
310 float oldIH = ag1.getInitialHeading();
311 int modIH = ag1.imgHeading - usedIncrement;
312 if (modIH < -128)
313 modIH += 256;
314 ag1.setInitialHeading(modIH * 360/256);
315 int modAngle = Math.round(ag2.getInitialHeading() - ag1.getInitialHeading());
316 if (modAngle < 0)
317 modAngle += 360;
318 int modImgAngle = getImgAngle(ag1.imgHeading, ag2.imgHeading);
319 if (modImgAngle >= aa.maskedMinAngle)
320 fixed = true;
321
322 log.info(sharpAngle, "changing arc with heading", getCompassBearing(oldIH), "->", getCompassBearing(ag1.getInitialHeading()),
323 "angle is now",modAngle+"°, in img format:",origImgDir1,"->",ag1.imgHeading, "img angle (0-255)",origImgAngle, "->", modImgAngle);
324 aa.angle = modAngle;
325 predAA.angle -= usedIncrement;
326 }
327 if (!fixed){
328 if (aa.angle == oldAngle)
329 log.info(sharpAngle, "don't know how to fix it");
330 else
331 log.info(sharpAngle, "don't know how to enlarge it further");
332 }
333 }
334 return;
335 }
336
337
338 /**
339 * Combine arcs with nearly the same initial heading.
340 * @param node
341 * @return
342 */
343 private List<ArcGroup> buildArcGroups(RouteNode node) {
344 List<ArcGroup> arcGroups = new ArrayList<>();
345 List<RouteArc> directArcs = new ArrayList<>();
346 for (RouteArc arc : node.getArcs()){
347 if (arc.isDirect()){
348 directArcs.add(arc);
349 }
350 }
351 if (directArcs.size() < 2)
352 return arcGroups; // should not happen
353
354 // sort the arcs by initial heading
355 Collections.sort(directArcs, new Comparator<RouteArc>() {
356 public int compare(RouteArc ra1, RouteArc ra2) {
357 if (ra1.getInitialHeading() < ra2.getInitialHeading())
358 return -1;
359 if (ra1.getInitialHeading() > ra2.getInitialHeading())
360 return 1;
361 int d = Integer.compare(ra1.getPointsHash(), ra2.getPointsHash());
362 if (d != 0)
363 return d;
364 d = Long.compare(ra1.getRoadDef().getId() , ra2.getRoadDef().getId());
365 if (d != 0)
366 return d;
367 return d;
368 }
369 });
370
371 Iterator<RouteArc> iter = directArcs.listIterator();
372 RouteArc arc1 = iter.next();
373 boolean addArc1 = false;
374 while (iter.hasNext() || addArc1){
375 ArcGroup ag = new ArcGroup();
376 ag.initialHeading = arc1.getInitialHeading();
377 ag.addArc(arc1);
378 arcGroups.add(ag);
379 addArc1 = false;
380 while (iter.hasNext()){
381 RouteArc arc2 = iter.next();
382 if (Math.abs(arc1.getInitialHeading()- arc2.getInitialHeading()) < 1){
383 if (arc1.getDest() != arc2.getDest() && arc1.getRoadDef().getId() != arc2.getRoadDef().getId())
384 log.warn("sharp angle < 1° at",node.getCoord().toDegreeString(),",maybe duplicated OSM way with bearing",getCompassBearing(arc1.getInitialHeading()));
385 ag.addArc(arc2);
386 } else{
387 arc1 = arc2;
388 if (iter.hasNext() == false)
389 addArc1 = true;
390 break;
391 }
392 }
393 }
394 for (ArcGroup ag : arcGroups){
395 ag.imgHeading = (byte) (RouteArc.directionFromDegrees(ag.initialHeading) & mask);
396 }
397 return arcGroups;
398 }
399
400 /**
401 * for log messages
402 */
403 private String getCompassBearing (float bearing){
404 float cb = (bearing + 360) % 360;
405 return Math.round(cb) + "°";
406 }
407
408 /**
409 * Debugging aid: guess what angle the Garmin algorithm is using.
410 * @param heading1
411 * @param heading2
412 * @return
413 */
414 private int getImgAngle(byte heading1, byte heading2){
415 int angle = heading2 - heading1;
416 if (angle < 0)
417 angle += 256;
418 if (angle > 255)
419 angle -= 256;
420 return angle;
421 }
422
423 }
4848 private final List<RouteNode> boundary = new ArrayList<>();
4949 private final List<RoadDef> roadDefs = new ArrayList<>();
5050 private List<RouteCenter> centers = new ArrayList<>();
51 private int adjustTurnHeadings ;
51 private AngleChecker angleChecker = new AngleChecker();
52
5253 private boolean checkRoundabouts;
5354 private boolean checkRoundaboutFlares;
5455 private int maxFlareLengthRatio ;
5556 private boolean reportSimilarArcs;
5657
5758 public void config(EnhancedProperties props) {
58 String ath = props.getProperty("adjust-turn-headings");
59 if(ath != null) {
60 if(ath.length() > 0)
61 adjustTurnHeadings = Integer.decode(ath);
62 else
63 adjustTurnHeadings = RouteNode.ATH_DEFAULT_MASK;
64 }
6559 checkRoundabouts = props.getProperty("check-roundabouts", false);
6660 checkRoundaboutFlares = props.getProperty("check-roundabout-flares", false);
6761 maxFlareLengthRatio = props.getProperty("max-flare-length-ratio", 0);
68
6962 reportSimilarArcs = props.getProperty("report-similar-arcs", false);
63 angleChecker.config(props);
7064 }
7165
7266 public void addRoad(RoadDef roadDef, List<Coord> coordList) {
127121
128122 RouteNode node1 = getOrAddNode(lastId, lastCoord);
129123 RouteNode node2 = getOrAddNode(id, co);
130
131124 if(node1 == node2)
132125 log.error("Road " + roadDef + " contains consecutive identical nodes at " + co.toOSMURL() + " - routing will be broken");
133126 else if(arcLength == 0)
134127 log.warn("Road " + roadDef + " contains zero length arc at " + co.toOSMURL());
135128
136129 Coord forwardBearingPoint = coordList.get(lastIndex + 1);
137 if(lastCoord.equals(forwardBearingPoint)) {
130 if(lastCoord.equals(forwardBearingPoint) || forwardBearingPoint.isAddedNumberNode()) {
138131 // bearing point is too close to last node to be
139132 // useful - try some more points
140133 for(int bi = lastIndex + 2; bi <= index; ++bi) {
141 if(!lastCoord.equals(coordList.get(bi))) {
142 forwardBearingPoint = coordList.get(bi);
143 break;
144 }
134 Coord coTest = coordList.get(bi);
135 if (coTest.isAddedNumberNode() || lastCoord.equals(coTest))
136 continue;
137 forwardBearingPoint = coTest;
138 break;
145139 }
146140 }
147141 Coord reverseBearingPoint = coordList.get(index - 1);
148 if(co.equals(reverseBearingPoint)) {
142 if(co.equals(reverseBearingPoint) || reverseBearingPoint.isAddedNumberNode()) {
149143 // bearing point is too close to this node to be
150144 // useful - try some more points
151145 for(int bi = index - 2; bi >= lastIndex; --bi) {
152 if(!co.equals(coordList.get(bi))) {
153 reverseBearingPoint = coordList.get(bi);
154 break;
155 }
146 Coord coTest = coordList.get(bi);
147 if (coTest.isAddedNumberNode() || co.equals(coTest))
148 continue;
149 reverseBearingPoint = coTest;
150 break;
156151 }
157152 }
158153
161156
162157 double reverseInitialBearing = co.bearingTo(reverseBearingPoint);
163158 double directLength = (lastIndex + 1 == index) ? arcLength : lastCoord.distance(co);
164 double reverseFinalBearing, forwardFinalBearing, reverseDirectBearing;
159 double reverseDirectBearing = 0;
165160 if (directLength > 0){
166161 // bearing on rhumb line is a constant, so we can simply revert
167162 reverseDirectBearing = (forwardDirectBearing <= 0) ? 180 + forwardDirectBearing: -(180 - forwardDirectBearing) % 180.0;
168 forwardFinalBearing = (reverseInitialBearing <= 0) ? 180 + reverseInitialBearing : -(180 - reverseInitialBearing) % 180.0;
169 reverseFinalBearing = (forwardInitialBearing <= 0) ? 180 + forwardInitialBearing : -(180 - forwardInitialBearing) % 180.0;
170 }
171 else {
172 reverseDirectBearing = 0;
173 forwardFinalBearing = 0;
174 reverseFinalBearing = 0;
175163 }
176164 // Create forward arc from node1 to node2
177165 RouteArc arc = new RouteArc(roadDef,
178166 node1,
179167 node2,
180168 forwardInitialBearing,
181 forwardFinalBearing,
182169 forwardDirectBearing,
183170 arcLength,
184171 arcLength,
191178 RouteArc reverseArc = new RouteArc(roadDef,
192179 node2, node1,
193180 reverseInitialBearing,
194 reverseFinalBearing,
195181 reverseDirectBearing,
196182 arcLength,
197183 arcLength,
268254 if(reportSimilarArcs)
269255 node.reportSimilarArcs();
270256 }
271 if(adjustTurnHeadings != 0)
272 node.tweezeArcs(adjustTurnHeadings);
257
273258 nod1.addNode(node);
274259 n++;
275260 }
280265
281266 public List<RouteCenter> getCenters() {
282267 if (centers.isEmpty()){
268 angleChecker.check(nodes);
283269 addArcsToMajorRoads();
284270 splitCenters();
285271 }
4242
4343 // heading / bearing:
4444 private float initialHeading; // degrees (A-> B in an arc ABCD)
45 private final float finalHeading; // degrees (C-> D in an arc ABCD)
4645 private final float directHeading; // degrees (A-> D in an arc ABCD)
4746
4847 private final RoadDef roadDef;
8079 * @param source The source node. (A)
8180 * @param dest The destination node (E).
8281 * @param initialBearing The initial heading (signed degrees) (A->B)
83 * @param finalBearing The final heading (signed degrees) (D->E)
8482 * @param directBearing The direct heading (signed degrees) (A->E)
8583 * @param arcLength the length of the arc in meter (A->B->C->D->E)
8684 * @param pathLength the length of the arc in meter (summed length for additional arcs)
8987 */
9088 public RouteArc(RoadDef roadDef,
9189 RouteNode source, RouteNode dest,
92 double initialBearing, double finalBearing, double directBearing,
90 double initialBearing, double directBearing,
9391 double arcLength,
9492 double pathLength,
9593 double directLength,
9997 this.source = source;
10098 this.dest = dest;
10199 this.initialHeading = (float) initialBearing;
102 this.finalHeading = (float) finalBearing;
103100 this.directHeading = (directBearing < 180) ? (float) directBearing : -180.0f;
104101 int len = NODHeader.metersToRaw(arcLength);
105102 if (len >= (1 << 22)) {
128125 return initialHeading;
129126 }
130127
128 public float getDirectHeading() {
129 return directHeading;
130 }
131
131132 public void setInitialHeading(float ih) {
132133 initialHeading = ih;
133134 }
134135
135136 public float getFinalHeading() {
136 return finalHeading;
137 float fh = 0;
138 if (lengthInMeter != 0){
139 fh = getReverseArc().getInitialHeading();
140 fh = (fh <= 0) ? 180.0f + fh : -(180.0f - fh) % 180.0f;
141 }
142 return fh;
137143 }
138144
139145 public RouteNode getSource() {
223229 return lengthInMeter;
224230 }
225231
226 public static byte directionFromDegrees(double dir) {
232 public static byte directionFromDegrees(float dir) {
227233 return (byte) Math.round(dir * 256.0 / 360) ;
228234 }
229235
1616 import it.unimi.dsi.fastutil.ints.IntArrayList;
1717 import java.util.ArrayList;
1818 import java.util.Collections;
19 import java.util.Comparator;
2019 import java.util.HashSet;
2120 import java.util.Iterator;
2221 import java.util.List;
2322 import uk.me.parabola.imgfmt.app.Coord;
2423 import uk.me.parabola.imgfmt.app.CoordNode;
2524 import uk.me.parabola.imgfmt.app.ImgFileWriter;
26 import uk.me.parabola.imgfmt.app.Label;
2725 import uk.me.parabola.log.Logger;
2826
2927 /**
5149 private static final int F_ARCS = 0x40;
5250 // only used internally in mkgmap
5351 private static final int F_DISCARDED = 0x100; // node has been discarded
54
55 private static final int MAX_MAIN_ROAD_HEADING_CHANGE = 120;
56 private static final int MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS = 45;
57 private static final int MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS = 50;
5852
5953 private int offsetNod1 = -1;
6054
349343 */
350344 public int compareTo(RouteNode otherNode) {
351345 return coord.compareTo(otherNode.getCoord());
352 }
353
354 private static boolean possiblySameRoad(RouteArc raa, RouteArc rab) {
355
356 RoadDef rda = raa.getRoadDef();
357 RoadDef rdb = rab.getRoadDef();
358
359 if(rda.getId() == rdb.getId()) {
360 // roads have the same (OSM) id
361 return true;
362 }
363
364 boolean bothArcsNamed = false;
365 for(Label laba : rda.getLabels()) {
366 if(laba != null && laba.getOffset() != 0) {
367 for(Label labb : rdb.getLabels()) {
368 if(labb != null && labb.getOffset() != 0) {
369 bothArcsNamed = true;
370 if(laba.equals(labb)) {
371 // the roads have the same label
372 if(rda.isLinkRoad() == rdb.isLinkRoad()) {
373 // if both are a link road or both are
374 // not a link road, consider them the
375 // same road
376 return true;
377 }
378 // One is a link road and the other isn't
379 // so consider them different roads - this
380 // is because people often give a link
381 // road that's leaving some road the same
382 // ref as that road but it suits us better
383 // to consider them as different roads
384 return false;
385 }
386 }
387 }
388 }
389 }
390
391 if(bothArcsNamed) {
392 // both roads have names and they don't match
393 return false;
394 }
395
396 // at least one road is unnamed
397 if(rda.isRoundabout() && rdb.isRoundabout()) {
398 // hopefully, segments of the same (unnamed) roundabout
399 return true;
400 }
401
402 return false;
403 }
404
405 private static boolean rightTurnRequired(float inHeading, float outHeading, float otherHeading) {
406 // given the headings of the incoming, outgoing and side
407 // roads, decide whether a side road is to the left or the
408 // right of the main road
409
410 outHeading -= inHeading;
411 while(outHeading < -180)
412 outHeading += 360;
413 while(outHeading > 180)
414 outHeading -= 360;
415
416 otherHeading -= inHeading;
417 while(otherHeading < -180)
418 otherHeading += 360;
419 while(otherHeading > 180)
420 otherHeading -= 360;
421
422 return otherHeading > outHeading;
423 }
424
425 private static final int ATH_OUTGOING = 1;
426 private static final int ATH_INCOMING = 2;
427
428 public static final int ATH_DEFAULT_MASK = ATH_OUTGOING | ATH_INCOMING;
429
430 public void tweezeArcs(int mask) {
431 if(arcs.size() >= 3) {
432
433 // detect the "shallow turn" scenario where at a junction
434 // on some "main" road, the side road leaves the main
435 // road at a very shallow angle and the GPS says "keep
436 // right/left" when it would be better if it said "turn
437 // right/left"
438
439 // also helps to produce a turn instruction when the main
440 // road bends sharply but the side road keeps close to the
441 // original heading
442
443 // the code tries to detect a pair of arcs (the "incoming"
444 // arc and the "outgoing" arc) that are the "main road"
445 // and the remaining arc (called the "other" arc) which is
446 // the "side road"
447
448 // having worked out the roles for the arcs, the heuristic
449 // applied is that if the main road doesn't change its
450 // heading by more than maxMainRoadHeadingChange, ensure
451 // that the side road heading differs from the outgoing
452 // heading by at least
453 // minDiffBetweenOutgoingAndOtherArcs and the side road
454 // heading differs from the incoming heading by at least
455 // minDiffBetweenIncomingAndOtherArcs
456
457 // list of outgoing arcs discovered at this node
458 List<RouteArc> outgoingArcs = new ArrayList<RouteArc>();
459
460 // sort incoming arcs by decreasing class/speed
461 List<RouteArc> inArcs = new ArrayList<RouteArc>();
462 for (RouteArc arc : arcs){
463 if (arc.isDirect())
464 inArcs.add(arc.getReverseArc());
465 }
466
467 Collections.sort(inArcs, new Comparator<RouteArc>() {
468 public int compare(RouteArc ra1, RouteArc ra2) {
469 int c1 = ra1.getRoadDef().getRoadClass();
470 int c2 = ra2.getRoadDef().getRoadClass();
471 if(c1 == c2)
472 return (ra2.getRoadDef().getRoadSpeed() -
473 ra1.getRoadDef().getRoadSpeed());
474 return c2 - c1;
475 }
476 });
477
478 // look at incoming arcs in order of decreasing class/speed
479 for(RouteArc inArc : inArcs) {
480
481 RoadDef inRoadDef = inArc.getRoadDef();
482
483 if(!inArc.isForward() && inRoadDef.isOneway()) {
484 // ignore reverse arc if road is oneway
485 continue;
486 }
487
488 float inHeading = inArc.getFinalHeading();
489 // determine the outgoing arc that is likely to be the
490 // same road as the incoming arc
491 RouteArc outArc = null;
492
493 if(throughRoutes != null) {
494 // through_route relations have the highest precedence
495 for(RouteArc[] pair : throughRoutes) {
496 if(pair[0] == inArc) {
497 outArc = pair[1];
498 log.info("Found through route from " + inRoadDef + " to " + outArc.getRoadDef());
499 break;
500 }
501 }
502 }
503
504 if(outArc == null) {
505 // next, if oa has the same RoadDef as inArc, it's
506 // definitely the same road
507 for(RouteArc oa : arcs) {
508 if (oa.isDirect() == false)
509 continue;
510 if(oa.getDest() != inArc.getSource()) {
511 // this arc is not going to the same node as
512 // inArc came from
513 if(oa.getRoadDef() == inRoadDef) {
514 outArc = oa;
515 break;
516 }
517 }
518 }
519 }
520
521 if(outArc == null) {
522 // next, although the RoadDefs don't match, use
523 // possiblySameRoad() to see if the roads' id or
524 // labels (names/refs) match
525 for(RouteArc oa : arcs) {
526 if (oa.isDirect() == false)
527 continue;
528 if(oa.getDest() != inArc.getSource()) {
529 // this arc is not going to the same node as
530 // inArc came from
531 if((oa.isForward() || !oa.getRoadDef().isOneway()) &&
532 possiblySameRoad(inArc, oa)) {
533 outArc = oa;
534 break;
535 }
536 }
537 }
538 }
539
540 // if we did not find the outgoing arc, give up with
541 // this incoming arc
542 if(outArc == null) {
543 //log.info("Can't continue road " + inRoadDef + " at " + coord.toOSMURL());
544 continue;
545 }
546
547 // remember that this arc is an outgoing arc
548 outgoingArcs.add(outArc);
549
550 float outHeading = outArc.getInitialHeading();
551 float mainHeadingDelta = outHeading - inHeading;
552 while(mainHeadingDelta > 180)
553 mainHeadingDelta -= 360;
554 while(mainHeadingDelta < -180)
555 mainHeadingDelta += 360;
556 //log.info(inRoadDef + " continues to " + outArc.getRoadDef() + " with a heading change of " + mainHeadingDelta + " at " + coord.toOSMURL());
557
558 if(Math.abs(mainHeadingDelta) > MAX_MAIN_ROAD_HEADING_CHANGE) {
559 // if the continuation road heading change is
560 // greater than maxMainRoadHeadingChange don't
561 // adjust anything
562 continue;
563 }
564
565 for(RouteArc otherArc : arcs) {
566 if (otherArc.isDirect() == false)
567 continue;
568
569 // for each other arc leaving this node, tweeze
570 // its heading if its heading change from the
571 // outgoing heading is less than
572 // minDiffBetweenOutgoingAndOtherArcs or its
573 // heading change from the incoming heading is
574 // less than minDiffBetweenIncomingAndOtherArcs
575
576 if(otherArc.getDest() == inArc.getSource() ||
577 otherArc == outArc) {
578 // we're looking at the incoming or outgoing
579 // arc, ignore it
580 continue;
581 }
582
583 if(!otherArc.isForward() &&
584 otherArc.getRoadDef().isOneway()) {
585 // ignore reverse arc if road is oneway
586 continue;
587 }
588
589 if(inRoadDef.isLinkRoad() &&
590 otherArc.getRoadDef().isLinkRoad()) {
591 // it's a link road leaving a link road so
592 // leave the angle unchanged to avoid
593 // introducing a time penalty by increasing
594 // the angle (this stops the router using link
595 // roads that "cut the corner" at roundabouts)
596 continue;
597 }
598
599 if(outgoingArcs.contains(otherArc)) {
600 // this arc was previously matched as an
601 // outgoing arc so we don't want to change its
602 // heading now
603 continue;
604 }
605
606 float otherHeading = otherArc.getInitialHeading();
607 float outToOtherDelta = otherHeading - outHeading;
608 while(outToOtherDelta > 180)
609 outToOtherDelta -= 360;
610 while(outToOtherDelta < -180)
611 outToOtherDelta += 360;
612 float inToOtherDelta = otherHeading - inHeading;
613 while(inToOtherDelta > 180)
614 inToOtherDelta -= 360;
615 while(inToOtherDelta < -180)
616 inToOtherDelta += 360;
617
618 float newHeading = otherHeading;
619 if(rightTurnRequired(inHeading, outHeading, otherHeading)) {
620 // side road to the right
621 if((mask & ATH_OUTGOING) != 0 &&
622 Math.abs(outToOtherDelta) < MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS)
623 newHeading = outHeading + MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS;
624 if((mask & ATH_INCOMING) != 0 &&
625 Math.abs(inToOtherDelta) < MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS) {
626 float nh = inHeading + MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS;
627 if(nh > newHeading)
628 newHeading = nh;
629 }
630
631 if(newHeading > 180)
632 newHeading -= 360;
633 }
634 else {
635 // side road to the left
636 if((mask & ATH_OUTGOING) != 0 &&
637 Math.abs(outToOtherDelta) < MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS)
638 newHeading = outHeading - MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS;
639 if((mask & ATH_INCOMING) != 0 &&
640 Math.abs(inToOtherDelta) < MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS) {
641 float nh = inHeading - MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS;
642 if(nh < newHeading)
643 newHeading = nh;
644 }
645
646 if(newHeading < -180)
647 newHeading += 360;
648 }
649 if(Math.abs(newHeading - otherHeading) > 0.0000001) {
650 otherArc.setInitialHeading(newHeading);
651 log.info("Adjusting turn heading from " + otherHeading + " to " + newHeading + " at junction of " + inRoadDef + " and " + otherArc.getRoadDef() + " at " + coord.toOSMURL());
652 }
653 }
654 }
655 }
656346 }
657347
658348 public void checkRoundabouts() {
1009699 sourceNode,
1010700 destNode,
1011701 roadArcs.get(i).getInitialHeading(), // not used
1012 arcToDest.getFinalHeading(), // not used
1013702 c1.bearingTo(c2),
1014703 partialArcLength, // from stepNode to destNode on road
1015704 pathLength, // from sourceNode to destNode on road
1110799 public int hashCode(){
1111800 return getCoord().getId();
1112801 }
802
803 public List<RouteArc> getDirectArcsBetween(RouteNode otherNode) {
804 List<RouteArc> result = new ArrayList<>();
805 for(RouteArc a : arcs){
806 if(a.isDirect() && a.getDest() == otherNode){
807 result.add(a);
808 }
809 }
810 return result;
811 }
812
813
1113814 }
10691069 */
10701070 private int addAsNumberNode(int pos, Coord toAdd){
10711071 toAdd.setNumberNode(true);
1072 toAdd.setAddedNumberNode(true);
10721073 getRoad().getPoints().add(pos, toAdd);
10731074
10741075 ExtNumbers work = next;
907907 MapRoad uncheckedRoads[] = new MapRoad[houses.length];
908908 for (int i = 0 ; i < houses.length; i++)
909909 uncheckedRoads[i] = houses[i].getRoad();
910
911910 isOK = info.checkRoads();
912 if (!isOK)
913 continue;
914911 // check if houses are assigned to different roads now
915912 houses = info.getHouseNodes();
916913 for (int i = 0 ; i < houses.length; i++){
918915 initialHousesForRoads.removeMapping(uncheckedRoads[i], houses[i]);
919916 if (houses[i].isIgnored() == false)
920917 initialHousesForRoads.add(houses[i].getRoad(), houses[i]);
918 else {
919 if (!isOK)
920 log.info("housenumber is assigned to different road after checking addr:interpolation way which turned out to be invalid",houses[i],info );
921 }
921922 }
922923 }
923924 }
163163 if (timesToAdd == 2){
164164 // add two new points between c1 and c2
165165 points.add(seg + 1, pointToUse);
166 pointToUse.setAddedNumberNode(true);
166167 pointToUse = new Coord (pointToUse);
167168 pointToUse.setNumberNode(true);
168169 points.add(seg + 1, pointToUse);
170 pointToUse.setAddedNumberNode(true);
169171 linkNode = pointToUse;
170172 } else {
171173 // copy it
173175 pointToUse.setNumberNode(true);
174176 // add copy before c2
175177 points.add(seg + 1, pointToUse);
178 pointToUse.setAddedNumberNode(true);
176179 if (pointToUse.highPrecEquals(c1)){
177180 linkNode = c1;
178181 } else {
170170 while (streetName.equals(knownHouses[i].getRoad().getStreet()) == false && knownHouses[i].hasAlternativeRoad()){
171171 HousenumberMatch testx = new HousenumberMatch(knownHouses[i]);
172172 MapRoad r = knownHouses[i].getAlternativeRoads().remove(0);
173 HousenumberGenerator.findClosestRoadSegment(testx, r);
174 if (testx.getDistance() < MAX_INTERPOLATION_DISTANCE_TO_ROAD){
175 copyRoadData(testx, knownHouses[i]);
173 if (streetName.equals(r.getStreet())){
174 HousenumberGenerator.findClosestRoadSegment(testx, r);
175 if (testx.getDistance() < MAX_INTERPOLATION_DISTANCE_TO_ROAD){
176 copyRoadData(testx, knownHouses[i]);
177 }
176178 }
177179 }
178180 }
1818 import java.util.Iterator;
1919 import java.util.Map;
2020
21 import uk.me.parabola.imgfmt.app.Label;
22 import uk.me.parabola.log.Logger;
23
24
2125 /**
2226 * Superclass of the node, segment and way OSM elements.
2327 */
2428 public abstract class Element {
29 private static final Logger log = Logger.getLogger(Element.class);
30
2531 private Tags tags;
2632 private long id;
2733 private long originalId;
3137 }
3238
3339 /**
34 * Add a tag to the way. Some tags are recognised separately and saved in
40 * Add a tag to the element. This method should be called by OSM readers
41 * because it trims obsolete spaces from the value.
42 *
43 * @param key The tag name.
44 * @param val Its value.
45 */
46 public void addTagFromRawOSM(String key, String val) {
47 if (val != null){
48 val = val.trim();
49 if (val.isEmpty() == false){
50 // remove duplicated spaces within value
51 String squashed = Label.squashSpaces(val);
52 if (val.equals(squashed) == false) {
53 if (log.isInfoEnabled())
54 log.info(this.toBrowseURL(),"obsolete blanks removed from tag", key, " '" + val + "' -> '" + squashed + "'");
55 val = squashed;
56 }
57 }
58 }
59 addTag(key, val.intern());
60 }
61
62 /**
63 * Add a tag to the element. Some tags are recognised separately and saved in
3564 * separate fields.
3665 *
3766 * @param key The tag name.
4473 }
4574
4675 /**
47 * Add a tag to the way. Some tags are recognised separately and saved in
76 * Add a tag to the element. Some tags are recognised separately and saved in
4877 * separate fields.
4978 *
5079 * @param tagKey The tag id created by TagDict
6969 Node node = new Node(id, co);
7070 for (int tid = 0; tid < tagCount; tid++) {
7171 String key = getStringById(binNode.getKeys(tid));
72 String val = getStringById(binNode.getVals(tid)).trim();
72 String val = getStringById(binNode.getVals(tid));
7373 key = keepTag(key, val);
7474 if (key != null)
75 node.addTag(key, val.intern());
75 node.addTagFromRawOSM(key, val);
7676 }
7777
7878 saver.addNode(node);
104104 int keyid = nodes.getKeysVals(kvid++);
105105 int valid = nodes.getKeysVals(kvid++);
106106 String key = getStringById(keyid);
107 String val = getStringById(valid).trim();
107 String val = getStringById(valid);
108108 key = keepTag(key, val);
109109 if (key != null) {
110110 if (node == null)
111111 node = new Node(id, co);
112 node.addTag(key, val.intern());
112 node.addTagFromRawOSM(key, val);
113113 ntags++;
114114 }
115115 }
131131 for (int j = 0; j < binWay.getKeysCount(); j++) {
132132
133133 String key = getStringById(binWay.getKeys(j));
134 String val = getStringById(binWay.getVals(j)).trim();
134 String val = getStringById(binWay.getVals(j));
135135 key = keepTag(key, val);
136136 if (key != null)
137 way.addTag(key, val.intern());
137 way.addTagFromRawOSM(key, val);
138138 }
139139
140140 long nid = 0;
156156 boolean tagsIncomplete = false;
157157 for (int j = 0; j < binRel.getKeysCount(); j++) {
158158 String key = getStringById(binRel.getKeys(j));
159 String val = getStringById(binRel.getVals(j)).trim();
159 String val = getStringById(binRel.getVals(j));
160160 // type is required for relations - all other tags are filtered
161161 if ("type".equals(key))
162162 // intern the string
166166 if (key == null)
167167 tagsIncomplete = true;
168168 else
169 rel.addTag(key, val.intern());
169 rel.addTagFromRawOSM(key, val);
170170 }
171171
172172 if (tagsIncomplete) {
313313 while (bytesToRead > 0){
314314 readStringPair();
315315 String key = stringPair[0];
316 String val = stringPair[1].trim();
316 String val = stringPair[1];
317317 // the type tag is required for relations - all other tags are filtered
318318 if (elem instanceof Relation && "type".equals(key))
319319 // intern the string
321321 else
322322 key = keepTag(key, val);
323323 if (key != null)
324 elem.addTag(key, val.intern());
324 elem.addTagFromRawOSM(key, val);
325325 else
326326 tagsIncomplete = true;
327327 }
206206 private void startInNode(String qName, Attributes attributes) {
207207 if (qName.equals("tag")) {
208208 String key = attributes.getValue("k");
209 String val = attributes.getValue("v").trim();
209 String val = attributes.getValue("v");
210210
211211 // We only want to create a full node for nodes that are POI's
212212 // and not just one point of a way. Only create if it has tags that
218218 currentNode = new Node(currentElementId, co);
219219 }
220220
221 currentNode.addTag(key, val.intern());
221 currentNode.addTagFromRawOSM(key, val);
222222 }
223223 }
224224 }
234234 addCoordToWay(currentWay, id);
235235 } else if (qName.equals("tag")) {
236236 String key = attributes.getValue("k");
237 String val = attributes.getValue("v").trim();
237 String val = attributes.getValue("v");
238238 key = keepTag(key, val);
239239 if (key != null)
240 currentWay.addTag(key, val.intern());
240 currentWay.addTagFromRawOSM(key, val);
241241 }
242242 }
243243
275275 currentRelation.addElement(attributes.getValue("role"), el);
276276 } else if (qName.equals("tag")) {
277277 String key = attributes.getValue("k");
278 String val = attributes.getValue("v").trim();
278 String val = attributes.getValue("v");
279279 // the type tag is required for relations - all other tags are filtered
280280 if ("type".equals(key))
281281 // intern the key
285285 if (key == null) {
286286 currentRelation.addTag(TAGS_INCOMPLETE_TAG, "true");
287287 } else {
288 currentRelation.addTag(key, val.intern());
288 currentRelation.addTagFromRawOSM(key, val);
289289 }
290290 }
291291 }
5353 new String[] {"1", "2", "3"},
5454 values.toArray());
5555 }
56
57 @Test
58 public void testaddTagFromRawOSM() {
59 Element el = new Way(1);
60
61 el.addTagFromRawOSM("a", "1");
62 el.addTagFromRawOSM("b", "1 ");
63 el.addTagFromRawOSM("c", " 1");
64 el.addTagFromRawOSM("d", "1 2");
65 el.addTagFromRawOSM("e", "1 2 3");
66 el.addTagFromRawOSM("f", " 1 2 3 4 ");
67 el.addTagFromRawOSM("g", " ");
68 el.addTagFromRawOSM("h", " ");
69
70 assertEquals("1", el.getTag("a"));
71 assertEquals("1", el.getTag("b"));
72 assertEquals("1", el.getTag("c"));
73 assertEquals("1 2", el.getTag("d"));
74 assertEquals("1 2 3", el.getTag("e"));
75 assertEquals("1 2 3 4", el.getTag("f"));
76 assertEquals("", el.getTag("g"));
77 assertEquals("", el.getTag("h"));
78 }
5679 }