Codebase list mkgmap / 58c6665
Imported Upstream version 0.0.0+svn3620 Bas Couwenberg 8 years ago
73 changed file(s) with 8146 addition(s) and 1576 deletion(s). Raw diff Collapse all Expand all
5858 NOTE: All tags can be assigned without any restrictions but mkgmap performs some special processing for the +mkgmap:country+ tag. See chapter +Country names+ below.
5959
6060 A common set of address rules is located in the +inc/address+ file in the default style of mkgmap.
61 The tag mkgmap:execute_finalize_rules=true should be set for all objects that
62 should be considered for address search but may not appear in the map:
63 addr:housenumber=* {set mkgmap:execute_finalize_rules=true}
64
65 You can also use the tag mkgmap:numbers=false to tell mkgmap
66 that a node or way should be excluded when calculating address information.
6167
6268 A common rule set to assign the +mkgmap:city+ tag looks like:
6369 ----
170170 ;--housenumbers
171171 : Enables house number search for OSM input files.
172172
173 : All nodes and polygons having addr:housenumber and addr:street set are matched
173 : All nodes and polygons having addr:housenumber set are matched
174174 to streets. A match between a house number element and a street is created if
175175 the street is located within a radius of 150m and the addr:street tag value of
176 the house number element equals the mgkmap:street tag value of the street.
176 the house number element equals the mgkmap:street tag value of the street
177 or the addr:street tag is missing and the number is plausible for the street.
177178 The mkgmap:street tag must be added to the street in the style file.
178
179 : Example:
179 For optimal results, the tags mkgmap:city and mkgmap:postal_code should be
180 set for the housenumber element. If a street connects two or more cities
181 this allows to find all addresses along the road, even they have the same
182 number.
183 : Example for given street name:
180184 :: Node - addr:street=Main Street addr:housenumber=2
181185 :: Way 1 - name=Main Street
182186 :: Way 2 - name=Main Street, mkgmap:street=Main Street
183 :: Way 3 - mkgmap:street=Mainstreet
187 :: Way 3 - mkgmap:street=Mainstreet
184188 :: Way 4 - name=Main Street [A504]
185 : The node matches to Way 2. It has mkgmap:street set with a value equal to
189 : The node 1 matches to Way 2. It has mkgmap:street set with a value equal to
186190 the addr:street tag value of the house number node.
191 : If the street is not given with addr:housenumber, mkgmap uses heuristics
192 to find the best match.
187193
188194 === Overview map options ===
189195 ;--overview-mapname=name
454460
455461
456462 ;--road-name-pois[=GarminCode]
457 : Generate a POI for each named road. By default, the POIs'
463 : Now ignored, former meaning:
464 Generate a POI for each named road. By default, the POIs'
458465 Garmin type code is 0x640a. If desired, a different type code
459466 can be specified with this option. This s a workaround for not
460467 being able to search for roads.
613620 are ignored for pedestrian-only ways.
614621
615622 ;--process-destination
616 : Splits all motorway_link and trunk_link ways tagged with
617 destination into two or three parts where the second part
618 is additionally tagged with mkgmap:dest_hint=true. This
619 allows to use any routable Garmin type (except 0x08 and 0x09)
623 : Splits all motorway_link, trunk_link, and primary_link
624 ways tagged with destination into two or three parts where
625 the second part is additionally tagged with mkgmap:dest_hint=true.
626 This allows using any routable Garmin type (except 0x08 and 0x09)
620627 for that part so that the Garmin device tells the name of
621628 this part as hint which destination to follow.
629 See also --process-exits.
622630
623631 ;--process-exits
624632 : Usual Garmin devices do not tell the name of the exit on motorways
625 while routing with mkgmap created maps. This option splits the each
626 motorway_link and trunk_link into three parts. All parts are tagged
627 with the original tags of the link. Additionally the middle part is
628 tagged with the following tags:
633 while routing with mkgmap created maps. This option splits each
634 motorway_link, trunk_link and primary_link way into three parts.
635 All parts are tagged with the original tags of the link.
636 Additionally the middle part is tagged with the following tags:
629637 mkgmap:exit_hint=true
630638 mkgmap:exit_hint_ref=<ref tag value of the exit>
631639 mkgmap:exit_hint_name=<name tag value of the exit>
634642 : Adding a rule checking the mkgmap:exit_hint=true makes it possible
635643 to use any routable Garmin type (except 0x08 and 0x09) for the middle
636644 part so that the Garmin device tells the name of this middle part as
637 hint where to leave the motorway/trunk.
645 hint where to leave the motorway/trunk. The first part must have type
646 0x08 or 0x09 so that Garmin uses the hint.
638647
639648 ;--delete-tags-file=FILENAME
640649 : Names a file that should contain one or more lines of the form
129129 |=========================================================
130130 | Tag | Description
131131 | +mkgmap:skipSizeFilter+ | If set to +true+ the line or polygon will pass the size filter, no matter what size it has
132 | +mkgmap:highest-resolution-only+ | If set to +true+ the object will only be added for the highest resolution configured in the element type definition.
132 | +mkgmap:highest-resolution-only+ | If set to +true+ the object will only be added for the highest resolution configured in the element type definition.
133 | +mkgmap:execute_finalize_rules+ | If set to +true+ mkgmap will execute the finalize rules even if no object is created fot the element.
134 | +mkgmap:numbers+ | If set to +false+ for a node or way mkgmap will ignore the object in the calculations for the --housenumber option
133135 |=========================================================
134136
135137
176176
177177 --housenumbers
178178 Enables house number search for OSM input files.
179 All nodes and polygons having addr:housenumber and addr:street set are matched
179 All nodes and polygons having addr:housenumber set are matched
180180 to streets. A match between a house number element and a street is created if
181181 the street is located within a radius of 150m and the addr:street tag value of
182182 the house number element equals the mgkmap:street tag value of the street.
183183 The mkgmap:street tag must be added to the street in the style file.
184 Example:
184 For optimal results, the tags mkgmap:city and mkgmap:postal_code should be
185 set for the housenumber element. If a street connects two or more cities
186 this allows to find all addresses along the road, even they have the same
187 number.
188 Example for given street name:
185189 Node - addr:street=Main Street addr:housenumber=2
186190 Way 1 - name=Main Street
187191 Way 2 - name=Main Street, mkgmap:street=Main Street
189193 Way 4 - name=Main Street [A504]
190194 The node matches to Way 2. It has mkgmap:street set with a value equal to
191195 the addr:street tag value of the house number node.
196 If the street is not given with addr:housenumber, mkgmap uses heuristics
197 to find the best match.
192198
193199 Overview map options:
194200 --overview-mapname=name
455461 that go nowhere.
456462
457463 --road-name-pois[=GarminCode]
464 Now ignored, former usage:
458465 Generate a POI for each named road. By default, the POIs'
459466 Garmin type code is 0x640a. If desired, a different type code
460467 can be specified with this option. This is a workaround for not
610617 are ignored for pedestrian-only ways.
611618
612619 --process-destination
613 Splits all motorway_link and trunk_link ways tagged with
614 destination into two or three parts where the second part
615 is additionally tagged with mkgmap:dest_hint=true. This
616 allows to use any routable Garmin type (except 0x08 and 0x09)
620 Splits all motorway_link, trunk_link, and primary_link
621 ways tagged with destination into two or three parts where
622 the second part is additionally tagged with mkgmap:dest_hint=true.
623 This allows to use any routable Garmin type (except 0x08 and 0x09)
617624 for that part so that the Garmin device tells the name of
618625 this part as hint which destination to follow.
626 See also --process-exits.
619627
620628 --process-exits
621629 Usual Garmin devices do not tell the name of the exit on motorways
622 while routing with mkgmap created maps. This option splits the each
623 motorway_link and trunk_link into three parts. All parts are tagged
624 with the original tags of the link. Additionally the middle part is
625 tagged with the following tags:
630 while routing with mkgmap created maps. This option splits each
631 motorway_link, trunk_link and primary_link way into three parts.
632 All parts are tagged with the original tags of the link.
633 Additionally the middle part is tagged with the following tags:
626634 mkgmap:exit_hint=true
627635 mkgmap:exit_hint_ref=<ref tag value of the exit>
628636 mkgmap:exit_hint_name=<name tag value of the exit>
630638 Adding a rule checking the mkgmap:exit_hint=true makes it possible
631639 to use any routable Garmin type (except 0x08 and 0x09) for the middle
632640 part so that the Garmin device tells the name of this middle part as
633 hint where to leave the motorway/trunk.
641 hint where to leave the motorway/trunk.
642 The first part must have type 0x08 or 0x09 so that Garmin uses the hint.
634643
635644 --delete-tags-file=FILENAME
636645 Names a file that should contain one or more lines of the form
0 svn.version: 3598
1 build.timestamp: 2015-05-18T07:21:27+0100
0 svn.version: 3620
1 build.timestamp: 2015-06-10T11:09:22+0100
3535 mkgmap:country=POL & mkgmap:region!=* & mkgmap:admin_level4=* { set mkgmap:region='${mkgmap:admin_level4|subst:województwo =>}' }
3636
3737 # other european countries
38 mkgmap:country=BEL & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
38 mkgmap:country=BEL & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9}' }
3939 mkgmap:country=CZE & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
4040 mkgmap:country=CZE & mkgmap:city!=* & mkgmap:admin_level7=* { set mkgmap:city='${mkgmap:admin_level7}' }
4141 mkgmap:country=DNK & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
4242 mkgmap:country=DNK & mkgmap:city!=* & mkgmap:admin_level7=* { set mkgmap:city='${mkgmap:admin_level7}' }
4343 mkgmap:country=FIN & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9}' }
4444 mkgmap:country=FIN & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
45 mkgmap:country=FRA & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9}' }
46 mkgmap:country=FRA & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
4745 mkgmap:country=ISL & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
4846 mkgmap:country=ITA & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
4947 mkgmap:country=LUX & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
00 natural=coastline [0x15 resolution 12]
11
2 route=ferry {set mkgmap:numbers = false }
23 route=ferry & (motorcar=no | motor_vehicle=no) {add mkgmap:ferry=1} [0x1b road_class=0 road_speed=0 resolution 23]
34 route=ferry {add mkgmap:ferry=1} [0x1b road_class=3 road_speed=0 resolution 19]
45
0 landuse=basin|landuse=reservoir [0x3f resolution 20]
0 landuse=basin [0x3f resolution 20]
1 (landuse=reservoir | (natural=water & water=reservoir)) [0x3f resolution 20]
12
23 natural=bay [0x3d resolution 18]
34 natural=glacier [0x4d resolution 18]
77 # and http://wiki.openstreetmap.org/wiki/Mkgmap/help/Custom_styles
88 # for more information.
99
10 addr:housenumber=* {set mkgmap:execute_finalize_rules=true}
1011 aeroway=runway [0x27 resolution 20]
1112 aeroway=taxiway [0x27 resolution 24]
1213
2627 {add mkgmap:dead-end-check=false}
2728 # Validation-like checks (uncomment to enable)
2829 #highway=motorway_link & oneway!=yes & oneway!=no { echo "motorway_link lacks oneway" }
29 highway=motorway|highway=motorway_link { add oneway=yes }
30 highway=motorway|highway=motorway_link { add oneway=yes; add mkgmap:numbers=false }
3031
3132 # Set highway names to include the reference if there is one
3233 highway=motorway { name '${ref|highway-symbol:hbox} ${name}' | '${ref|highway-symbol:hbox}' | '${name}' }
3334
34 (highway=motorway_link | highway=trunk_link) & mkgmap:exit_hint=true & mkgmap:dest_hint=true
35 (highway=motorway_link | highway=trunk_link | highway=primary_link) & mkgmap:exit_hint=true & mkgmap:dest_hint=true
3536 { name '${destination:ref|subst: =>} ${destination|subst:;=> |subst:/=> }' |
3637 '${ref|subst: =>} ${destination|subst:;=> |subst:/=> }' |
3738 '${destination|subst:;=> |subst:/=> }' |
4243 'Exit ${mkgmap:exit_hint_ref}'
4344 }
4445
45 (highway=motorway_link | highway=trunk_link) & mkgmap:exit_hint!=* & mkgmap:dest_hint=true
46 (highway=motorway_link | highway=trunk_link | highway=primary_link) & mkgmap:exit_hint!=* & mkgmap:dest_hint=true
4647 { name '${destination:ref|subst: =>} ${destination|subst:;=> |subst:/=> }' |
4748 '${ref|subst: =>} ${destination|subst:;=> |subst:/=> }' |
4849 '${destination|subst:;=> |subst:/=> }'
4950 }
5051
51 (highway=motorway_link | highway=trunk_link) & mkgmap:exit_hint=true & mkgmap:dest_hint!=*
52 (highway=motorway_link | highway=trunk_link | highway=primary_link) & mkgmap:exit_hint=true & mkgmap:dest_hint!=*
5253 { name 'Exit ${mkgmap:exit_hint_ref} ${mkgmap:exit_hint_name}' |
5354 'Exit ${mkgmap:exit_hint_ref} ${mkgmap:exit_hint_exit_to}' |
5455 'Exit ${mkgmap:exit_hint_exit_to}' |
124125 highway=* & highway!=proposed & motorroad=yes [0x02 road_class=4 road_speed=4 resolution 18]
125126 highway=primary & ( network=e-road | int_ref=* ) [0x03 resolution 17-18 continue]
126127 highway=primary [0x03 road_class=3 road_speed=4 resolution 19]
128 highway=primary_link & (mkgmap:exit_hint=true | mkgmap:dest_hint=true)[0x06 road_class=3 road_speed=1 resolution 21]
127129 highway=primary_link [0x08 road_class=3 road_speed=1 resolution 21]
128130 highway=secondary & ( network=e-road | int_ref=* ) [0x04 resolution 18-19 continue]
129131 highway=secondary [0x04 road_class=2 road_speed=3 resolution 20]
196198 #limit artificial cycleways to to resolution 24
197199 mkgmap:synthesised=yes & mkgmap:bicycle=yes { set mkgmap:highest-resolution-only = true }
198200
201 # don't add house numbers to unnamed or artifical bicycle ways
202 mkgmap:bicycle=yes & (mkgmap:foot=no & mkgmap:car=no & mkgmap:street!=* | mkgmap:synthesised=yes) {set mkgmap:numbers=false}
203
199204 name=* { name '${name}' }
200205
201206 highway=* & ref=* { addlabel '${ref}' }
77 # and http://wiki.openstreetmap.org/wiki/Mkgmap/help/Custom_styles
88 # for more information.
99
10 addr:housenumber=* {set mkgmap:execute_finalize_rules=true}
1011 barrier=* & bicycle=* { set mkgmap:bicycle='${bicycle|subst:private=>no}' }
1112 barrier=* & foot=* { set mkgmap:foot='${foot|subst:private=>no}' }
1213 barrier=* & hgv=* { set mkgmap:truck='${hgv|subst:private=>no}' }
271272 tourism=attraction [0x2c04 resolution 24]
272273 tourism=artwork [0x2c04 resolution 24]
273274 tourism=aquarium [0x2c07 resolution 24]
274 tourism=camp_site [0x2b03 resolution 24]
275 tourism=caravan_site [0x2b03 resolution 24]
275 tourism=camp_site [0x2b05 resolution 24]
276 tourism=caravan_site [0x2b05 resolution 24]
276277 tourism=chalet [0x2b02 resolution 24]
277278 tourism=guest_house [0x2b02 resolution 24]
278279 tourism=hostel [0x2b02 resolution 24]
66 # See http://wiki.openstreetmap.org/wiki/Mkgmap/help/style_rules
77 # and http://wiki.openstreetmap.org/wiki/Mkgmap/help/Custom_styles
88 # for more information.
9
10 addr:housenumber=* {set mkgmap:execute_finalize_rules=true}
911
1012 leisure=* & sport=* & name=* { set name='${name} (${sport})' }
1113 leisure=* & sport=* & name!=* { add name='${sport}' }
7981 <finalize>
8082 # The finalizer section is executed for each element when a rule with an element type matches
8183
84 # we need addrees info from buildings for the address search
85 include 'inc/address';
86
8287 name=* { name '${name}' }
4747 private final static short PART_OF_BAD_ANGLE = 0x0080; // bit in flags is true if point should be treated as a node
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
50 private final static short HOUSENUMBER_NODE = 0x0400; // start/end of house number interval
5051
5152 public final static int HIGH_PREC_BITS = 30;
5253 public final static int DELTA_SHIFT = 6;
176177 }
177178
178179 public boolean preserved() {
179 return (flags & PRESERVED_MASK) != 0;
180 return (flags & PRESERVED_MASK) != 0 || (flags & HOUSENUMBER_NODE) != 0;
180181 }
181182
182183 public void preserved(boolean preserved) {
330331 this.flags &= ~END_OF_WAY;
331332 }
332333
334 /**
335 * @return if this is the beginning/end of a house number interval
336 */
337 public boolean isNumberNode(){
338 return (flags & HOUSENUMBER_NODE) != 0;
339 }
340
341 /**
342 * Set or unset flag for {@link WrongAngleFixer}
343 * @param b true or false
344 */
345 public void setNumberNode(boolean b) {
346 if (b)
347 this.flags |= HOUSENUMBER_NODE;
348 else
349 this.flags &= ~HOUSENUMBER_NODE;
350 }
351
333352 public int hashCode() {
334353 // Use a factor for latitude to span over the whole integer range:
335354 // max lat: 4194304
338357 return 503 * latitude + longitude;
339358 }
340359
360 /**
361 * Compares the coordinates that are displayed in the map
362 */
341363 public boolean equals(Object obj) {
342364 if (obj == null || !(obj instanceof Coord))
343365 return false;
345367 return latitude == other.latitude && longitude == other.longitude;
346368 }
347369
370 /**
371 * Compares the coordinates using the delta values.
372 * XXX: Note that
373 * p1.highPrecEquals(p2) is not always equal to p1.equals(p2)
374 * @param other
375 * @return
376 */
348377 public boolean highPrecEquals(Coord other) {
349378 if (other == null)
350379 return false;
546575 }
547576
548577 public String toDegreeString() {
549 return String.format(Locale.ENGLISH, "%.6f/%.6f",
578 return String.format(Locale.ENGLISH, "%.6f,%.6f",
550579 getLatDegrees(),
551580 getLonDegrees());
552581 }
3636 super(latitude, longitude);
3737 this.id = id;
3838 setOnBoundary(boundary);
39 setNumberNode(true);
3940 preserved(true);
4041 }
4142
4344 super(other);
4445 this.id = id;
4546 setOnBoundary(boundary);
47 setNumberNode(true);
4648 preserved(true);
4749
4850 }
127127 SortKey<LabeledRoadDef> nameKey = sort.createSortKey(lrd, label, 0, cache);
128128
129129 // If there is a city add it to the sort.
130 City city = rd.getCity();
130 City city = (rd.getCities().isEmpty() ? null : rd.getCities().get(0)); // what if we more than one?
131131 SortKey<LabeledRoadDef> cityKey;
132132 if (city != null) {
133133 int region = city.getRegionNumber();
159159
160160 Label name = lrd.label;
161161 RoadDef road = lrd.roadDef;
162 City city = road.getCity();
162 City city = (road.getCities().isEmpty() ? null : road.getCities().get(0)); // what if we more than one?
163163
164164 if (road.hasHouseNumbers() || !name.equals(lastName) || city != lastCity) {
165165
1111 */
1212 package uk.me.parabola.imgfmt.app.net;
1313
14 import it.unimi.dsi.fastutil.ints.IntArrayList;
15
1416 import java.util.ArrayList;
1517 import java.util.Collections;
1618 import java.util.HashMap;
110112 int zipFlag = (flags2 >> 10) & 0x3;
111113 int cityFlag = (flags2 >> 12) & 0x3;
112114 int numberFlag = (flags2 >> 14) & 0x3;
113
114 road.setZip(fetchZipCity(reader, zipFlag, zips, zipSize));
115 road.setCity(fetchZipCity(reader, cityFlag, cities, citySize));
115 IntArrayList indexes = new IntArrayList();
116 fetchZipCityIndexes(reader, zipFlag, zipSize, indexes);
117 for (int index : indexes){
118 road.addZipIfNotPresent(zips.get(index));
119 }
120 fetchZipCityIndexes(reader, cityFlag, citySize, indexes);
121 for (int index : indexes){
122 road.addCityIfNotPresent(cities.get(index));
123 }
116124
117125 fetchNumber(reader, numberFlag);
118126 }
131139 }
132140
133141 /**
134 * Fetch a zip or a city.
135 * @param <T> Can be city or zip.
136 * @return The found City or Zip.
137 */
138 private <T> T fetchZipCity(ImgFileReader reader, int flag, List<T> list, int size) {
139 T item = null;
142 * Parse a list of zip/city indexes.
143 * @param reader
144 * @param flag
145 * @param size
146 * @param indexes
147 */
148 private void fetchZipCityIndexes(ImgFileReader reader, int flag, int size, IntArrayList indexes) {
149 indexes.clear();
140150 if (flag == 2) {
141151 // fetch city/zip index
142152 int ind = (size == 2)? reader.getChar(): (reader.get() & 0xff);
143153 if (ind != 0)
144 item = list.get(ind-1);
154 indexes.add(ind-1);
145155 } else if (flag == 3) {
146156 // there is no item
147157 } else if (flag == 0) {
148 // Skip over these
149 int n = reader.get();
150 reader.get(n);
158 int n = reader.get() & 0xff;
159 parseList(reader, n, size, indexes);
151160 } else if (flag == 1) {
152 // Skip over these
153161 int n = reader.getChar();
154 reader.get(n);
162 parseList(reader, n, size, indexes);
155163 } else {
156164 assert false : "flag is " + flag;
157165 }
158 return item;
166 }
167
168 private void parseList(ImgFileReader reader, int n, int size,
169 IntArrayList indexes) {
170 long endPos = reader.position() + n;
171 int node = 0; // not yet used
172 while (reader.position() < endPos) {
173 int initFlag = reader.get() & 0xff;
174 int skip = (initFlag & 0x1f);
175 initFlag >>= 5;
176 if (initFlag == 7) {
177 // Need to read another byte
178 initFlag = reader.get() & 0xff;
179 skip |= ((initFlag & 0x1f) << 5);
180 initFlag >>= 5;
181 }
182 node += skip + 1;
183 int right = 0, left = 0;
184 if (initFlag == 0) {
185 right = left = getCityOrZip(reader, size, endPos);
186 } else if ((initFlag & 0x4) != 0) {
187 if ((initFlag & 1) == 0)
188 right = 0;
189 if ((initFlag & 2) == 0)
190 left = 0;
191 } else {
192 if ((initFlag & 1) != 0)
193 left = getCityOrZip(reader, size, endPos);
194 if ((initFlag & 2) != 0)
195 right = getCityOrZip(reader, size, endPos);
196 }
197 if (left > 0)
198 indexes.add(left - 1);
199 if (right > 0 && left != right)
200 indexes.add(right - 1);
201 }
202 }
203
204 private int getCityOrZip(ImgFileReader reader, int size, long endPos) {
205 if (reader.position() > endPos - size) {
206 assert false : "ERRROR overflow";
207 return 0;
208 }
209 int cnum;
210 if (size == 1)
211 cnum = reader.get() & 0xff;
212 else if (size == 2)
213 cnum = reader.getChar();
214 else {
215 assert false : "unexpected size value" + size;
216 return 0;
217 }
218 return cnum;
159219 }
160220
161221 /**
1111 */
1212 package uk.me.parabola.imgfmt.app.net;
1313
14 import java.io.ByteArrayOutputStream;
15 import java.util.Arrays;
1416 import java.util.Iterator;
1517 import java.util.List;
1618
1719 import uk.me.parabola.imgfmt.app.BitWriter;
20 import uk.me.parabola.imgfmt.app.lbl.City;
21 import uk.me.parabola.imgfmt.app.lbl.Zip;
22 import uk.me.parabola.log.Logger;
23 import uk.me.parabola.mkgmap.general.CityInfo;
24 import uk.me.parabola.mkgmap.general.ZipCodeInfo;
1825
1926 import static uk.me.parabola.imgfmt.app.net.NumberStyle.*;
2027
2835 * @author Steve Ratcliffe
2936 */
3037 public class NumberPreparer {
31
38 private static final Logger log = Logger.getLogger(NumberPreparer.class);
3239 private final List<Numbers> numbers;
3340 private boolean valid;
3441
3845
3946 private BitWriter bw;
4047 private boolean swappedDefaultStyle;
48 CityZipWriter zipWriter;
49 CityZipWriter cityWriter;
4150
4251 public NumberPreparer(List<Numbers> numbers) {
4352 this.numbers = numbers;
44 }
45
53 this.zipWriter = new CityZipWriter("zip", 0, 0);
54 this.cityWriter = new CityZipWriter("city", 0, 0);
55 }
56
57
58 public NumberPreparer(List<Numbers> numbers, Zip zip, City city, int numCities, int numZips) {
59 this.numbers = numbers;
60
61 zipWriter = new CityZipWriter("zip",(zip == null) ? 0: zip.getIndex(), numZips);
62 cityWriter = new CityZipWriter("city",(city == null) ? 0: city.getIndex(), numCities);
63 }
64
65 public boolean prepare(){
66 fetchBitStream();
67 if (!valid)
68 return false;
69 zipWriter.compile(numbers);
70 cityWriter.compile(numbers);
71 return true;
72 }
4673 /**
4774 * Make the bit stream and return it. This is only done once, if you call this several times
4875 * the same bit writer is returned every time.
5178 public BitWriter fetchBitStream() {
5279 if (bw != null)
5380 return bw;
54
5581 int initialValue = setup();
5682
5783 // Write the bitstream
74100 if (bw.getLength() > 1)
75101 valid = true;
76102 } catch (Abandon e) {
77 System.out.println(e.getMessage());
103 log.error(e.getMessage());
78104 valid = false;
79105 }
80106
81107 return bw;
82108 }
83
109
84110 /**
85111 * Do some initial calculation and sanity checking of the numbers that we are to
86112 * write.
135161
136162 int lastNode = -1;
137163 for (Numbers n : numbers) {
138 if (!n.hasRnodNumber())
164 if (!n.hasIndex())
139165 throw new Abandon("no r node set");
140
141166 // See if we need to skip some nodes
142 if (n.getRnodNumber() != lastNode + 1)
143 state.writeSkip(bw, n.getRnodNumber() - lastNode - 2);
167 if (n.getIndex() != lastNode + 1)
168 state.writeSkip(bw, n.getIndex() - lastNode - 2);
144169
145170 // Normal case write out the next node.
146171 state.setTarget(n);
151176 state.writeNumbers(bw);
152177 state.restoreWriters();
153178
154 lastNode = n.getRnodNumber();
179 lastNode = n.getIndex();
155180 }
156181 }
157182
206231 * @return True if the preparer believes that the output is valid.
207232 */
208233 public boolean isValid() {
234 try {
235 fetchBitStream();
236 } catch (Exception e) {
237 }
209238 return valid;
210239 }
211240
750779 }
751780 }
752781 }
782
753783 }
784
754785
755786 /**
756787 * A bit writer that can be configured with different bit width and sign properties.
848879 super("HOUSE NUMBER RANGE: " + message);
849880 }
850881 }
882
883 class CityZipWriter {
884 private ByteArrayOutputStream buf;
885 private final String type;
886 private final int numItems;
887 private final int defaultIndex;
888
889
890 public CityZipWriter(String type, int defIndex, int numItems) {
891 this.type = type;
892 this.defaultIndex = defIndex;
893 this.numItems = numItems;
894 buf = new ByteArrayOutputStream();
895 }
896
897 public ByteArrayOutputStream getBuffer(){
898 return buf;
899 }
900 public boolean compile(List<Numbers> numbers){
901 try {
902 int lastNodeIndex = -1;
903 // left and right entry in zip or city table
904 int []prevIndexes = new int[2];
905 prevIndexes[0] = prevIndexes[1] = -1;
906 int []indexes = new int[2];
907 for (Numbers num : numbers){
908 for (int i = 0; i < 2; i++){
909 indexes[i] = -1;
910 boolean left = (i == 0);
911 switch (type) {
912 case "zip":
913 ZipCodeInfo zipInfo = num.getZipCodeInfo(left);
914 if (zipInfo != null){
915 if (zipInfo.getImgZip() != null)
916 indexes[i] = zipInfo.getImgZip().getIndex();
917 else
918 indexes[i] = 0; // or default?
919 }
920 break;
921 case "city":
922 CityInfo cityInfo = num.getCityInfo(left);
923 if (cityInfo != null){
924 if (cityInfo.getImgCity() != null)
925 indexes[i] = cityInfo.getImgCity().getIndex();
926 else
927 indexes[i] = 0; // or default?
928 }
929 break;
930 default:
931 break;
932 }
933 }
934 if (indexes[0] < 0 && indexes[1] < 0)
935 continue;
936 if (lastNodeIndex < 0){
937 if (num.getIndex() > 0 ){
938 int [] defindexes = {defaultIndex,defaultIndex};
939 write(0, defindexes, prevIndexes);
940 }
941 }
942 int skip = num.getIndex() - lastNodeIndex - 1;
943 assert defaultIndex > 0 : "bad default index";
944 lastNodeIndex = num.getIndex();
945 if (indexes[0] < 0)
946 indexes[0] = defaultIndex;
947 if (indexes[1] < 0)
948 indexes[1] = defaultIndex;
949 write(skip, indexes, prevIndexes);
950 }
951 } catch (Abandon e) {
952 return false;
953 }
954 return true;
955 }
956
957 private void write(int skip, int[] indexes, int[] prevIndexes) {
958 if (Arrays.equals(indexes, prevIndexes))
959 return;
960 // we can signal new values for left and / or right side
961 int sidesFlag = 0;
962 if (indexes[0] <= 0 && indexes[1] <= 0){
963 sidesFlag |= 4; // signal end of a zip code/city interval
964 if (indexes[0] == 0)
965 sidesFlag |= 1;
966 if (indexes[1] == 0)
967 sidesFlag |= 2;
968 } else {
969 if (indexes[1] != indexes[0]){
970 if (indexes[0] > 0 && indexes[0] != prevIndexes[0])
971 sidesFlag |= 1;
972 if (indexes[1] > 0 && indexes[1] != prevIndexes[1])
973 sidesFlag |= 2;
974 }
975 }
976
977 int initFlag = Math.max(skip-1,0);
978 if (initFlag > 31){
979 // we have to write two bytes
980 buf.write((byte) (initFlag & 0x1f | 0x7<<5));
981 initFlag >>= 5;
982 }
983 initFlag |= sidesFlag << 5;
984 if ((initFlag & 0xff) == 4){
985 long dd = 4;
986 }
987 buf.write((byte) (initFlag & 0xff));
988 if ((sidesFlag & 4) == 0) {
989 if (indexes[0] > 0 && (sidesFlag == 0 || (sidesFlag & 1) == 1))
990 writeIndex(indexes[0]);
991 if (indexes[1] > 0 && (sidesFlag & 2) != 0)
992 writeIndex(indexes[1]);
993 } else {
994 long dd = 4;
995 }
996 System.arraycopy(indexes, 0, prevIndexes, 0, indexes.length);
997 }
998
999 void writeIndex(int val){
1000 if (val <= 0)
1001 return;
1002 if (numItems > 255){
1003 buf.write((byte) val & 0xff);
1004 buf.write((byte) (val >> 8));
1005 }
1006 else
1007 buf.write((byte) val);
1008 }
1009
1010 }
1212 package uk.me.parabola.imgfmt.app.net;
1313
1414 import uk.me.parabola.log.Logger;
15 import uk.me.parabola.mkgmap.general.CityInfo;
16 import uk.me.parabola.mkgmap.general.ZipCodeInfo;
1517
1618 /**
1719 * Describes the house numbering from a node in the road.
1921 */
2022 public class Numbers {
2123 private static final Logger log = Logger.getLogger(Numbers.class);
24 public static final boolean LEFT = true;
25 public static final boolean RIGHT = false;
26
27 private static final int MAX_DELTA = 131071; // see NumberPreparer
2228
2329 // The node in the road where these numbers apply. In the polish notation it is the
24 // node in the road, whereas in the NET file it is the number of the routing node.
30 // node in the road, whereas in the NET file it is the index of the number node.
2531 private int nodeNumber; // node in road index
26 private Integer rnodNumber; // routing node index
27
28 // On the left hand side of the road.
29 private NumberStyle leftNumberStyle;
30 private int leftStart;
31 private int leftEnd;
32
33 // On the right hand side of the road.
34 private NumberStyle rightNumberStyle;
35 private int rightStart;
36 private int rightEnd;
32 private Integer indexNumber; // the position in the list of Numbers (starting with 0)
33 // the data on side of the road
34 private RoadSide leftSide,rightSide;
35
36 private class RoadSide {
37 NumDesc numbers;
38 // to be added
39 CityInfo cityInfo;
40 ZipCodeInfo zipCode;
41 boolean isEmpty(){
42 return cityInfo == null && zipCode == null && numbers == null;
43 }
44 }
45
46 private class NumDesc{
47 NumberStyle numberStyle;
48 int start,end;
49
50 public NumDesc(NumberStyle numberStyle, int start, int end) {
51 this.numberStyle = numberStyle;
52 this.start = start;
53 this.end = end;
54 }
55 public boolean contained(int hn){
56 boolean isEven = (hn % 2 == 0);
57 if (numberStyle == NumberStyle.BOTH
58 || numberStyle == NumberStyle.EVEN && isEven
59 || numberStyle == NumberStyle.ODD && !isEven){
60 if (start <= end) {
61 if (start <= hn && hn <= end)
62 return true;
63 }
64 else {
65 if (end <= hn && hn <= start)
66 return true;
67 }
68 }
69 return false;
70 }
71
72 @Override
73 public String toString() {
74 return String.format("%s,%d,%d", numberStyle, start,end);
75 }
76
77 }
3778
3879 public Numbers() {
3980 }
4081
4182 /**
4283 * This constructor takes a comma separated list as in the polish format. Also used in testing as
43 * it is an easy way to set all the parameters at once.
84 * it is an easy way to set all common parameters at once.
4485 *
4586 * @param spec Node number, followed by left and then right parameters as in the polish format.
4687 */
4788 public Numbers(String spec) {
4889 String[] strings = spec.split(",");
4990 nodeNumber = Integer.valueOf(strings[0]);
50 leftNumberStyle = NumberStyle.fromChar(strings[1]);
51 leftStart = Integer.valueOf(strings[2]);
52 leftEnd = Integer.valueOf(strings[3]);
53 rightNumberStyle = NumberStyle.fromChar(strings[4]);
54 rightStart = Integer.valueOf(strings[5]);
55 rightEnd = Integer.valueOf(strings[6]);
91 NumberStyle numberStyle = NumberStyle.fromChar(strings[1]);
92 int start = Integer.valueOf(strings[2]);
93 int end = Integer.valueOf(strings[3]);
94 setNumbers(LEFT, numberStyle, start, end);
95 numberStyle = NumberStyle.fromChar(strings[4]);
96 start = Integer.valueOf(strings[5]);
97 end = Integer.valueOf(strings[6]);
98 setNumbers(RIGHT, numberStyle, start, end);
99
100 if (strings.length > 8){
101 // zip codes
102 String zip = strings[7];
103 if ("-1".equals(zip) == false)
104 setZipCode(LEFT, new ZipCodeInfo(zip));
105 zip = strings[8];
106 if ("-1".equals(zip) == false)
107 setZipCode(RIGHT, new ZipCodeInfo(zip));
108 }
109 if (strings.length > 9){
110 String city,region,country;
111 int nextPos = 9;
112 city = strings[nextPos];
113 if ("-1".equals(city) == false){
114 region = strings[nextPos + 1];
115 country = strings[nextPos + 2];
116 setCityInfo(LEFT, new CityInfo(city, region, country));
117 nextPos = 12;
118 } else
119 nextPos = 10;
120 city = strings[nextPos];
121 if ("-1".equals(city) == false){
122 region = strings[nextPos + 1];
123 country = strings[nextPos + 2];
124 setCityInfo(RIGHT, new CityInfo(city, region, country));
125 }
126 }
127 }
128
129 public void setNumbers(boolean left, NumberStyle numberStyle, int start, int end){
130 if (numberStyle != NumberStyle.NONE || start != -1 || end != -1){
131 RoadSide rs = assureSideIsAllocated(left);
132 rs.numbers = new NumDesc(numberStyle, start, end);
133 } else {
134 RoadSide rs = (left) ? leftSide : rightSide;
135 if (rs != null)
136 rs.numbers = null;
137 removeIfEmpty(left);
138 }
139 }
140
141 public void setCityInfo(boolean left, CityInfo ci){
142 if (ci != null){
143 RoadSide rs = assureSideIsAllocated(left);
144 rs.cityInfo = ci;
145 } else {
146 RoadSide rs = (left) ? leftSide : rightSide;
147 if (rs != null)
148 rs.cityInfo = null;
149 removeIfEmpty(left);
150 }
151 }
152
153 public CityInfo getCityInfo(boolean left){
154 RoadSide rs = (left) ? leftSide : rightSide;
155 return (rs != null) ? rs.cityInfo : null;
156 }
157
158 public void setZipCode(boolean left, ZipCodeInfo zipCode){
159 if (zipCode != null){
160 RoadSide rs = assureSideIsAllocated(left);
161 rs.zipCode = zipCode;
162 } else {
163 RoadSide rs = (left) ? leftSide : rightSide;
164 if (rs != null)
165 rs.zipCode= null;
166 removeIfEmpty(left);
167 }
168 }
169
170
171 public ZipCodeInfo getZipCodeInfo (boolean left){
172 RoadSide rs = (left) ? leftSide : rightSide;
173 return (rs != null) ? rs.zipCode: null;
174 }
175
176 private void removeIfEmpty(boolean left){
177 if (left && leftSide != null && leftSide.isEmpty())
178 leftSide = null;
179 if (!left && rightSide != null && rightSide.isEmpty())
180 rightSide = null;
181 }
182
183 // allocate or return allocated RoadSide instance for the given road side
184 private RoadSide assureSideIsAllocated(boolean left){
185 if (left && leftSide == null)
186 leftSide = new RoadSide();
187 if (!left && rightSide == null)
188 rightSide = new RoadSide();
189 return (left) ? leftSide : rightSide;
56190 }
57191
58192 public int getNodeNumber() {
63197 this.nodeNumber = nodeNumber;
64198 }
65199
66 public int getRnodNumber() {
67 if (rnodNumber == null) {
68 log.error("WARNING: rnod not set!!");
200 public int getIndex() {
201 if (indexNumber == null) {
202 log.error("WARNING: index not set!!");
69203 return nodeNumber;
70204 }
71 return rnodNumber;
72 }
73
74 public boolean hasRnodNumber() {
75 return rnodNumber != null;
76 }
77
78 public void setRnodNumber(int rnodNumber) {
79 this.rnodNumber = rnodNumber;
80 }
81
82 public NumberStyle getLeftNumberStyle() {
83 return leftNumberStyle;
84 }
85
86 public void setLeftNumberStyle(NumberStyle leftNumberStyle) {
87 this.leftNumberStyle = leftNumberStyle;
88 }
89
90 public int getLeftStart() {
91 return leftStart;
92 }
93
94 public void setLeftStart(int leftStart) {
95 this.leftStart = leftStart;
96 }
97
98 public int getLeftEnd() {
99 return leftEnd;
100 }
101
102 public void setLeftEnd(int leftEnd) {
103 this.leftEnd = leftEnd;
104 }
105
106 public NumberStyle getRightNumberStyle() {
107 return rightNumberStyle;
108 }
109
110 public void setRightNumberStyle(NumberStyle rightNumberStyle) {
111 this.rightNumberStyle = rightNumberStyle;
112 }
113
114 public int getRightStart() {
115 return rightStart;
116 }
117
118 public void setRightStart(int rightStart) {
119 this.rightStart = rightStart;
120 }
121
122 public int getRightEnd() {
123 return rightEnd;
124 }
125
126 public void setRightEnd(int rightEnd) {
127 this.rightEnd = rightEnd;
205 return indexNumber;
206 }
207
208 public boolean hasIndex() {
209 return indexNumber != null;
210 }
211
212 /**
213 * @param index the nth number node
214 */
215 public void setIndex(int index) {
216 this.indexNumber = index;
217 }
218
219 private NumDesc getNumbers(boolean left) {
220 RoadSide rs = (left) ? leftSide : rightSide;
221 return (rs != null) ? rs.numbers : null;
222 }
223
224 public NumberStyle getNumberStyle(boolean left) {
225 NumDesc n = getNumbers(left);
226 return (n == null) ? NumberStyle.NONE : n.numberStyle;
227 }
228
229 public int getStart(boolean left) {
230 NumDesc n = getNumbers(left);
231 return (n == null) ? -1 : n.start; // -1 is the default in the polish format
232 }
233
234 public int getEnd(boolean left) {
235 NumDesc n = getNumbers(left);
236 return (n == null) ? -1 : n.end; // -1 is the default in the polish format
128237 }
129238
130239 public String toString() {
131240 String nodeStr = "0";
132241 if (nodeNumber > 0)
133242 nodeStr = String.valueOf(nodeNumber);
134 else if (getRnodNumber() > 0)
135 nodeStr = String.format("(n%d)", getRnodNumber());
136
137 return String.format("%s,%s,%d,%d,%s,%d,%d",
243 else if (getIndex() > 0)
244 nodeStr = String.format("(n%d)", getIndex());
245
246 nodeStr = String.format("%s,%s,%d,%d,%s,%d,%d",
138247 nodeStr,
139 leftNumberStyle,
140 leftStart,
141 leftEnd,
142 rightNumberStyle,
143 rightStart,
144 rightEnd);
145 }
248 getNumberStyle(LEFT),
249 getStart(LEFT),
250 getEnd(LEFT),
251 getNumberStyle(RIGHT),
252 getStart(RIGHT),
253 getEnd(RIGHT));
254
255 if (getCityInfo(LEFT) != null || getCityInfo(RIGHT) != null
256 || getZipCodeInfo(LEFT) != null || getZipCodeInfo(RIGHT) != null) {
257 nodeStr = String.format("%s,%s,%s", nodeStr,
258 getPolishZipCode(LEFT), getPolishZipCode(RIGHT));
259 if (getCityInfo(LEFT) != null || getCityInfo(RIGHT) != null) {
260 nodeStr = String.format("%s,%s,%s",nodeStr,
261 getPolishCityInfo(LEFT),getPolishCityInfo(RIGHT));
262 }
263 }
264 return nodeStr;
265 }
266
267 public NumberStyle getLeftNumberStyle() {
268 return getNumberStyle(LEFT);
269 }
270 public NumberStyle getRightNumberStyle() {
271 return getNumberStyle(RIGHT);
272 }
273 public int getLeftStart(){
274 return getStart(LEFT);
275 }
276 public int getRightStart(){
277 return getStart(RIGHT);
278 }
279 public int getLeftEnd(){
280 return getEnd(LEFT);
281 }
282 public int getRightEnd(){
283 return getEnd(RIGHT);
284 }
146285
147286 public boolean equals(Object obj) {
148287 if (!(obj instanceof Numbers))
154293
155294 public int hashCode() {
156295 return toString().hashCode();
157 }
296 }
297
298 public boolean isPlausible(){
299 if (!isPlausible(getLeftNumberStyle(), getLeftStart(), getLeftEnd()))
300 return false;
301 if (!isPlausible(getRightNumberStyle(), getRightStart(), getRightEnd()))
302 return false;
303 if (getLeftNumberStyle() == NumberStyle.NONE
304 || getRightNumberStyle() == NumberStyle.NONE)
305 return true;
306 if (getCityInfo(LEFT) != null){
307 if (getCityInfo(LEFT).equals(getCityInfo(RIGHT)) == false)
308 return true;
309 } else if (getCityInfo(RIGHT) != null)
310 return true;
311 if (getZipCodeInfo(LEFT) != null){
312 if (getZipCodeInfo(LEFT).equals(getZipCodeInfo(RIGHT)) == false)
313 return true;
314 } else if (getCityInfo(RIGHT) != null)
315 return true;
316 if (getLeftNumberStyle() == getRightNumberStyle() || getLeftNumberStyle() == NumberStyle.BOTH || getRightNumberStyle()==NumberStyle.BOTH){
317 // check if intervals are overlapping
318 int start1, start2,end1,end2;
319 if (getLeftStart() < getLeftEnd()){
320 start1 = getLeftStart();
321 end1 = getLeftEnd();
322 } else {
323 start1 = getLeftEnd();
324 end1 = getLeftStart();
325 }
326 if (getRightStart() < getRightEnd()){
327 start2 = getRightStart();
328 end2 = getRightEnd();
329 } else {
330 start2 = getRightEnd();
331 end2 = getRightStart();
332 }
333 if (start2 > end1 || end2 < start1)
334 return true;
335 if (getLeftStart() == getLeftEnd() && getRightStart() == getRightEnd() && getLeftStart() == getRightStart())
336 return true; // single number on both sides of the road
337
338 return false;
339 }
340
341 return true;
342 }
343
344 private static boolean isPlausible(NumberStyle style, int start, int end){
345 if (Math.abs(start - end) > MAX_DELTA)
346 return false;
347 if (style == NumberStyle.EVEN)
348 return start % 2 == 0 && end % 2 == 0;
349 if (style == NumberStyle.ODD)
350 return start % 2 != 0 && end % 2 != 0;
351 return true;
352 }
353
354 public boolean isContained(int hn, boolean left){
355 RoadSide rs = left ? leftSide : rightSide;
356 if (rs == null || rs.numbers == null)
357 return false;
358 return rs.numbers.contained(hn);
359 }
360
361 /**
362 * @param hn a house number
363 * @param left left or right side
364 * @return 0 if the number is not within the intervals, 1 if it is on one side, 2 if it on both sides
365 */
366 public int countMatches(int hn) {
367 int matches = 0;
368 if (isContained(hn, LEFT))
369 matches++;
370 if (isContained(hn, RIGHT))
371 matches++;
372 if (matches > 1){
373 if (getLeftStart() == getLeftEnd() && getRightStart() == getRightEnd())
374 matches = 1; // single number on both sides of the road
375 }
376 return matches;
377 }
378
379 /**
380 * Compare all fields that describe the interval, but not the position
381 * @param other
382 * @return true if these fields are equal
383 */
384 public boolean isSimilar(Numbers other){
385 if (other == null)
386 return false;
387 if (getLeftNumberStyle() != other.getLeftNumberStyle()
388 || getLeftStart() != other.getLeftStart() || getLeftEnd() != other.getLeftEnd()
389 || getRightNumberStyle() != other.getRightNumberStyle()
390 || getRightStart() != other.getRightStart() || getRightEnd() != other.getRightEnd())
391 return false;
392 return true;
393
394 }
395
396 public boolean isEmpty(){
397 return getLeftNumberStyle() == NumberStyle.NONE && getRightNumberStyle() == NumberStyle.NONE;
398 }
399
400
401 private String getPolishCityInfo (boolean left){
402 CityInfo ci = getCityInfo(left);
403 if (ci == null)
404 return "-1";
405 StringBuilder sb = new StringBuilder();
406 if (ci.getCity() != null)
407 sb.append(ci.getCity());
408 sb.append(",");
409 if (ci.getRegion() != null)
410 sb.append(ci.getRegion());
411 sb.append(",");
412 if (ci.getCountry() != null)
413 sb.append(ci.getCountry());
414 return sb.toString();
415 }
416
417 private String getPolishZipCode (boolean left){
418 ZipCodeInfo zip = getZipCodeInfo(left);
419 return (zip != null && zip.getZipCode() != null ) ? zip.getZipCode() : "-1";
420 }
421
422
158423 }
1515 */
1616 package uk.me.parabola.imgfmt.app.net;
1717
18 import java.io.ByteArrayOutputStream;
1819 import java.util.ArrayList;
20 import java.util.BitSet;
21 import java.util.Collections;
1922 import java.util.HashSet;
2023 import java.util.List;
2124 import java.util.Set;
3033 import uk.me.parabola.imgfmt.app.lbl.Zip;
3134 import uk.me.parabola.imgfmt.app.trergn.Polyline;
3235 import uk.me.parabola.log.Logger;
36 import uk.me.parabola.mkgmap.general.CityInfo;
37 import uk.me.parabola.mkgmap.general.ZipCodeInfo;
3338
3439 /**
3540 * A road definition. This ties together all segments of a single road
8287 private static final int TABAACCESS_FLAG_NO_BIKE = 0x0020;
8388 private static final int TABAACCESS_FLAG_NO_TRUCK = 0x0040;
8489
90 // true if road should not be added to NOD
91 private boolean skipAddToNOD;
92
8593 // the offset in Nod2 of our Nod2 record
8694 private int offsetNod2;
8795
108116
109117 private final SortedMap<Integer,List<RoadIndex>> roadIndexes = new TreeMap<>();
110118
111 private City city;
112 private Zip zip;
113119 private boolean paved = true;
114120 private boolean ferry;
115121 private boolean roundabout;
119125 private Set<String> messageIssued;
120126
121127 private final List<Offset> rgnOffsets = new ArrayList<>();
128 // for the NOD2 bit stream
129 private BitSet nod2BitSet;
122130
123131 /*
124132 * Everything that's relevant for writing out Nod 2.
144152 private final long id;
145153 private final String name;
146154 private List<Numbers> numbersList;
155 private List<City> cityList;
156 private List<Zip> zipList;
147157 private int nodeCount;
148158
149159 public RoadDef(long id, String name) {
195205 if (numlabels == 0)
196206 return;
197207 assert numlabels > 0;
198
208 Zip zip = getZips().isEmpty() ? null : getZips().get(0);
209 City city = getCities().isEmpty() ? null: getCities().get(0);
199210 offsetNet1 = writer.position();
200
201211 NumberPreparer numbers = null;
202212 if (numbersList != null) {
203 numbers = new NumberPreparer(numbersList);
204 numbers.fetchBitStream();
205 if (!numbers.isValid()){
213 numbers = new NumberPreparer(numbersList, zip, city, numCities, numZips);
214 if (!numbers.prepare()){
206215 numbers = null;
207216 log.warn("Invalid housenumbers in",this.toString());
208217 }
221230
222231 if((netFlags & NET_FLAG_ADDRINFO) != 0) {
223232 nodeCount--;
233 if (nodeCount + 2 != nnodes){
234 log.error("internal error? The nodeCount doesn't match value calculated by RoadNetWork:",this);
235 }
224236 writer.put((byte) (nodeCount & 0xff)); // lo bits of node count
225237
226 int code = 0xe8; // zip and city present
227 code |= ((nodeCount >> 8) & 0x3); // top bits of node count
228 if(city == null)
229 code |= 0x10; // no city
230 if(zip == null)
231 code |= 0x04; // no zip
232 if (numbers != null) {
233 code &= ~0xc0;
234 if (numbers.fetchBitStream().getLength() > 255)
235 code |= 0x40;
236 }
238 int code = ((nodeCount >> 8) & 0x3); // top bits of node count
239 int len, flag;
240
241 ByteArrayOutputStream zipBuf = null, cityBuf = null;
242 len = (numbers == null) ? 0: numbers.zipWriter.getBuffer().size();
243 if (len > 0){
244 zipBuf = numbers.zipWriter.getBuffer();
245 flag = (len > 255) ? 1 : 0;
246 } else
247 flag = (zip == null) ? 3 : 2;
248 code |= flag << 2;
249
250 len = (numbers == null) ? 0: numbers.cityWriter.getBuffer().size();
251 if (len > 0){
252 cityBuf = numbers.cityWriter.getBuffer();
253 flag = (len > 255) ? 1 : 0;
254 } else
255 flag = (city == null) ? 3 : 2;
256 code |= flag << 4;
257
258 len = (numbers == null) ? 0 : numbers.fetchBitStream().getLength();
259 if (len > 0){
260 flag = (len > 255) ? 1 : 0;
261 } else
262 flag = 3;
263 code |= flag << 6;
264
237265 writer.put((byte)code);
238 if(zip != null) {
239 char zipIndex = (char)zip.getIndex();
240 if(numZips > 255)
241 writer.putChar(zipIndex);
266 // System.out.printf("%d %d %d\n", (code >> 2 & 0x3), (code >> 4 & 0x3), (code >> 6 & 0x3));
267
268 if (zipBuf != null){
269 len = zipBuf.size();
270 if (len > 255)
271 writer.putChar((char) len);
242272 else
243 writer.put((byte)zipIndex);
244 }
245 if(city != null) {
246 char cityIndex = (char)city.getIndex();
247 if(numCities > 255)
248 writer.putChar(cityIndex);
273 writer.put((byte) len);
274 writer.put(zipBuf.toByteArray());
275 } else {
276 if(zip != null) {
277 char zipIndex = (char)zip.getIndex();
278 if(numZips > 255)
279 writer.putChar(zipIndex);
280 else
281 writer.put((byte)zipIndex);
282 }
283 }
284 if (cityBuf != null){
285 len = cityBuf.size();
286 if (len > 255)
287 writer.putChar((char) len);
249288 else
250 writer.put((byte)cityIndex);
289 writer.put((byte) len);
290 writer.put(cityBuf.toByteArray());
291 } else {
292 if(city != null) {
293 char cityIndex = (char)city.getIndex();
294 if(numCities > 255)
295 writer.putChar(cityIndex);
296 else
297 writer.put((byte)cityIndex);
298 }
251299 }
252300 if (numbers != null) {
253301 BitWriter bw = numbers.fetchBitStream();
355403 l.add(new RoadIndex(pl));
356404
357405 if (level == 0) {
358 nodeCount += pl.getNodeCount();
406 nodeCount += pl.getNodeCount(hasHouseNumbers());
359407 }
360408 }
361409
448496 }
449497 }
450498
451 private boolean internalNodes = true;
499 private boolean internalNodes;
452500
453501 /**
454502 * Does the road have any nodes besides start and end?
455 *
503 * These can be number nodes or routing nodes.
456504 * This affects whether we need to write extra bits in
457505 * the bitstream in RGN.
458506 */
471519 * which will be pointed at from NET 1.
472520 */
473521 public void setNode(RouteNode node) {
522 if (skipAddToNOD)
523 return;
474524 netFlags |= NET_FLAG_NODINFO;
475525 this.node = node;
476526 }
497547 netFlags |= NET_FLAG_ADDRINFO;
498548 }
499549 }
550
551 public List<Numbers> getNumbersList() {
552 return numbersList;
553 }
554
500555
501556 /**
502557 * Write this road's NOD2 entry.
509564 public void writeNod2(ImgFileWriter writer) {
510565 if (!hasNodInfo())
511566 return;
567 if (skipAddToNOD){
568 // should not happen
569 log.error("internal error: writeNod2 called for roaddef with skipAddToNOD=true");
570 return;
571 }
512572
513573 log.debug("writing nod2");
514574
520580 // this is related to the number of nodes, but there
521581 // is more to it...
522582 // For now, shift by one if the first node is not a
523 // routing node. Supposedly, other holes are also
524 // possible.
525 // This might be unnecessary if we just make sure
526 // that every road starts with a node.
583 // routing node.
584 // If the road has house numbers, we count also
585 // the number nodes, and these get a 0 in the bit stream.
527586 int nbits = nnodes;
528587 if (!startsWithNode)
529588 nbits++;
530589 writer.putChar((char) nbits);
531590 boolean[] bits = new boolean[nbits];
532 for (int i = 0; i < bits.length; i++)
533 bits[i] = true;
534 if (!startsWithNode)
535 bits[0] = false;
591
592 if (hasHouseNumbers()){
593 int off = startsWithNode ? 0 :1;
594 for (int i = 0; i < bits.length; i++){
595 if (nod2BitSet.get(i))
596 bits[i+off] = true;
597 }
598 } else {
599 for (int i = 0; i < bits.length; i++)
600 bits[i] = true;
601 if (!startsWithNode)
602 bits[0] = false;
603 }
536604 for (int i = 0; i < bits.length; i += 8) {
537605 int b = 0;
538606 for (int j = 0; j < 8 && j < bits.length - i; j++)
635703
636704 private int roadClass = -1;
637705
706
638707 // road class that goes in various places (really?)
639708 public void setRoadClass(int roadClass) {
640709 assert roadClass < 0x08;
678747 return (netFlags & NET_FLAG_ONEWAY) != 0;
679748 }
680749
681 public void setCity(City city) {
682 this.city = city;
750 public void addCityIfNotPresent(City city) {
751 if (city == null){
752 log.error("trying to add null value to city list in road",this);
753 return;
754 }
683755 netFlags |= NET_FLAG_ADDRINFO;
684 }
685
686 public void setZip(Zip zip) {
687 this.zip = zip;
756 if (cityList == null){
757 cityList = new ArrayList<>(2);
758 }
759 if (cityList.contains(city) == false)
760 cityList.add(city);
761 }
762
763 public void addZipIfNotPresent(Zip zip) {
764 if (zip == null){
765 log.error("trying to add null value to zip list in road",this);
766 return;
767 }
688768 netFlags |= NET_FLAG_ADDRINFO;
689 }
690
691 public City getCity() {
692 return city;
693 }
694
769 if (zipList == null){
770 zipList = new ArrayList<>(2);
771 }
772 if (zipList.contains(zip) == false)
773 zipList.add(zip);
774 }
775
776
777 public List<City> getCities(){
778 if (cityList == null)
779 return Collections.emptyList();
780 return cityList;
781 }
782
783 public List<Zip> getZips(){
784 if (zipList == null)
785 return Collections.emptyList();
786 return zipList;
787 }
788
695789 public boolean paved() {
696790 return paved;
697791 }
748842 return previouslyIssued;
749843 }
750844
845 public void setNod2BitSet(BitSet bs) {
846 if (skipAddToNOD)
847 return;
848 nod2BitSet = bs;
849 }
850
851 public boolean skipAddToNOD() {
852 return skipAddToNOD;
853 }
854
855 public void skipAddToNOD(boolean skip) {
856 this.skipAddToNOD = skip;
857 }
858
859 public void resetImgData() {
860 zipList = null;
861 cityList = null;
862 if (numbersList != null){
863 for (Numbers num : numbersList){
864 for (int side = 0; side < 2; side++){
865 boolean left = side == 0;
866 CityInfo ci = num.getCityInfo(left);
867 if (ci != null)
868 ci.setImgCity(null);
869 ZipCodeInfo z = num.getZipCodeInfo(left);
870 if (z != null)
871 z.setImgZip(null);
872 }
873 }
874 }
875 }
876
751877 }
1717
1818 import java.util.ArrayList;
1919 import java.util.Arrays;
20 import java.util.BitSet;
2021 import java.util.Iterator;
2122 import java.util.LinkedHashMap;
2223 import java.util.List;
7879 int pointsHash = 0;
7980
8081 int npoints = coordList.size();
82 int numCoordNodes = 0;
83 boolean hasInternalNodes = false;
84 int numNumberNodes = 0;
85 BitSet nodeFlags = new BitSet();
8186 for (int index = 0; index < npoints; index++) {
8287 Coord co = coordList.get(index);
83
84 if (index > 0) {
88 int id = co.getId();
89
90 if (id != 0){
91 nodeFlags.set(numNumberNodes);
92 ++numCoordNodes;
93 if(index > 0 && index < npoints - 1)
94 hasInternalNodes = true;
95 }
96 if (co.isNumberNode())
97 ++numNumberNodes;
98 if (index == 0){
99 if (id == 0)
100 roadDef.setStartsWithNode(false);
101
102 } else {
85103 double d = co.distance(coordList.get(index-1));
86104 arcLength += d;
87105 roadLength += d;
88106 }
89
90 int id = co.getId();
91
107 if (roadDef.skipAddToNOD())
108 continue;
109
92110 pointsHash += co.hashCode();
93111
94112 if (id == 0)
193211 arcLength = 0;
194212 pointsHash = co.hashCode();
195213 }
214 if (roadDef.hasHouseNumbers()){
215 // we ignore number nodes when we have no house numbers
216 if (numCoordNodes < numNumberNodes)
217 hasInternalNodes = true;
218 roadDef.setNumNodes(numNumberNodes);
219 roadDef.setNod2BitSet(nodeFlags);
220 } else {
221 roadDef.setNumNodes(numCoordNodes);
222 }
223 if (hasInternalNodes)
224 roadDef.setInternalNodes(true);
196225 roadDef.setLength(roadLength);
197226 }
198227
264293 long t1 = System.currentTimeMillis();
265294
266295 for (RoadDef rd: roadDefs){
296 if (rd.skipAddToNOD())
297 continue;
267298 if (rd.getRoadClass() >= 1)
268299 rd.getNode().addArcsToMajorRoads(rd);
269300 }
4646 private int[] deltas;
4747 private boolean[] nodes;
4848
49 private boolean ignoreNumberOnlyNodes;
50
4951 LinePreparer(Polyline line) {
5052 if (line.isRoad() &&
5153 line.getSubdiv().getZoom().getLevel() == 0 &&
5456 // but who knows
5557 extraBit = true;
5658 }
59 if (!line.hasHouseNumbers())
60 ignoreNumberOnlyNodes = true;
5761
5862 extTypeLine = line.hasExtendedType();
5963
236240 assert (dy == 0 && lat != lastLat) == false: ("delta lat too large: " + (lat - lastLat));
237241 lastLong = lon;
238242 lastLat = lat;
239
240 if (dx != 0 || dy != 0 || (extraBit && co.getId() != 0))
243 boolean isSpecialNode = false;
244 if (co.getId() > 0 || (co.isNumberNode() && ignoreNumberOnlyNodes == false))
245 isSpecialNode = true;
246 if (dx != 0 || dy != 0 || extraBit && isSpecialNode)
241247 firstsame = i;
242248
243249 /*
244250 * Current thought is that the node indicator is set when
245 * the point is a node. There's a separate first extra bit
251 * the point is a routing node or a house number node.
252 * There's a separate first extra bit
246253 * that always appears to be false. The last points' extra bit
247254 * is set if the point is a node and this is not the last
248255 * polyline making up the road.
249 * Todo: special case the last bit
250256 */
251257 if (extraBit) {
252258 boolean extra = false;
253 if (co.getId() != 0) {
259 if (isSpecialNode) {
254260 if (i < nodes.length - 1)
255261 // inner node of polyline
256262 extra = true;
277277 return getSubdiv().getLongitude() + (getDeltaLong() << getSubdiv().getShift());
278278 }
279279
280 public int getNodeCount() {
280 /**
281 *
282 * @param countAllNodes : false: count only coord nodes, true: count number nodes
283 * @return
284 */
285 public int getNodeCount(boolean countAllNodes ) {
281286 int idx = 0;
282287 int count = 0;
288
283289 for (Coord co : points) {
284 if (idx++ > 0 && co.getId() > 0)
290 if (idx++ > 0 && (co.getId() > 0 || countAllNodes && co.isNumberNode()))
285291 count++;
286292 }
287293 return count;
288294 }
295
296 public boolean hasHouseNumbers() {
297 if (!isRoad())
298 return false;
299 return roaddef.hasHouseNumbers();
300 }
289301 }
183183
184184 switch (option) {
185185 case "input-file":
186 log.debug("adding filename", value);
187 add(new Filename(value));
186 if (value != null){
187 log.debug("adding filename", value);
188 add(new Filename(value));
189 }
188190 break;
189191 case "read-config":
190192 readConfigFile(value);
1919
2020 import uk.me.parabola.log.Logger;
2121 import uk.me.parabola.mkgmap.general.MapPoint;
22 import uk.me.parabola.mkgmap.general.MapPointKdTree;
2322 import uk.me.parabola.mkgmap.reader.osm.Tags;
2423 import uk.me.parabola.util.EnhancedProperties;
24 import uk.me.parabola.util.KdTree;
2525 import uk.me.parabola.util.MultiHashMap;
2626
2727 public class Locator {
3030 /** hash map to collect equally named MapPoints*/
3131 private final MultiHashMap<String, MapPoint> cityMap = new MultiHashMap<String, MapPoint>();
3232
33 private final MapPointKdTree cityFinder = new MapPointKdTree();
33 private final KdTree<MapPoint> cityFinder = new KdTree<>();
3434 private final List<MapPoint> placesMap = new ArrayList<MapPoint>();
3535
3636 /** Contains the tags defined by the option name-tag-list */
6868 autofillOptions.add("nearest");
6969 autofillOptions.remove("3");
7070 }
71 final List<String> knownOptions = Arrays.asList("bounds","is_in","nearest");
72 for (String s : autofillOptions){
73 if (knownOptions.contains(s) == false){
74 throw new IllegalArgumentException(s + " is not a known sub option for option location-autofill: " + optionStr);
75 }
76 }
7177 return autofillOptions;
7278 }
7379 }
3939 import uk.me.parabola.imgfmt.app.map.Map;
4040 import uk.me.parabola.imgfmt.app.net.NETFile;
4141 import uk.me.parabola.imgfmt.app.net.NODFile;
42 import uk.me.parabola.imgfmt.app.net.Numbers;
4243 import uk.me.parabola.imgfmt.app.net.RoadDef;
44 import uk.me.parabola.imgfmt.app.net.RoadNetwork;
4345 import uk.me.parabola.imgfmt.app.net.RouteCenter;
4446 import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes;
4547 import uk.me.parabola.imgfmt.app.trergn.Overview;
7274 import uk.me.parabola.mkgmap.filters.RoundCoordsFilter;
7375 import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
7476 import uk.me.parabola.mkgmap.filters.SizeFilter;
77 import uk.me.parabola.mkgmap.general.CityInfo;
7578 import uk.me.parabola.mkgmap.general.LevelInfo;
7679 import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
7780 import uk.me.parabola.mkgmap.general.MapDataSource;
8184 import uk.me.parabola.mkgmap.general.MapPoint;
8285 import uk.me.parabola.mkgmap.general.MapRoad;
8386 import uk.me.parabola.mkgmap.general.MapShape;
84 import uk.me.parabola.imgfmt.app.net.RoadNetwork;
87 import uk.me.parabola.mkgmap.general.ZipCodeInfo;
8588 import uk.me.parabola.mkgmap.reader.MapperBasedMapDataSource;
8689 import uk.me.parabola.mkgmap.reader.overview.OverviewMapDataSource;
8790 import uk.me.parabola.util.Configurable;
400403 }
401404 }
402405
403 if (cityName == null && (cityCountryName != null || cityRegionName != null)) {
404 // if city name is unknown and region and/or country is known
405 // use empty name for the city
406 cityName = UNKNOWN_CITY_NAME;
407 }
408
409 if(cityName != null) {
410
411 Country cc = (cityCountryName == null)? getDefaultCountry() : lbl.createCountry(cityCountryName, locator.getCountryISOCode(cityCountryName));
412
413 Region cr = (cityRegionName == null)? getDefaultRegion(cc) : lbl.createRegion(cc, cityRegionName, null);
414
415 if(cr != null) {
416 ((MapRoad)line).setRoadCity(lbl.createCity(cr, cityName, false));
406 MapRoad road = (MapRoad) line;
407 road.resetImgData();
408
409 City roadCity = calcCity(lbl, cityName, cityRegionName, cityCountryName);
410 if (roadCity != null)
411 road.addRoadCity(roadCity);
412
413 if(zipStr != null) {
414 road.addRoadZip(lbl.createZip(zipStr));
415 }
416
417 List<Numbers> numbers = road.getRoadDef().getNumbersList();
418 if (numbers != null){
419 for (Numbers num : numbers){
420 for (int i = 0; i < 2; i++){
421 boolean left = (i == 0);
422 ZipCodeInfo zipInfo = num.getZipCodeInfo(left);
423 if (zipInfo != null && zipInfo.getZipCode() != null){
424 Zip zip = zipInfo.getImgZip();
425 if (zipInfo.getImgZip() == null){
426 zip = lbl.createZip(zipInfo.getZipCode());
427 zipInfo.setImgZip(zip);
428 }
429 if (zip != null)
430 road.addRoadZip(zip);
431 }
432 CityInfo cityInfo = num.getCityInfo(left);
433 if (cityInfo != null){
434 City city = cityInfo.getImgCity();
435 if (city == null ){
436 city = calcCity(lbl, cityInfo.getCity(), cityInfo.getRegion(), cityInfo.getCountry());
437 cityInfo.setImgCity(city);
438 }
439 if (city != null)
440 road.addRoadCity(city);
441 }
442 }
417443 }
418 else {
419 ((MapRoad)line).setRoadCity(lbl.createCity(cc, cityName, false));
420 }
421 }
422
423 if(zipStr != null) {
424 ((MapRoad)line).setRoadZip(lbl.createZip(zipStr));
425 }
426
444 }
427445 }
428446 }
429447 }
430448
449 private City calcCity(LBLFile lbl, String city, String region, String country){
450 if (city == null && region == null && country == null)
451 return null;
452 Country cc = (country == null)? getDefaultCountry() : lbl.createCountry(country, locator.getCountryISOCode(country));
453 Region cr = (region == null)? getDefaultRegion(cc) : lbl.createRegion(cc, region, null);
454 if (city == null && (country != null || region != null)) {
455 // if city name is unknown and region and/or country is known
456 // use empty name for the city
457 city = UNKNOWN_CITY_NAME;
458 }
459 if (city == null)
460 return null;
461 if(cr != null) {
462 return lbl.createCity(cr, city, false);
463 }
464 else {
465 return lbl.createCity(cc, city, false);
466 }
467 }
468
469
431470 private void processPOIs(Map map, MapDataSource src) {
432471
433472 LBLFile lbl = map.getLblFile();
442481 // * cities (already processed)
443482 // * extended types (address information not shown in MapSource and on GPS)
444483 // * all POIs except roads in case the no-poi-address option is set
445 else if (!p.isCity() && !p.hasExtendedType() && (p.isRoadNamePOI() || poiAddresses))
484 else if (!p.isCity() && !p.hasExtendedType() && poiAddresses)
446485 {
447486
448487 String countryStr = p.getCountry();
487526 }
488527
489528
490 if(p.isRoadNamePOI() && cityStr != null)
491 {
492 // If it is road POI add city name and street name into address info
493 p.setStreet(p.getName());
494 p.setName(p.getName() + "/" + cityStr);
495 }
496
497529 POIRecord r = lbl.createPOI(p.getName());
498
499 if (cityStr == null && (countryStr != null || regionStr != null)) {
500 // if city name is unknown and region and/or country is known
501 // use empty name for the city
502 cityStr = UNKNOWN_CITY_NAME;
503 }
504530
505 if(cityStr != null)
506 {
507 Country thisCountry;
508
509 if(countryStr != null)
510 thisCountry = lbl.createCountry(countryStr, locator.getCountryISOCode(countryStr));
511 else
512 thisCountry = getDefaultCountry();
513
514 Region thisRegion;
515 if(regionStr != null)
516 thisRegion = lbl.createRegion(thisCountry,regionStr, null);
517 else
518 thisRegion = getDefaultRegion(thisCountry);
519
520 City city;
521 if(thisRegion != null)
522 city = lbl.createCity(thisRegion, cityStr, false);
523 else
524 city = lbl.createCity(thisCountry, cityStr, false);
525
526 r.setCity(city);
527
531 if(cityStr != null || regionStr != null || countryStr != null){
532 r.setCity(calcCity(lbl, cityStr, regionStr, countryStr));
528533 }
529534
530535 if (zipStr != null)
309309 String name = road.getName();
310310 if (name == null || name.isEmpty())
311311 continue;
312
313312 Mdr5Record mdrCity = null;
314 if (road.getCity() != null) {
315 mdrCity = cityList.get(road.getCity().getIndex() - 1);
316 if (mdrCity.getMapIndex() == 0)
317 mdrCity = null;
318 }
319
320 mdrFile.addStreet(road, mdrCity);
313 List<City> cities = road.getCities();
314 if (cities.isEmpty())
315 mdrFile.addStreet(road, mdrCity);
316 else {
317 for (City city : cities){
318 mdrCity = cityList.get(city.getIndex() - 1);
319 if (mdrCity.getMapIndex() == 0)
320 mdrCity = null;
321
322 mdrFile.addStreet(road, mdrCity);
323 }
324 }
321325 }
322326 }
323327
8585 lastLong = lon;
8686 lastLat = lat;
8787 if (dx == 0 && dy == 0){
88 if(!line.isRoad() || co.getId() == 0)
88 if(!line.isRoad() || (co.getId() == 0 && co.isNumberNode() == false))
8989 continue;
9090 }
9191 ++numPointsEncoded;
3535 public class LineSizeSplitterFilter implements MapFilter {
3636 private static final Logger log = Logger.getLogger(LineSizeSplitterFilter.class);
3737
38 private static final int MAX_SIZE = 0x7fff;
38 public static final int MAX_SIZE = 0x7fff;
3939
4040 private int maxSize;
4141
0 /*
1 * Copyright (C) 2015 Gerd Petermann
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.mkgmap.general;
13
14 import uk.me.parabola.imgfmt.app.lbl.City;
15
16 public class CityInfo implements Comparable<CityInfo> {
17 private static final String UNKNOWN = "?";
18 private final String city,region,country;
19 private City imgCity;
20
21 public CityInfo (String city, String region, String country){
22 this.city = (city != null) ? city: UNKNOWN;
23 this.region = (region != null) ? region : UNKNOWN;
24 this.country = (country != null) ? country : UNKNOWN;
25 }
26
27
28 public String getCity() {
29 if (city == UNKNOWN)
30 return null;
31 return city;
32 }
33
34 public String getRegion() {
35 if (region == UNKNOWN)
36 return null;
37 return region;
38 }
39
40 public String getCountry() {
41 if (country == UNKNOWN)
42 return null;
43 return country;
44 }
45
46
47 public City getImgCity() {
48 return imgCity;
49 }
50
51
52 public void setImgCity(City imgCity) {
53 this.imgCity = imgCity;
54 }
55
56 public boolean isEmpty(){
57 return city == UNKNOWN && region == UNKNOWN && country == UNKNOWN;
58 }
59
60 @Override
61 public int hashCode() {
62 final int prime = 31;
63 int result = 1;
64 result = prime * result + ((city == null) ? 0 : city.hashCode());
65 result = prime * result + ((country == null) ? 0 : country.hashCode());
66 result = prime * result + ((region == null) ? 0 : region.hashCode());
67 return result;
68 }
69
70 @Override
71 public boolean equals(Object obj) {
72 if (this == obj)
73 return true;
74 if (obj == null)
75 return false;
76 if (!(obj instanceof CityInfo))
77 return false;
78 CityInfo other = (CityInfo) obj;
79 if (city == null) {
80 if (other.city != null)
81 return false;
82 } else if (!city.equals(other.city))
83 return false;
84 if (country == null) {
85 if (other.country != null)
86 return false;
87 } else if (!country.equals(other.country))
88 return false;
89 if (region == null) {
90 if (other.region != null)
91 return false;
92 } else if (!region.equals(other.region))
93 return false;
94 return true;
95 }
96
97 @Override
98 public int compareTo(CityInfo o) {
99 if (this == o)
100 return 0;
101 int d = city.compareTo(o.city);
102 if (d != 0)
103 return d;
104 d = region.compareTo(o.region);
105 if (d != 0)
106 return d;
107 return country.compareTo(o.country);
108 }
109
110 @Override
111 public String toString() {
112 return city + "/" + region + " in " + country;
113 }
114 }
4646 private final List<MapShape> shapes = new ArrayList<MapShape>();
4747 private final List<MapPoint> points = new ArrayList<MapPoint>();
4848
49 private int minLat = Utils.toMapUnit(180.0);
50 private int minLon = Utils.toMapUnit(180.0);
51 private int maxLat = Utils.toMapUnit(-180.0);
52 private int maxLon = Utils.toMapUnit(-180.0);
49 private int minLat30 = Utils.toMapUnit(180.0) << Coord.DELTA_SHIFT ;
50 private int minLon30 = Utils.toMapUnit(180.0) << Coord.DELTA_SHIFT;
51 private int maxLat30 = Utils.toMapUnit(-180.0) << Coord.DELTA_SHIFT;
52 private int maxLon30 = Utils.toMapUnit(-180.0) << Coord.DELTA_SHIFT;
5353
5454 // Keep lists of all items that were used.
5555 private final Map<Integer, Integer> pointOverviews = new HashMap<Integer, Integer>();
140140 * @param p The coordinates of the point to add.
141141 */
142142 public void addToBounds(Coord p) {
143 int lat = p.getLatitude();
144 int lon = p.getLongitude();
145 if (lat < minLat)
146 minLat = lat;
147 if (lat > maxLat)
148 maxLat = lat;
149 if (lon < minLon)
150 minLon = lon;
151 if (lon > maxLon)
152 maxLon = lon;
143 int lat30 = p.getHighPrecLat();
144 int lon30 = p.getHighPrecLon();
145
146 if (lat30 < minLat30)
147 minLat30 = lat30;
148 if (lat30 > maxLat30)
149 maxLat30 = lat30;
150 if (lon30 < minLon30)
151 minLon30 = lon30;
152 if (lon30 > maxLon30)
153 maxLon30 = lon30;
153154 }
154155
155156 /**
158159 * @return An area covering all the points in the map.
159160 */
160161 public Area getBounds() {
162 int minLat = minLat30 >> Coord.DELTA_SHIFT;
163 int maxLat = maxLat30 >> Coord.DELTA_SHIFT;
164 int minLon = minLon30 >> Coord.DELTA_SHIFT;
165 int maxLon = maxLon30 >> Coord.DELTA_SHIFT;
166 if ((maxLat << Coord.DELTA_SHIFT) < maxLat30)
167 maxLat++;
168 if ((maxLon << Coord.DELTA_SHIFT) < maxLon30)
169 maxLon++;
161170 return new Area(minLat, minLon, maxLat, maxLon);
162171 }
163172
2727 * @author Steve Ratcliffe.
2828 */
2929 public abstract class MapElement {
30 private String[] labels;
30 protected String[] labels;
3131 private int type;
3232
3333 private int minResolution = 24;
9090 this.labels = Arrays.copyOf(labels, 4);
9191 }
9292
93 public int numLabels(){
94 int count = 0;
95 for (int i = 0; i < 4; i++) {
96 if (this.labels[i] != null)
97 count++;
98 else
99 break;
100 }
101 return count;
102 }
103
93104 public ExtTypeAttributes getExtTypeAttributes() {
94105 return extTypeAttributes;
95106 }
1616
1717 import uk.me.parabola.imgfmt.app.Area;
1818 import uk.me.parabola.imgfmt.app.Coord;
19 import uk.me.parabola.util.Locatable;
1920
2021 /**
2122 * A point on the map. This will appear as a symbol on the map and it will
2324 *
2425 * @author Steve Ratcliffe
2526 */
26 public class MapPoint extends MapElement {
27 public class MapPoint extends MapElement implements Locatable{
2728 private Coord location;
28 private boolean isRoadNamePoi;
2929
3030 public MapPoint() {
3131 }
6161 return isCityType(getType());
6262 }
6363
64 public void setRoadNamePOI(boolean isRoadNamePoi) {
65 this.isRoadNamePoi = isRoadNamePoi;
66 }
67
68 public boolean isRoadNamePOI() {
69 return this.isRoadNamePoi;
70 }
71
7264 public static boolean isCityType(int type)
7365 {
7466 return type >= 0x0100 && type <= 0x1100;
+0
-167
src/uk/me/parabola/mkgmap/general/MapPointKdTree.java less more
0 /*
1 * Copyright (C) 2012.
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
13 package uk.me.parabola.mkgmap.general;
14
15
16 import uk.me.parabola.imgfmt.app.Coord;
17 import uk.me.parabola.mkgmap.general.MapPoint;
18
19
20 /**
21 * A kd-tree (2D) implementation to solve the nearest neighbor problem.
22 * The tree is not explicitly balanced.
23 *
24 * @author GerdP
25 *
26 */
27 public class MapPointKdTree {
28 private static final boolean ROOT_NODE_USES_LONGITUDE = false;
29
30 private static class KdNode {
31 MapPoint point;
32 KdNode left;
33 KdNode right;
34
35 KdNode(MapPoint p) {
36 point = p;
37 }
38 }
39 // the tree root
40 private KdNode root;
41 // number of saved MapPoint objects
42 private int size;
43
44 // helpers
45 private MapPoint nextPoint ;
46 private double minDist;
47
48 /**
49 * create an empty tree
50 */
51 public MapPointKdTree() {
52 root = null;
53 }
54
55 public long size()
56 {
57 return size;
58 }
59
60
61 /**
62 * Start the add action with the root
63 * @param toAdd
64 */
65 public void add(MapPoint toAdd) {
66 size++;
67 root = add(toAdd, root, ROOT_NODE_USES_LONGITUDE);
68 }
69
70 /**
71 * Compares the given axis of both points.
72 * @param longitude <code>true</code>: compare longitude; <code>false</code> compare latitude
73 * @param c1 a point
74 * @param c2 another point
75 * @return <code>true</code> the axis value of c1 is smaller than c2;
76 * <code>false</code> the axis value of c1 is equal or larger than c2
77 */
78 private boolean isSmaller(boolean longitude, Coord c1, Coord c2) {
79 if (longitude) {
80 return c1.getLongitude() < c2.getLongitude();
81 } else {
82 return c1.getLatitude() < c2.getLatitude();
83 }
84 }
85
86 /**
87 * Recursive routine to find the right place for inserting
88 * into the tree.
89 * @param toAdd the point
90 * @param tree the subtree root node where to add (maybe <code>null</code>)
91 * @param useLongitude <code>true</code> the tree node uses longitude for comparison;
92 * <code>false</code> the tree node uses latitude for comparison
93 * @return the subtree root node after insertion
94 */
95 private KdNode add( MapPoint toAdd, KdNode tree, boolean useLongitude){
96 if( tree == null ) {
97 tree = new KdNode( toAdd );
98 } else {
99 if(isSmaller(useLongitude, toAdd.getLocation(), tree.point.getLocation())) {
100 tree.left = add(toAdd, tree.left, !useLongitude);
101 } else {
102 tree.right = add(toAdd, tree.right, !useLongitude);
103 }
104 }
105 return tree;
106 }
107
108 /**
109 * Searches for the point that has smallest distance to the given point.
110 * @param p the point to search for
111 * @return the point with shortest distance to <var>p</var>
112 */
113 public MapPoint findNextPoint(MapPoint p) {
114 // reset
115 minDist = Double.MAX_VALUE;
116 nextPoint = null;
117
118 // false => first node is a latitude level
119 return findNextPoint(p, root, ROOT_NODE_USES_LONGITUDE);
120 }
121
122 private MapPoint findNextPoint(MapPoint p, KdNode tree, boolean useLongitude) {
123 boolean continueWithLeft = false;
124 if (tree == null)
125 return nextPoint;
126
127 if (tree.left == null && tree.right == null){
128 double dist = tree.point.getLocation().distanceInDegreesSquared(p.getLocation());
129 if (dist < minDist){
130 nextPoint = tree.point;
131 minDist = dist;
132 }
133 return nextPoint;
134 }
135 else {
136 if (isSmaller(useLongitude, p.getLocation(), tree.point.getLocation())){
137 continueWithLeft = false;
138 nextPoint = findNextPoint(p, tree.left, !useLongitude);
139 }
140 else {
141 continueWithLeft = true;
142 nextPoint = findNextPoint(p, tree.right, !useLongitude);
143 }
144 }
145
146 double dist = tree.point.getLocation().distanceInDegreesSquared(p.getLocation());
147 if (dist < minDist){
148 nextPoint = tree.point;
149 minDist = dist;
150 }
151
152 // do we have to search the other part of the tree?
153 Coord test;
154 if (useLongitude)
155 test = Coord.makeHighPrecCoord(p.getLocation().getHighPrecLat(), tree.point.getLocation().getHighPrecLon());
156 else
157 test = Coord.makeHighPrecCoord(tree.point.getLocation().getHighPrecLat(), p.getLocation().getHighPrecLon());
158 if (test.distanceInDegreesSquared(p.getLocation()) < minDist){
159 if (continueWithLeft)
160 nextPoint = findNextPoint(p, tree.left, !useLongitude);
161 else
162 nextPoint = findNextPoint(p, tree.right, !useLongitude);
163 }
164 return nextPoint;
165 }
166 }
3939
4040 private final RoadDef roadDef;
4141 private boolean segmentsFollowing;
42 private boolean skipHousenumberProcessing;
43 private boolean namedByHousenumberProcessing;
44 private final int roadId;
4245
43 public MapRoad(long id, MapLine line) {
46 public MapRoad(int roadId, long OSMid, MapLine line) {
4447 super(line);
48 this.roadId = roadId;
4549 setPoints(line.getPoints());
46 roadDef = new RoadDef(id, getName());
50 roadDef = new RoadDef(OSMid, getName());
4751 }
4852
4953 private MapRoad(MapRoad r) {
5054 super(r);
55 roadId = r.roadId;
5156 roadDef = r.roadDef;
5257 segmentsFollowing = r.segmentsFollowing;
5358 }
5459
60 /**
61 * @return value that can be used to sort MapRoad instances
62 */
63 public int getRoadId(){
64 return roadId;
65 }
5566 public MapRoad copy() {
5667 return new MapRoad(this);
5768 }
100111 roadDef.setNoThroughRouting();
101112 }
102113
103 public void setStartsWithNode(boolean s) {
104 roadDef.setStartsWithNode(s);
105 }
106
107 public void setInternalNodes(boolean s) {
108 roadDef.setInternalNodes(s);
109 }
110
111 public void setNumNodes(int n) {
112 roadDef.setNumNodes(n);
113 }
114
115114 public void setNumbers(List<Numbers> numbers) {
116115 roadDef.setNumbersList(numbers);
117116 }
117 public List<Numbers> getNumbers() {
118 return roadDef.getNumbersList();
119 }
118120
119121 public RoadDef getRoadDef() {
120122 return roadDef;
121123 }
122124
123 public void setRoadCity(City c) {
124 roadDef.setCity(c);
125 }
126
127 public void setRoadZip(Zip z) {
128 roadDef.setZip(z);
125 public void addRoadCity(City c) {
126 roadDef.addCityIfNotPresent(c);
127 }
128
129 public void addRoadZip(Zip z) {
130 roadDef.addZipIfNotPresent(z);
129131 }
130132
131133 public void setRoundabout(boolean r) {
148150 this.segmentsFollowing = segmentsFollowing;
149151 }
150152
153 public boolean isSkipHousenumberProcessing() {
154 return skipHousenumberProcessing;
155 }
156
157 public void setSkipHousenumberProcessing(boolean skipHousenumberProcessing) {
158 this.skipHousenumberProcessing = skipHousenumberProcessing;
159 }
160
161 public boolean isNamedByHousenumberProcessing() {
162 return namedByHousenumberProcessing;
163 }
164
165 public void setNamedByHousenumberProcessing(boolean namedByHousenumberProcessing) {
166 this.namedByHousenumberProcessing = namedByHousenumberProcessing;
167 }
168
169 public boolean skipAddToNOD() {
170 return roadDef.skipAddToNOD();
171 }
172
173 public void skipAddToNOD(boolean skip) {
174 roadDef.skipAddToNOD(skip);
175 }
176
177 public boolean addLabel(String label){
178 if (label == null)
179 return false;
180 for (int i = 0; i < labels.length; i++){
181 if (labels[i] == null){
182 labels[i] = label;
183 return true;
184 }
185 if (labels[i].equals(label))
186 return false;
187 }
188 return false;
189 }
190
191 public String toString(){
192 if ((getName() == null || getName().isEmpty()) && getStreet() != null)
193 return "id="+this.getRoadDef().getId() + ", (" + this.getStreet() + ")";
194 else
195 return "id="+this.getRoadDef().getId() + ", " + this.getName();
196 }
197
198 public void resetImgData() {
199 roadDef.resetImgData();
200
201 }
151202 }
0 /*
1 * Copyright (C) 2015 Gerd Petermann
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.mkgmap.general;
13
14 import uk.me.parabola.imgfmt.app.lbl.Zip;
15
16 public class ZipCodeInfo implements Comparable<ZipCodeInfo> {
17 private static final String UNKNOWN = "?";
18 private final String zipCode;
19 private Zip imgZip;
20
21 public ZipCodeInfo (String zipCode){
22 this.zipCode = (zipCode != null) ? zipCode: UNKNOWN;
23 }
24
25 public String getZipCode() {
26 if (zipCode == UNKNOWN)
27 return null;
28 return zipCode;
29 }
30
31 public void setImgZip(Zip zip){
32 imgZip = zip;
33 }
34
35
36 public Zip getImgZip() {
37 return imgZip;
38 }
39
40 @Override
41 public int hashCode() {
42 final int prime = 31;
43 int result = 1;
44 result = prime * result + ((zipCode == null) ? 0 : zipCode.hashCode());
45 return result;
46 }
47
48 @Override
49 public boolean equals(Object obj) {
50 if (this == obj)
51 return true;
52 if (obj == null)
53 return false;
54 if (!(obj instanceof ZipCodeInfo))
55 return false;
56 ZipCodeInfo other = (ZipCodeInfo) obj;
57 if (zipCode == null) {
58 if (other.zipCode != null)
59 return false;
60 } else if (!zipCode.equals(other.zipCode))
61 return false;
62 return true;
63 }
64
65 @Override
66 public int compareTo(ZipCodeInfo o) {
67 if (this == o)
68 return 0;
69 return zipCode.compareTo(o.zipCode);
70 }
71
72 @Override
73 public String toString() {
74 return zipCode;
75 }
76 }
1717
1818 import java.io.File;
1919 import java.io.FileNotFoundException;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Set;
2620
2721 import uk.me.parabola.imgfmt.FileExistsException;
2822 import uk.me.parabola.imgfmt.FileNotWritableException;
2923 import uk.me.parabola.imgfmt.FileSystemParam;
3024 import uk.me.parabola.imgfmt.FormatException;
3125 import uk.me.parabola.imgfmt.MapFailedException;
32 import uk.me.parabola.imgfmt.app.Coord;
3326 import uk.me.parabola.imgfmt.app.map.Map;
3427 import uk.me.parabola.imgfmt.app.srt.Sort;
35 import uk.me.parabola.imgfmt.app.srt.SortKey;
3628 import uk.me.parabola.log.Logger;
3729 import uk.me.parabola.mkgmap.CommandArgs;
3830 import uk.me.parabola.mkgmap.build.MapBuilder;
3931 import uk.me.parabola.mkgmap.combiners.OverviewBuilder;
4032 import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
41 import uk.me.parabola.mkgmap.general.MapLine;
42 import uk.me.parabola.mkgmap.general.MapPoint;
43 import uk.me.parabola.mkgmap.general.MapRoad;
4433 import uk.me.parabola.mkgmap.reader.plugin.MapReader;
4534
4635 /**
6150 try {
6251 LoadableMapDataSource src = loadFromFile(args, filename);
6352 sort = args.getSort();
64 log.info("Making Road Name POIs for", filename);
65 makeRoadNamePOIS(args, src);
6653 if (createOverviewFiles){
6754 if (src.overviewMapLevels() != null){
6855 makeMap(args, src, OverviewBuilder.OVERVIEW_PREFIX);
167154 log.info("Finished loading", name);
168155 return src;
169156 }
170
171 void makeRoadNamePOIS(CommandArgs args, LoadableMapDataSource src) {
172 String rnp = args.get("road-name-pois", null);
173 // are road name POIS wanted?
174 if(rnp != null) {
175 rnp = rnp.toUpperCase();
176 int rnpt = 0x640a; // Garmin type 'Locale'
177 if(rnp.length() > 0) {
178 // override type code
179 rnpt = Integer.decode(rnp);
180 }
181 // collect lists of roads that have the same name
182 java.util.Map<String, List<MapRoad>> namedRoads = new HashMap<String, List<MapRoad>>();
183 for(MapLine l : src.getLines()) {
184 if(l.isRoad()) {
185 MapRoad r = (MapRoad)l;
186 String rn = r.getName();
187 if(rn != null) {
188 List<MapRoad> rl = namedRoads.get(rn);
189 if(rl == null) {
190 rl = new ArrayList<MapRoad>();
191 namedRoads.put(rn, rl);
192 }
193 rl.add(r);
194 }
195 }
196 }
197
198 // generate a POI for each named road
199
200 // sort by name and coordinate of first point so that
201 // the order is always the same for the same input
202 List<SortKey<MapRoad>> rnpRoads = new ArrayList<SortKey<MapRoad>>();
203 for(List<MapRoad> lr : findConnectedRoadsWithSameName(namedRoads)) {
204 // connected roads are not ordered so just use first in list
205 MapRoad r = lr.get(0);
206 String key = r.getName();
207 List<Coord> points = r.getPoints();
208 if(!points.isEmpty())
209 key += "_" + points.get(0);
210 rnpRoads.add(sort.createSortKey(r, key));
211 }
212 Collections.sort(rnpRoads);
213 for(SortKey<MapRoad> sr : rnpRoads)
214 src.getPoints().add(makeRoadNamePOI(sr.getObject(), rnpt));
215 }
216 }
217
218 private boolean roadsAreJoined(MapLine r1, MapLine r2) {
219 if (r1.getBounds().intersects(r2.getBounds()) == false) {
220 return false;
221 }
222
223 if(r1 != r2) {
224 for(Coord c1 : r1.getPoints()) {
225 for(Coord c2 : r2.getPoints()) {
226 if(c1 == c2 || c1.highPrecEquals(c2))
227 return true;
228 }
229 }
230 }
231 return false;
232 }
233
234 // hairy function to build a set of lists - each list contains
235 // the roads that have the same name and are connected
236
237 private Set<List<MapRoad>> findConnectedRoadsWithSameName(java.util.Map<String, List<MapRoad>> namedRoads) {
238 // roadGroups is a set to avoid duplicate groups
239 Set<List<MapRoad>> roadGroups = new HashSet<List<MapRoad>>();
240
241 // loop over the lists of roads that have the same name
242 for(List<MapRoad> allRoadsWithSameName : namedRoads.values()) {
243 // for each road that has the same name, keep track of its group
244 java.util.Map<MapRoad,List<MapRoad>> roadGroupMap = new HashMap<MapRoad,List<MapRoad>>();
245
246 // loop over all of the roads with the same name
247 for(int i = 0; i < allRoadsWithSameName.size(); ++i) {
248 boolean roadWasJoined = false;
249 for(int j = 0; j < allRoadsWithSameName.size(); ++j) {
250 if(i != j) {
251 // see if these two roads are joined
252 MapRoad ri = allRoadsWithSameName.get(i);
253 MapRoad rj = allRoadsWithSameName.get(j);
254 if(roadsAreJoined(ri, rj)) {
255 // yes, they are joined so put both in a group
256 // and associate the group with each road
257 roadWasJoined = true;
258 List<MapRoad> groupi = roadGroupMap.get(ri);
259 List<MapRoad> groupj = roadGroupMap.get(rj);
260 if(groupi == null) {
261 // ri is not in a group yet
262 if(groupj == null) {
263 // neither is rj so make a new group
264 groupi = new ArrayList<MapRoad>();
265 groupi.add(ri);
266 groupi.add(rj);
267 roadGroupMap.put(ri, groupi);
268 roadGroupMap.put(rj, groupi);
269 }
270 else {
271 // add ri to groupj
272 groupj.add(ri);
273 roadGroupMap.put(ri, groupj);
274 }
275 }
276 else if(groupj == null) {
277 // add rj to groupi
278 groupi.add(rj);
279 roadGroupMap.put(rj, groupi);
280 }
281 else if(groupi != groupj) {
282 // ri and rj are in separate groups so put
283 // all the roads in groupj into groupi
284 for(MapRoad r : groupj)
285 roadGroupMap.put(r, groupi);
286 groupi.addAll(groupj);
287 }
288 }
289 }
290 }
291 if(!roadWasJoined) {
292 // make a group with just one entry
293 MapRoad ri = allRoadsWithSameName.get(i);
294 List<MapRoad>group = new ArrayList<MapRoad>();
295 group.add(ri);
296 roadGroupMap.put(ri, group);
297 }
298 }
299
300 // now add the new group(s) to the final result
301 for(List<MapRoad> r : roadGroupMap.values())
302 roadGroups.add(r);
303 }
304 return roadGroups;
305 }
306
307 private MapPoint makeRoadNamePOI(MapRoad road, int type) {
308 List<Coord> points = road.getPoints();
309 int numPoints = points.size();
310 Coord coord;
311 // XXX Why not always use an existing point close to
312 // numpoints/2 ?
313 if ((numPoints & 1) == 0) {
314 int i2 = numPoints / 2;
315 int i1 = i2 - 1;
316 coord = points.get(i1).makeBetweenPoint(points.get(i2), 0.5);
317
318 } else {
319 coord = points.get(numPoints / 2);
320 }
321
322 String name = road.getName();
323 MapPoint rnp = new MapPoint();
324
325 rnp.setName(name);
326 rnp.setRoadNamePOI(true);
327 rnp.setType(type);
328 rnp.setLocation(coord);
329 return rnp;
330 }
331157 }
675675 }
676676
677677 @Override
678 public Rule getFinalizeRule() {
679 if (rules.isEmpty())
680 return null;
681 return rules.get(0).getFinalizeRule();
682 }
683
684 @Override
678685 public void printStats(String header) {
679686 // TODO Auto-generated method stub
680687 }
165165 this.finalizeRule = finalizeRule;
166166 }
167167
168 @Override
169 public Rule getFinalizeRule() {
170 return finalizeRule;
171 }
168172
169173 public Op getOp(){
170174 return expression;
9494 this.finalizeRule = finalizeRule;
9595 }
9696
97 @Override
98 public Rule getFinalizeRule() {
99 return finalizeRule;
100 }
101
97102 public Op getOp(){
98103 return expression;
99104 }
7171 add("mkgmap:synthesised");
7272 add("mkgmap:highest-resolution-only");
7373 add("mkgmap:flare-check");
74 add("mkgmap:numbers");
7475 }
7576 };
7677
3232 import uk.me.parabola.mkgmap.osmstyle.eval.Op;
3333 import uk.me.parabola.mkgmap.reader.osm.Element;
3434 import uk.me.parabola.mkgmap.reader.osm.Rule;
35 import uk.me.parabola.mkgmap.reader.osm.TagDict;
3536 import uk.me.parabola.mkgmap.reader.osm.TypeResult;
3637 import uk.me.parabola.mkgmap.reader.osm.WatchableTypeResult;
3738
5051 // identifies cached values
5152 int cacheId;
5253 boolean compiled = false;
54
55 private final static short executeFinalizeRulesTagKey = TagDict.getInstance().xlate("mkgmap:execute_finalize_rules");
5356
5457 private RuleIndex index = new RuleIndex();
5558 private final Set<String> usedTags = new HashSet<String>();
8689 if (rules != null && !rules.isEmpty() )
8790 candidates.or(rules);
8891 }
92 Rule lastRule = null;
8993 for (int i = candidates.nextSetBit(0); i >= 0; i = candidates.nextSetBit(i + 1)) {
9094 a.reset();
91 cacheId = rules[i].resolveType(cacheId, el, a);
95 lastRule = rules[i];
96 cacheId = lastRule.resolveType(cacheId, el, a);
9297 if (a.isResolved())
9398 return cacheId;
99 }
100 if (lastRule != null && lastRule.getFinalizeRule() != null){
101 if ("true".equals(el.getTag(executeFinalizeRulesTagKey))){
102 cacheId = lastRule.getFinalizeRule().resolveType(cacheId, el, a);
103 }
94104 }
95105 return cacheId;
96106 }
263273 }
264274
265275 @Override
276 public Rule getFinalizeRule() {
277 return finalizeRule;
278 }
279
280 @Override
266281 public void printStats(String header) {
267282 if (rules == null)
268283 return;
119119 private HashSet<Long> deletedRoads = new HashSet<>();
120120
121121 private int nextNodeId = 1;
122 private int nextRoadId = 1;
122123
123124 private HousenumberGenerator housenumberGenerator;
124125
141142 private int reportDeadEnds;
142143 private final boolean linkPOIsToWays;
143144 private final boolean mergeRoads;
145 private final boolean routable;
144146
145147
146148 private LineAdder lineAdder = new LineAdder() {
203205
204206 // undocumented option - usually used for debugging only
205207 mergeRoads = props.getProperty("no-mergeroads", false) == false;
206
208 routable = props.containsKey("route");
207209
208210 }
209211
228230 el = way.copy();
229231 }
230232 postConvertRules(el, type);
231 housenumberGenerator.addWay((Way)el);
233 if (type.isRoad() == false)
234 housenumberGenerator.addWay((Way)el);
232235 addConvertedWay((Way) el, type);
233236 }
234237
278281 else
279282 rules = wayRules;
280283 }
281
282284 Way cycleWay = null;
283285 String cycleWayTag = way.getTag(makeCycleWayTagKey);
284286 if ("yes".equals(cycleWayTag)){
613615 if (cw.isValid())
614616 addRoad(cw);
615617 }
616 housenumberGenerator.generate(lineAdder);
617
618 createRouteRestrictionsFromPOI();
618 housenumberGenerator.generate(lineAdder, nextNodeId);
619 housenumberGenerator = null;
620
621 if (routable)
622 createRouteRestrictionsFromPOI();
619623 poiRestrictions = null;
620
621 for (RestrictionRelation rr : restrictions) {
622 rr.addRestriction(collector, nodeIdMap);
624 if (routable){
625 for (RestrictionRelation rr : restrictions) {
626 rr.addRestriction(collector, nodeIdMap);
627 }
623628 }
624629 roads = null;
625
626 for(Relation relation : throughRouteRelations) {
627 Node node = null;
628 Way w1 = null;
629 Way w2 = null;
630 for(Map.Entry<String,Element> member : relation.getElements()) {
631 if(member.getValue() instanceof Node) {
632 if(node == null)
633 node = (Node)member.getValue();
634 else
635 log.warn("Through route relation", relation.toBrowseURL(), "has more than 1 node");
636 }
637 else if(member.getValue() instanceof Way) {
638 Way w = (Way)member.getValue();
639 if(w1 == null)
640 w1 = w;
641 else if(w2 == null)
642 w2 = w;
643 else
644 log.warn("Through route relation", relation.toBrowseURL(), "has more than 2 ways");
645 }
646 }
647
648 CoordNode coordNode = null;
649 if(node == null)
650 log.warn("Through route relation", relation.toBrowseURL(), "is missing the junction node");
651 else {
652 Coord junctionPoint = node.getLocation();
653 if(bbox != null && !bbox.contains(junctionPoint)) {
654 // junction is outside of the tile - ignore it
655 continue;
656 }
657 coordNode = nodeIdMap.get(junctionPoint);
658 if(coordNode == null)
659 log.warn("Through route relation", relation.toBrowseURL(), "junction node at", junctionPoint.toOSMURL(), "is not a routing node");
660 }
661
662 if(w1 == null || w2 == null)
663 log.warn("Through route relation", relation.toBrowseURL(), "should reference 2 ways that meet at the junction node");
664
665 if(coordNode != null && w1 != null && w2 != null)
666 collector.addThroughRoute(coordNode.getId(), w1.getId(), w2.getId());
630 if (routable){
631 for(Relation relation : throughRouteRelations) {
632 Node node = null;
633 Way w1 = null;
634 Way w2 = null;
635 for(Map.Entry<String,Element> member : relation.getElements()) {
636 if(member.getValue() instanceof Node) {
637 if(node == null)
638 node = (Node)member.getValue();
639 else
640 log.warn("Through route relation", relation.toBrowseURL(), "has more than 1 node");
641 }
642 else if(member.getValue() instanceof Way) {
643 Way w = (Way)member.getValue();
644 if(w1 == null)
645 w1 = w;
646 else if(w2 == null)
647 w2 = w;
648 else
649 log.warn("Through route relation", relation.toBrowseURL(), "has more than 2 ways");
650 }
651 }
652
653 CoordNode coordNode = null;
654 if(node == null)
655 log.warn("Through route relation", relation.toBrowseURL(), "is missing the junction node");
656 else {
657 Coord junctionPoint = node.getLocation();
658 if(bbox != null && !bbox.contains(junctionPoint)) {
659 // junction is outside of the tile - ignore it
660 continue;
661 }
662 coordNode = nodeIdMap.get(junctionPoint);
663 if(coordNode == null)
664 log.warn("Through route relation", relation.toBrowseURL(), "junction node at", junctionPoint.toOSMURL(), "is not a routing node");
665 }
666
667 if(w1 == null || w2 == null)
668 log.warn("Through route relation", relation.toBrowseURL(), "should reference 2 ways that meet at the junction node");
669
670 if(coordNode != null && w1 != null && w2 != null)
671 collector.addThroughRoute(coordNode.getId(), w1.getId(), w2.getId());
672 }
667673 }
668674 // return memory to GC
669675 nodeIdMap = null;
15111517 if((i + 1) < points.size()) {
15121518 Coord nextP = points.get(i + 1);
15131519 double d = p.distance(nextP);
1514 // get arc size as a proportion of the max allowed - a
1515 // value greater than 1.0 indicate that the bbox is
1516 // too large in at least one dimension
1517 double arcProp = LineSizeSplitterFilter.testDims(nextP.getLatitude() -
1518 p.getLatitude(),
1519 nextP.getLongitude() -
1520 p.getLongitude());
1521 if(arcProp >= 1.0 || d > MAX_ARC_LENGTH) {
1522 nextP = p.makeBetweenPoint(nextP, 0.95 * Math.min(1 / arcProp, MAX_ARC_LENGTH / d));
1523 nextP.incHighwayCount();
1524 points.add(i + 1, nextP);
1525 double newD = p.distance(nextP);
1526 if (log.isInfoEnabled())
1527 log.info("Way", debugWayName, "contains a segment that is", (int)d + "m long but I am adding a new point to reduce its length to", (int)newD + "m");
1528 d = newD;
1529 }
1530
1520 for (;;){
1521 int dlat = Math.abs(nextP.getLatitude() - p.getLatitude());
1522 int dlon = Math.abs(nextP.getLongitude() - p.getLongitude());
1523 if (d > MAX_ARC_LENGTH || Math.max(dlat, dlon) >= LineSizeSplitterFilter.MAX_SIZE){
1524 double frac = Math.min(0.5, 0.95 * (MAX_ARC_LENGTH / d));
1525 nextP = p.makeBetweenPoint(nextP, frac);
1526 nextP.incHighwayCount();
1527 points.add(i + 1, nextP);
1528 double newD = p.distance(nextP);
1529 if (log.isInfoEnabled())
1530 log.info("Way", debugWayName, "contains a segment that is", (int)d + "m long but I am adding a new point to reduce its length to", (int)newD + "m");
1531 d = newD;
1532 } else
1533 break;
1534 }
1535
15311536 wayBBox.addPoint(nextP);
15321537
15331538 if((arcLength + d) > MAX_ARC_LENGTH) {
1539 if (i <= 0)
1540 log.error("internal error: long arc segment was not split", debugWayName);
15341541 assert i > 0 : "long arc segment was not split";
15351542 assert trailingWay == null : "trailingWay not null #1";
15361543 trailingWay = splitWayAt(way, i);
15401547 log.info("Splitting way", debugWayName, "at", points.get(i).toOSMURL(), "to limit arc length to", (long)arcLength + "m");
15411548 }
15421549 else if(wayBBox.tooBig()) {
1550 if (i <= 0)
1551 log.error("internal error: arc segment with big bbox not split", debugWayName);
15431552 assert i > 0 : "arc segment with big bbox not split";
15441553 assert trailingWay == null : "trailingWay not null #2";
15451554 trailingWay = splitWayAt(way, i);
16071616 MapLine line = new MapLine();
16081617 elementSetup(line, cw.getGType(), way);
16091618 line.setPoints(points);
1610 MapRoad road = new MapRoad(way.getId(), line);
1611
1619 MapRoad road = new MapRoad(nextRoadId++, way.getId(), line);
1620 if (routable == false)
1621 road.skipAddToNOD(true);
1622
16121623 boolean doFlareCheck = true;
16131624
16141625 if (cw.isRoundabout()){
16691680 rr.updateViaWay(way, nodeIndices);
16701681 }
16711682 }
1672 road.setNumNodes(numNodes);
1683
16731684 if(numNodes > 0) {
16741685 // replace Coords that are nodes with CoordNodes
1675 boolean hasInternalNodes = false;
16761686 for(int i = 0; i < numNodes; ++i) {
16771687 int n = nodeIndices.get(i);
1678 if(n > 0 && n < points.size() - 1)
1679 hasInternalNodes = true;
16801688 Coord coord = points.get(n);
16811689 CoordNode thisCoordNode = nodeIdMap.get(coord);
16821690 assert thisCoordNode != null : "Way " + debugWayName + " node " + i + " (point index " + n + ") at " + coord.toOSMURL() + " yields a null coord node";
16861694 }
16871695 points.set(n, thisCoordNode);
16881696 }
1689
1690 road.setStartsWithNode(nodeIndices.get(0) == 0);
1691 road.setInternalNodes(hasInternalNodes);
16921697 }
16931698
16941699 if (roadLog.isInfoEnabled()) {
20432048 }
20442049 }
20452050
2051 @Override
20462052 public Boolean getDriveOnLeft(){
20472053 assert roads == null : "getDriveOnLeft() should be called after end()";
20482054 return driveOnLeft;
0 /*
1 * Copyright (C) 2015 Gerd Petermann
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
13 package uk.me.parabola.mkgmap.osmstyle.housenumber;
14
15 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
16
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.BitSet;
20 import java.util.Collections;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Set;
27 import java.util.TreeMap;
28
29 import uk.me.parabola.imgfmt.Utils;
30 import uk.me.parabola.imgfmt.app.Coord;
31 import uk.me.parabola.imgfmt.app.net.NumberStyle;
32 import uk.me.parabola.imgfmt.app.net.Numbers;
33 import uk.me.parabola.log.Logger;
34 import uk.me.parabola.mkgmap.filters.LineSplitterFilter;
35 import uk.me.parabola.mkgmap.general.CityInfo;
36 import uk.me.parabola.mkgmap.general.MapRoad;
37 import uk.me.parabola.mkgmap.general.ZipCodeInfo;
38 import uk.me.parabola.mkgmap.osmstyle.housenumber.HousenumberGenerator.HousenumberMatchByPosComparator;
39
40 /**
41 * Helper class to allow easy corrections like splitting.
42 *
43 * If we want to split an interval because it overlaps
44 * with another one, we have different options.
45 * 1) if the interval covers multiple points of the road, we may change a point to a number node
46 * 2) or if the road segment is long enough we may add a point to split it,
47 * long enough means that the we can find a point that is so close to the original
48 * line that it is not too distorting,
49 * 3) or we may duplicate the node at one end (or both) and move some of the numbers to that new
50 * zero-length-interval.
51 *
52 * When we find no more overlaps, we can start to reduce the distance
53 * of the calculated position (the result of the address search in
54 * Garmin products) and the known best position on the road.
55 * This is a bit tricky: Garmin software places
56 * a) a single house in the middle of the
57 * segment covered by the interval
58 * b) two or more houses are placed so that the first
59 * is at the very beginning, the last is at the very end,
60 * the rest is between them with equal distances.
61 * The problem: We can have houses on both sides of the segment,
62 * so the optimal length for the left side may not be the
63 * best for the right side.
64 * We try to find a good compromise between good search result
65 * and the number of additional intervals.
66 *
67 * @author GerdP
68 *
69 */
70 public class ExtNumbers {
71 private static final Logger log = Logger.getLogger(ExtNumbers.class);
72
73 private final HousenumberRoad housenumberRoad;
74 private static final int MAX_LOCATE_ERROR = 40;
75
76 private static final List<HousenumberMatch> NO_HOUSES = Collections.emptyList();
77
78 public ExtNumbers prev,next;
79 class RoadSide {
80 List<HousenumberMatch> houses = Collections.emptyList();
81 boolean multipleZipCodes;
82 boolean multipleCities;
83 boolean notInOrder;
84 }
85
86 private RoadSide leftSide,rightSide;
87 private Numbers numbers = null;
88 private int startInRoad, endInRoad;
89 private int nodeIndex;
90
91 private boolean needsSplit;
92 private HousenumberMatch worstHouse;
93
94 // indicates a number that is found in the interval, but should not
95 private int badNum;
96
97 private boolean hasGaps; // true if interval covers more numbers than known
98
99 // constants representing reasons for splitting
100 public static final int SR_FIX_ERROR = 0;
101 public static final int SR_OPT_LEN = 1;
102 public static final int SR_SPLIT_ROAD_END = 2;
103
104 public ExtNumbers(HousenumberRoad housenumberRoad) {
105 super();
106 this.housenumberRoad = housenumberRoad;
107 reset();
108 }
109
110 private void setNeedsSplit(boolean b) {
111 needsSplit = true;
112 }
113
114 public boolean needsSplit() {
115 return needsSplit;
116 }
117
118 private boolean notInOrder(boolean left){
119 RoadSide rs = (left) ? leftSide : rightSide;
120 return (rs != null) ? rs.notInOrder : false;
121 }
122
123 private List<HousenumberMatch> getHouses(boolean left){
124 RoadSide rs = (left) ? leftSide : rightSide;
125 return (rs != null) ? rs.houses : NO_HOUSES;
126 }
127
128 private MapRoad getRoad(){
129 return housenumberRoad.getRoad();
130 }
131
132 private void reset() {
133 numbers = null;
134 needsSplit = false;
135 hasGaps = false;
136 }
137
138
139 public void setNodeIndex(int nodeIndex) {
140 this.nodeIndex = nodeIndex;
141 if (numbers != null)
142 numbers.setIndex(nodeIndex);
143 }
144
145
146
147 public Numbers getNumbers() {
148 if (numbers == null){
149 numbers = new Numbers();
150 numbers.setIndex(nodeIndex);
151 fillNumbers(Numbers.LEFT);
152 fillNumbers(Numbers.RIGHT);
153 if (!numbers.isEmpty()){
154 verify(getHouses(Numbers.LEFT)); // TODO : remove
155 verify(getHouses(Numbers.RIGHT)); // TODO : remove
156 }
157 }
158 return numbers;
159 }
160
161 /**
162 * Store given house numbers and meta info
163 * @param housenumbers a list of house numbers, sorted by appearance on the road
164 * @param startSegment index of road point where this segment starts
165 * @param endSegment index of road point where this segment ends
166 * @param left {@code true} the left side of the street; {@code false} the right side of the street
167 * @return the number of elements which were used (from the beginning)
168 */
169 public int setNumbers(List<HousenumberMatch> housenumbers, int startSegment, int endSegment, boolean left) {
170 int assignedNumbers = 0;
171 if (housenumbers.isEmpty() == false) {
172 RoadSide rs = new RoadSide();
173 if (left)
174 leftSide = rs;
175 else
176 rightSide = rs;
177 // get the sublist of house numbers
178 int maxN = -1;
179 int numHouses = housenumbers.size();
180 for (int i = 0; i< numHouses; i++) {
181 HousenumberMatch house = housenumbers.get(i);
182 if (house.isIgnored())
183 continue;
184 if (house.getSegment() >= endSegment) {
185 break;
186 }
187 maxN = i;
188 }
189
190 if (maxN >= 0) {
191 assignedNumbers = maxN + 1;
192 rs.houses = new ArrayList<>(housenumbers.subList(0, assignedNumbers));
193 startInRoad = startSegment;
194 endInRoad = endSegment;
195 assert startSegment < endSegment;
196 if (getRoad().getPoints().get(startInRoad).isNumberNode() == false || getRoad().getPoints().get(endInRoad).isNumberNode() == false){
197 log.error("internal error: start or end is not a number node", this);
198 }
199 }
200 }
201 return assignedNumbers;
202 }
203
204 /**
205 * Apply the given house numbers to the numbers object.
206 * @param left {@code true} the left side of the street; {@code false} the right side of the street
207 */
208 private void fillNumbers(boolean left) {
209 NumberStyle style = NumberStyle.NONE;
210 List<HousenumberMatch> houses = getHouses(left);
211 if (houses.isEmpty() == false) {
212 Set<CityInfo> cityInfos = new HashSet<>();
213 Set<ZipCodeInfo> zipCodes = new HashSet<>();
214 // get the sublist of house numbers
215 boolean even = false;
216 boolean odd = false;
217 boolean inOrder = true;
218 boolean inc = false;
219 boolean dec = false;
220 HousenumberMatch highest, lowest;
221 lowest = highest = houses.get(0);
222 Int2IntOpenHashMap distinctNumbers = new Int2IntOpenHashMap();
223 int numHouses = houses.size();
224 HousenumberMatch pred = null;
225 for (int i = 0; i< numHouses; i++) {
226 HousenumberMatch house = houses.get(i);
227 if (house.getCityInfo() != null && house.getCityInfo().isEmpty() == false)
228 cityInfos.add(house.getCityInfo());
229 if (house.getZipCode() != null && house.getZipCode().getZipCode() != null)
230 zipCodes.add(house.getZipCode());
231 int num = house.getHousenumber();
232 if (!hasGaps)
233 distinctNumbers.put(num, 1);
234 if (num > highest.getHousenumber())
235 highest = house;
236 if (num < lowest.getHousenumber())
237 lowest = house;
238 if (num % 2 == 0) {
239 even = true;
240 } else {
241 odd = true;
242 }
243
244 if (pred != null){
245 int diff = num - pred.getHousenumber();
246 if(diff > 0)
247 inc = true;
248 else if (diff < 0)
249 dec = true;
250 }
251 pred = house;
252 }
253
254 if (even && odd) {
255 style = NumberStyle.BOTH;
256 } else if (even) {
257 style = NumberStyle.EVEN;
258 } else {
259 style = NumberStyle.ODD;
260 }
261 int highestNum = highest.getHousenumber();
262 int lowestNum = lowest.getHousenumber();
263 int start = houses.get(0).getHousenumber();
264 int end = houses.get(numHouses-1).getHousenumber();
265 boolean increasing = false; // from low to high
266 if (dec & inc)
267 inOrder = false;
268 if (start == end && highestNum - lowestNum != 0){
269 if (prev != null){
270 int lastEnd = prev.getNumbers().getEnd(left );
271 if (lastEnd <= lowestNum)
272 increasing = true;
273 } else if (next != null){
274 int nextStart = next.getNumbers().getStart(left);
275 if (highestNum < nextStart)
276 increasing = true;
277 } else {
278 increasing = true;
279 }
280 }
281 else if (start != highestNum && start != lowestNum
282 || end != highestNum && end != lowestNum) {
283 inOrder = false;
284 if (start <= end)
285 increasing = true;
286 } else if (start < end){
287 increasing = true;
288 }
289 if (increasing){
290 start = lowestNum;
291 end = highestNum;
292 } else {
293 start = highestNum;
294 end = lowestNum;
295 }
296 if (!hasGaps){
297 int step = (style == NumberStyle.BOTH) ? 1 : 2;
298 for (int n = lowestNum+step; n < highestNum; n += step){
299 if (distinctNumbers.containsKey(n))
300 continue;
301 hasGaps = true;
302 break;
303 }
304 }
305 RoadSide rs = (left) ? leftSide : rightSide;
306 numbers.setNumbers(left, style, start, end);
307 rs.multipleCities = (cityInfos.size() > 1);
308 rs.multipleZipCodes = (zipCodes.size() > 1);
309 if (cityInfos.size() == 1){
310 CityInfo ci = cityInfos.iterator().next();
311 if (ci.isEmpty() == false){
312 if (ci.equals(housenumberRoad.getRoadCityInfo()) == false)
313 numbers.setCityInfo(left, ci);
314 }
315 }
316
317 if (zipCodes.size() == 1){
318 ZipCodeInfo zipCodeInfo = zipCodes.iterator().next();
319 if (zipCodeInfo.getZipCode() != null){
320 if (zipCodeInfo.equals(housenumberRoad.getRoadZipCode()) == false){
321 // we found a zip code and the road doesn't yet have one, use it for the whole road
322 if (getRoad().getZip() == null){
323 housenumberRoad.setZipCodeInfo(zipCodeInfo);
324 } else
325 numbers.setZipCode(left, zipCodeInfo);
326 }
327 }
328 }
329 rs.notInOrder = !inOrder;
330 }
331 }
332
333 /**
334 * Return the intervals in the format used for the writer routines
335 * @return
336 */
337 public List<Numbers> getNumberList() {
338 // do we have numbers?
339 boolean foundNumbers = false;
340 for (ExtNumbers curr = this; curr != null; curr = curr.next){
341 if (curr.hasNumbers()){
342 foundNumbers = true;
343 break;
344 }
345 }
346 if (!foundNumbers)
347 return null;
348
349 List<Numbers> list = new ArrayList<>();
350 for (ExtNumbers curr = this; curr != null; curr = curr.next){
351 if (curr.hasNumbers() == false)
352 continue;
353 list.add(curr.getNumbers());
354 if (log.isInfoEnabled()) {
355 if (curr.prev == null){
356 MapRoad road = curr.getRoad();
357 if (road.getStreet() == null && road.getName() == null)
358 log.info("final numbers for", road, curr.housenumberRoad.getName(), "in", road.getCity());
359 else
360 log.info("final numbers for", road, "in", road.getCity());
361 }
362 Numbers cn = curr.getNumbers();
363 log.info("Left: ",cn.getLeftNumberStyle(),cn.getIndex(),"Start:",cn.getLeftStart(),"End:",cn.getLeftEnd(), "numbers "+curr.getHouses(Numbers.LEFT));
364 log.info("Right:",cn.getRightNumberStyle(),cn.getIndex(),"Start:",cn.getRightStart(),"End:",cn.getRightEnd(), "numbers "+curr.getHouses(Numbers.RIGHT));
365 }
366 }
367 return list;
368 }
369
370 public ExtNumbers checkSingleChainSegments(String streetName, boolean removeGaps) {
371 ExtNumbers curr = this;
372 ExtNumbers head = this;
373 if (housenumberRoad.isRandom() || removeGaps){
374 for (curr = head; curr != null; curr = curr.next){
375 while (curr.hasGaps && (removeGaps || curr.notInOrder(true) || curr.notInOrder(false))){
376 curr.worstHouse = null;
377 curr.badNum = -1;
378 ExtNumbers test = curr.tryChange(SR_FIX_ERROR);
379 if (test != curr){
380 if (curr.prev == null)
381 head = test;
382 curr = test;
383 }
384 else {
385 log.warn("can't split numbers interaval for road", curr.getNumbers(), curr);
386 break;
387 }
388 }
389
390 }
391 }
392 for (curr = head; curr != null; curr = curr.next){
393 while (curr.isPlausible() == false){
394 // this happens in the following cases:
395 // 1. correct OSM data, multiple houses build a block. Standing on the road
396 // you probably see a small service road which leads to the houses.
397 // It is okay to use each of them.
398 // 2. correct OSM data, one or more house should be connected to a
399 // different road with the same name, we want to ignore them
400 // 3. wrong OSM data, one or more numbers are wrong, we want to ignore them
401 // 4. other cases, e.g. numbers 1,3,5 followed by 10,14,12. This should be fixed
402 // by splitting the segment first, as the OSM data might be correct.
403 if (log.isInfoEnabled())
404 log.info("detected unplausible interval in",streetName, curr.getNumbers(),"in road", getRoad());
405 if (log.isDebugEnabled()){
406 if (curr.notInOrder(Numbers.LEFT))
407 log.debug("left numbers not in order:", getRoad(), curr.getHouses(Numbers.LEFT));
408 if (curr.notInOrder(Numbers.RIGHT))
409 log.debug("right numbers not in order:", getRoad(), curr.getHouses(Numbers.RIGHT));
410 }
411 curr.setNeedsSplit(true);
412 curr.findGoodSplitPos();
413 ExtNumbers test = curr.tryChange(SR_FIX_ERROR);
414 if (test != curr){
415 housenumberRoad.setChanged(true);
416 if (curr.prev == null)
417 head = test;
418 curr = test;
419 }
420 else {
421 log.warn("can't fix unplausible numbers interaval for road",curr.getNumbers(),curr);
422 break;
423 }
424 }
425 }
426 return head;
427 }
428
429 private void verify(List<HousenumberMatch> houses) {
430 for (HousenumberMatch house : houses){
431 if (house.isIgnored())
432 continue;
433 if (house.getSegment() < startInRoad || house.getSegment() >= endInRoad){
434 log.error("internal error, house has wrong segment, road",getRoad(),"house",house,house.getElement().toBrowseURL());
435 }
436 if (Double.isNaN(house.getDistance()) || house.getDistance() > HousenumberGenerator.MAX_DISTANCE_TO_ROAD + 10){
437 if (house.getGroup() == null)
438 log.error("internal error, distance to road too large, road",getRoad(),"house",house,house.getElement().toBrowseURL());
439 }
440 }
441 }
442
443
444 /**
445 * Split an interval. This means that we either change an existing point
446 * to a number node or we add a number node. A new node may be added between
447 * two other points or as a duplicate of one of them.
448 * Depending on the reason for the split we use different methods to distribute
449 * the existing numbers to the new intervals.
450 * @param reason indicates the reason for the split
451 * @return this if split was not done or a new {@link ExtNumbers} instance which is
452 * the start of a chain, the last instance in this chain points to the same
453 * {@link ExtNumbers} instance as the {@code next} field in {@code this} .
454 *
455 */
456 public ExtNumbers tryChange(int reason){
457 ExtNumbers en = this;
458 if (reason == SR_FIX_ERROR){
459 if (notInOrder(Numbers.LEFT) == false && notInOrder(Numbers.RIGHT) == false){
460 if (badNum < 0 && worstHouse != null)
461 badNum = worstHouse.getHousenumber();
462 if (badNum > 0){
463 en = splitInterval();
464 }
465 else {
466 log.info("have to split",this);
467 }
468 }
469 }
470 //TODO: in some cases it might be better to move a house to the prev or next interval instead of splitting
471 if (en == this)
472 en = tryAddNumberNode(reason);
473 boolean changedInterval = false;
474 if (en != this){
475 if (en.hasNumbers() && en.next != null && en.next.hasNumbers()){
476 changedInterval = true;
477 } else {
478 ExtNumbers test = en.hasNumbers() ? en : en.next;
479 if (test.getNumbers().isSimilar(this.getNumbers()) == false)
480 changedInterval = true;
481 }
482 if (changedInterval)
483 housenumberRoad.setChanged(true);
484 else {
485 if (reason == SR_FIX_ERROR){
486 if (en.hasNumbers()){
487 en.worstHouse = worstHouse;
488 return en.tryChange(reason);
489 } else {
490 en.next.worstHouse = worstHouse;
491 en.next = en.next.tryAddNumberNode(reason);
492 }
493 }
494 }
495 }
496 return en;
497 }
498
499 /**
500 * Split an interval to remove overlaps
501 * 1) detect the optimal split position
502 * 2) calculate the new intervals
503 * @return
504 */
505 private ExtNumbers splitInterval(){
506 if (log.isDebugEnabled())
507 log.debug("trying to split",this,"so that",badNum,"is not contained");
508 boolean doSplit = false;
509 Numbers origNumbers = getNumbers();
510 if (origNumbers.countMatches(badNum) == 0){
511 if (log.isDebugEnabled())
512 log.debug("badNum",badNum,"is not contained in",this);
513 return this;
514 }
515 // create an test interval to find out which side contains the bad number
516 Numbers testNumbers = new Numbers();
517 testNumbers.setNumbers(Numbers.LEFT, origNumbers.getLeftNumberStyle(), origNumbers.getLeftStart(), origNumbers.getLeftEnd());
518 boolean left = (testNumbers.countMatches(badNum) > 0);
519
520 List<HousenumberMatch> before = new ArrayList<>();
521 List<HousenumberMatch> after = new ArrayList<>();
522 List<HousenumberMatch> toSplit = getHouses(left);
523 boolean inc = (origNumbers.getEnd(left) > origNumbers.getStart(left));
524 BitSet segmentsBefore = new BitSet();
525 BitSet segmentsAfter = new BitSet();
526 for (HousenumberMatch house : toSplit){
527 List<HousenumberMatch> target;
528 if (house.getHousenumber() < badNum){
529 target = inc ? before : after;
530 } else if (house.getHousenumber() > badNum){
531 target = inc ? after : before;
532 } else {
533 int s = origNumbers.getStart(left);
534 target = (s == badNum) ? before : after;
535 }
536 target.add(house);
537 if (target == before){
538 segmentsBefore.set(house.getSegment());
539 } else {
540 segmentsAfter.set(house.getSegment());
541 }
542 }
543 if (before.isEmpty() || after.isEmpty())
544 return this;
545 if (log.isDebugEnabled())
546 log.debug("todo: find best method to separate",before,"and",after);
547 HousenumberMatch house1 = before.get(before.size() - 1);
548 HousenumberMatch house2 = after.get(0);
549 List<HousenumberMatch> testOrder = new ArrayList<>();
550 testOrder.add(house1);
551 testOrder.add(house2);
552 Collections.sort(testOrder, new HousenumberMatchByPosComparator());
553 if (testOrder.get(0) != house1){
554 log.info("order indicates random case or missing road!",this);
555 housenumberRoad.setRandom(true);
556 }
557 int splitSegment = -1;
558 if (house1.getSegment() != house2.getSegment()){
559 // simple case: change point
560 if (log.isDebugEnabled())
561 log.debug("simple case: change point to number node between",house1,house2);
562 // what point is best?, use beginning of 2nd for now
563 splitSegment = house2.getSegment();
564 doSplit = true;
565
566 } else {
567 int seg = house1.getSegment();
568 Coord c1 = getRoad().getPoints().get(seg);
569 Coord c2 = getRoad().getPoints().get(seg + 1);
570 double segmentLength = c1.distance(c2);
571
572 Coord toAdd = null;
573 boolean addOK = true;
574 double wantedFraction = (house1.getSegmentFrac() + house2.getSegmentFrac()) / 2;
575 // handle cases where perpendicular is not on the road
576 if (wantedFraction <= 0){
577 wantedFraction = 0;
578 toAdd = new Coord(c1);
579 } else if (wantedFraction >= 1){
580 wantedFraction = 1;
581 toAdd = new Coord(c2);
582 }
583 double usedFraction = wantedFraction;
584
585 if (toAdd == null) {
586 Coord wanted = c1.makeBetweenPoint(c2, wantedFraction);
587
588 toAdd = rasterLineNearPoint(c1, c2, wanted, true);
589 if (toAdd != null){
590 if (toAdd.equals(c1)){
591 toAdd = new Coord(c1);
592 usedFraction = 0.0;
593 }
594 else if (toAdd.equals(c2)){
595 toAdd = new Coord(c2);
596 usedFraction = 0;
597 }
598 else {
599 addOK = checkLineDistortion(c1, c2, toAdd);
600 if (addOK)
601 usedFraction = HousenumberGenerator.getFrac(c1, c2, toAdd);
602 else
603 toAdd = null;
604 }
605 }
606 }
607 if (toAdd == null){
608 double len1 = wantedFraction * segmentLength;
609 double len2 = (1 - wantedFraction) * segmentLength;
610 if (Math.min(len1, len2) < MAX_LOCATE_ERROR){
611 if (len1 < len2){
612 toAdd = new Coord(c1);
613 usedFraction = 0.0;
614 } else {
615 toAdd = new Coord(c2);
616 usedFraction = 1.0;
617 }
618 }
619 }
620 if (toAdd == null){
621 log.error("internal error, cannot split",this);
622 }
623 if (toAdd != null){
624 if (log.isDebugEnabled())
625 log.debug("solution: split segment with length",formatLen(segmentLength),"at",formatLen(usedFraction * segmentLength));
626 doSplit = true;
627 splitSegment = seg+1;
628 addAsNumberNode(splitSegment, toAdd);
629 this.endInRoad++;
630 for (HousenumberMatch house : before){
631 if (house.getSegment() >= seg){
632 HousenumberGenerator.findClosestRoadSegment(house, getRoad(), seg, splitSegment);
633 }
634 }
635 for (HousenumberMatch house : after){
636 if (house.getSegment() < splitSegment)
637 HousenumberGenerator.findClosestRoadSegment(house, getRoad(), splitSegment, splitSegment + 1);
638 else
639 house.setSegment(house.getSegment()+1);
640 }
641 recalcHousePositions(getHouses(!left)); // the other side
642 }
643 }
644 if (doSplit){
645 ExtNumbers en1 = split(splitSegment);
646 ExtNumbers en2 = en1.next;
647 if (en1.getHouses(Numbers.LEFT).size() + en2.getHouses(Numbers.LEFT).size() != getHouses(Numbers.LEFT).size() ||
648 en1.getHouses(Numbers.RIGHT).size() + en2.getHouses(Numbers.RIGHT).size() != getHouses(Numbers.RIGHT).size()){
649 log.error("internal error, lost houses");
650 }
651 log.info("number node added in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());
652 return en1;
653 }
654 return this;
655 }
656
657 private boolean checkLineDistortion(Coord c1, Coord c2, Coord toAdd){
658 double distToLine = toAdd.getDisplayedCoord().distToLineSegment(c1.getDisplayedCoord(), c2.getDisplayedCoord());
659 if (distToLine > 0.2){
660 double angle = Utils.getDisplayedAngle(c1, toAdd, c2);
661 if (Math.abs(angle) > 3){
662 return false;
663 }
664 }
665 return true;
666 }
667
668 /**
669 * Try to add a number node.
670 * We may change an existing point to a number node or add a new
671 * number node. A new node might be between the existing ones
672 * or a duplicate of one of them.
673 * @return
674 */
675 private ExtNumbers tryAddNumberNode(int reason) {
676 String action;
677 if (endInRoad - startInRoad > 1)
678 action = "change";
679 else {
680 if (getRoad().getPoints().size() + 1 > LineSplitterFilter.MAX_POINTS_IN_LINE){
681 log.warn("can't change intervals, road has already",LineSplitterFilter.MAX_POINTS_IN_LINE,"points");
682 return this; // can't add a node
683 }
684 Coord c1 = getRoad().getPoints().get(startInRoad);
685 Coord c2 = getRoad().getPoints().get(startInRoad+1);
686 if (c1.equals(c2)){
687 return dupNode(0, SR_FIX_ERROR);
688 }
689 double segmentLength = c1.distance(c2);
690 int countAfterEnd = 0, countBeforeStart = 0;
691 double minFraction0To1 = 2;
692 double maxFraction0To1 = -1;
693 for (int side = 0; side < 2; side++){
694 boolean left = side == 0;
695 List<HousenumberMatch> houses = getHouses(left);
696 for (HousenumberMatch house : houses){
697 if (house.getSegmentFrac() < 0)
698 ++countBeforeStart;
699 else if (house.getSegmentFrac() > 1)
700 ++countAfterEnd;
701 else {
702 if (minFraction0To1 > house.getSegmentFrac())
703 minFraction0To1 = house.getSegmentFrac();
704 if (maxFraction0To1 < house.getSegmentFrac())
705 maxFraction0To1 = house.getSegmentFrac();
706 }
707 }
708 }
709 // special cases: perpendicular not on the road
710 if (countBeforeStart > 0){
711 return dupNode(0, SR_SPLIT_ROAD_END);
712 }
713 if (countAfterEnd > 0){
714 return dupNode(1, SR_SPLIT_ROAD_END);
715 }
716
717 // try to find a good split point depending on the split reason
718 double wantedFraction, midFraction;
719 wantedFraction = midFraction = (minFraction0To1 + maxFraction0To1) / 2;
720 Coord toAdd = null;
721 double len1 = segmentLength * minFraction0To1; // dist to first
722 double len2 = segmentLength * maxFraction0To1;
723 double len3 = (1-maxFraction0To1) * segmentLength;
724 double expectedError = c1.getDisplayedCoord().distance(new Coord(c1.getLatitude()+1,c1.getLongitude()));
725 double maxDistBefore = expectedError;
726 double maxDistAfter = expectedError;
727 if (reason == SR_FIX_ERROR && worstHouse != null){
728 wantedFraction = worstHouse.getSegmentFrac();
729 if (wantedFraction < minFraction0To1 || wantedFraction > maxFraction0To1){
730 log.error("internal error, worst house not found",this,worstHouse);
731 }
732 }
733 boolean allowSplitBetween = true;
734 boolean forceEmpty = false;
735 if (reason == SR_OPT_LEN){
736 if (log.isDebugEnabled()){
737 if (maxFraction0To1 != minFraction0To1){
738 log.debug("trying to find good split point, houses are between",formatLen(len1),"and",formatLen(len2),"in segment with",formatLen(segmentLength));
739 } else
740 log.debug("trying to find good split point, houses are at",formatLen(len1),"in segment with",formatLen(segmentLength));
741 }
742 if (len2 - len1 < 10 && getHouses(Numbers.LEFT).size() <= 1 && getHouses(Numbers.RIGHT).size() <= 1){
743 // one house or two opposite houses
744 // we try to split so that the house(s) are near the middle of one part
745 wantedFraction = wantedFraction * 2 - (wantedFraction > 0.5 ? 1 : 0);
746 allowSplitBetween = false;
747 } else {
748 if (len1 > MAX_LOCATE_ERROR / 2){
749 // create empty segment at start
750 wantedFraction = minFraction0To1 * 0.999;
751 forceEmpty = true;
752 }
753 if (len3 > MAX_LOCATE_ERROR / 2 && len3 > len1){
754 // create empty segment at end
755 wantedFraction = maxFraction0To1 * 1.001;
756 forceEmpty = true;
757 }
758 }
759 }
760 double partLen = wantedFraction * segmentLength ;
761 double shorterLen = Math.min(partLen , segmentLength - partLen);
762 if (shorterLen < 10){
763 if (reason == SR_FIX_ERROR && minFraction0To1 == maxFraction0To1)
764 return dupNode(midFraction, SR_FIX_ERROR);
765 double splitFrac = len1 < len3 ? minFraction0To1 : maxFraction0To1;
766 return dupNode(splitFrac, SR_OPT_LEN);
767 }
768 double usedFraction = 0;
769 double bestDist = Double.MAX_VALUE;
770 if (wantedFraction < minFraction0To1){
771 maxDistAfter = 0;
772 }
773 if (wantedFraction > maxFraction0To1){
774 maxDistBefore = 0;
775 }
776 for (;;){
777 Coord wanted = c1.makeBetweenPoint(c2, wantedFraction);
778 Map<Double, List<Coord>> candidates = rasterLineNearPoint2(c1, c2, wanted, maxDistBefore, maxDistAfter);
779 boolean foundGood = false;
780 for (Entry<Double, List<Coord>> entry : candidates.entrySet()){
781 if (foundGood)
782 break;
783 bestDist = entry.getKey();
784 for (Coord candidate: entry.getValue()){
785 toAdd = candidate;
786 usedFraction = HousenumberGenerator.getFrac(c1, c2, toAdd);
787 if (usedFraction <= 0 || usedFraction >= 1)
788 toAdd = null;
789 else if (usedFraction > minFraction0To1 && wantedFraction < minFraction0To1 || usedFraction < maxFraction0To1 && wantedFraction > maxFraction0To1){
790 toAdd = null;
791 } else if (allowSplitBetween == false && usedFraction > minFraction0To1 && usedFraction < maxFraction0To1){
792 toAdd = null;
793 } else {
794 if (bestDist > 0.2){
795 double angle = Utils.getDisplayedAngle(c1, toAdd, c2);
796 if (Math.abs(angle) > 3){
797 toAdd = null;
798 continue;
799 }
800 }
801 foundGood = true;
802 break;
803 }
804 }
805 }
806 if (foundGood){
807 break;
808 }
809 toAdd = null;
810 boolean tryAgain = false;
811 if (maxDistBefore > 0 && maxDistBefore < segmentLength * wantedFraction) {
812 maxDistBefore *= 2;
813 tryAgain = true;
814 }
815 if (maxDistAfter > 0 && maxDistAfter < segmentLength * (1 - wantedFraction)) {
816 maxDistAfter *= 2;
817 tryAgain = true;
818 }
819 if (!tryAgain)
820 break;
821 }
822
823 boolean addOK = true;
824 if (toAdd == null)
825 addOK = false;
826 else {
827 toAdd.incHighwayCount();
828 if (log.isDebugEnabled()){
829 log.debug("spliting road segment",startInRoad,"at",formatLen(usedFraction * segmentLength));
830 }
831 }
832 if (!addOK){
833 if (reason == SR_FIX_ERROR && minFraction0To1 == maxFraction0To1)
834 return dupNode(midFraction, SR_FIX_ERROR);
835 if (Math.min(len1, len3) < MAX_LOCATE_ERROR ){
836 double splitFrac = -1;
837 if (reason == SR_OPT_LEN){
838 if (wantedFraction <= minFraction0To1)
839 splitFrac = minFraction0To1;
840 else if (wantedFraction >= maxFraction0To1)
841 splitFrac = maxFraction0To1;
842 if (splitFrac <= 0.5 && len1 >= MAX_LOCATE_ERROR || splitFrac > 0.5 && len3 >= MAX_LOCATE_ERROR){
843 splitFrac = -1;
844 }
845 }
846 if (splitFrac < 0)
847 splitFrac = (minFraction0To1 != maxFraction0To1) ? midFraction : minFraction0To1;
848 return dupNode(splitFrac, SR_OPT_LEN);
849 }
850 if(reason == SR_FIX_ERROR)
851 log.warn("can't fix error in interval",this);
852 else if (log.isDebugEnabled())
853 log.debug("can't improve search result",this);
854 return this;
855 }
856 if (log.isInfoEnabled())
857 log.info("adding number node at",toAdd.toDegreeString(),"to split, dist to line is",formatLen(bestDist));
858 action = "add";
859 this.endInRoad = addAsNumberNode(startInRoad + 1, toAdd);
860 int forcedSegment = - 1;
861 if (forceEmpty){
862 if (wantedFraction < minFraction0To1)
863 forcedSegment = startInRoad + 1;
864 else if (wantedFraction > maxFraction0To1)
865 forcedSegment = startInRoad;
866 }
867 if (forcedSegment >= 0){
868 setSegment(forcedSegment, getHouses(Numbers.LEFT));
869 setSegment(forcedSegment, getHouses(Numbers.RIGHT));
870 } else {
871 this.recalcHousePositions(getHouses(Numbers.LEFT));
872 this.recalcHousePositions(getHouses(Numbers.RIGHT));
873 }
874 }
875 int splitSegment = (startInRoad + endInRoad) / 2;
876 if (worstHouse != null){
877 if (worstHouse.getSegment() == startInRoad)
878 splitSegment = startInRoad + 1;
879 else if (worstHouse.getSegment() == endInRoad - 1)
880 splitSegment = worstHouse.getSegment();
881 } else if (endInRoad - startInRoad > 2){
882 int firstSegWithHouses = endInRoad;
883 int lastSegWithHouses = -1;
884 for (int side = 0; side < 2; side++){
885 boolean left = side == 0;
886 List<HousenumberMatch> houses = getHouses(left);
887 for (HousenumberMatch house : houses){
888 int s = house.getSegment();
889 if (s < firstSegWithHouses)
890 firstSegWithHouses = s;
891 if (s > lastSegWithHouses)
892 lastSegWithHouses = s;
893 }
894
895 }
896 splitSegment = (firstSegWithHouses + lastSegWithHouses) / 2;
897 if (splitSegment == startInRoad)
898 splitSegment++;
899 }
900 ExtNumbers en1 = split(splitSegment);
901 ExtNumbers en2 = en1.next;
902 if (reason == SR_OPT_LEN){
903 // TODO: fill gaps, e.g. if split results in O,1,9 -> O,1,1 + O,9,9 ?
904 }
905 if ("add".equals(action))
906 log.info("number node added in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());
907 else
908 log.info("point changed to number node in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());
909 return en1;
910 }
911
912
913 /**
914 * Duplicate a node in the road. This creates a zero-length segment.
915 * We can add house numbers to this segment and the position in the
916 * address search will be the same for all of them.
917 * @param fraction a value below 0.5 means duplicate the start node, others
918 * duplicate the end node
919 * @return the new chain with two segments
920 */
921 private ExtNumbers dupNode(double fraction, int reason) {
922 log.info("duplicating number node in road",getRoad(),getNumbers(),getHouses(Numbers.LEFT),getHouses(Numbers.RIGHT));
923 boolean atStart = (fraction <= 0.5);
924
925 // add a copy of an existing node
926 int index = (atStart) ? startInRoad : endInRoad;
927 int splitSegment = (atStart) ? startInRoad + 1: endInRoad;
928 Coord closePoint = getRoad().getPoints().get(index);
929 Coord toAdd = new Coord(closePoint);
930 toAdd.setOnBoundary(closePoint.getOnBoundary());
931 toAdd.incHighwayCount();
932 // we have to make sure that the road starts and ends with a CoordNode!
933 this.endInRoad = addAsNumberNode(splitSegment, toAdd);
934
935 // distribute the houses to the new intervals
936 List<ArrayList<HousenumberMatch>> leftTargets = Arrays.asList(new ArrayList<HousenumberMatch>(),new ArrayList<HousenumberMatch>());
937 List<ArrayList<HousenumberMatch>> rightTargets = Arrays.asList(new ArrayList<HousenumberMatch>(),new ArrayList<HousenumberMatch>());
938 int target;
939 if (reason == SR_SPLIT_ROAD_END || reason == SR_OPT_LEN){
940 for (int side = 0; side < 2; side++){
941 boolean left = side == 0;
942 List<ArrayList<HousenumberMatch>> targets = left ? leftTargets : rightTargets;
943 for (HousenumberMatch house : getHouses(left)){
944 if (house.getSegmentFrac() < fraction)
945 target = 0;
946 else if (house.getSegmentFrac() > fraction)
947 target = 1;
948 else target = (atStart) ? 0 : 1;
949 targets.get(target).add(house);
950 }
951 }
952 } else if (getHouses(Numbers.LEFT).size() > 1 || getHouses(Numbers.RIGHT).size() > 1){
953 int start,end;
954 for (int side = 0; side < 2; side++){
955 boolean left = side == 0;
956 if (getHouses(left).isEmpty())
957 continue;
958 start = getNumbers().getStart(left);
959 end = getNumbers().getEnd(left);
960 List<ArrayList<HousenumberMatch>> targets = left ? leftTargets : rightTargets;
961 if (start != end){
962 int midNum = (start + end) / 2;
963 for (HousenumberMatch house : getHouses(left)){
964 if (house.getHousenumber() < midNum)
965 target = 0;
966 else if (house.getHousenumber() > midNum)
967 target = 1;
968 else target = (atStart) ? 0 : 1;
969 targets.get(target).add(house);
970 }
971 } else if (multipleZipOrCity(left) == false){
972 if (atStart)
973 targets.get(1).addAll(getHouses(left));
974 else
975 targets.get(0).addAll(getHouses(left));
976 } else {
977 int mid = getHouses(left).size() / 2;
978 targets.get(0).addAll(getHouses(left).subList(0, mid));
979 targets.get(1).addAll(getHouses(left).subList(mid,getHouses(left).size()));
980 }
981
982 }
983 } else {
984 log.error("internal error, don't know how to split", this);
985 }
986
987 assert splitSegment != startInRoad && splitSegment != endInRoad;
988 // make sure that the numbers are assigned to the wanted segment
989 setSegment(startInRoad, leftTargets.get(0));
990 setSegment(startInRoad, rightTargets.get(0));
991 setSegment(splitSegment, leftTargets.get(1));
992 setSegment(splitSegment, rightTargets.get(1));
993
994 // don't use split() here, the numbers in this may not be properly
995 // sorted and we don't want to sort them
996 ExtNumbers en1 = divide();
997 ExtNumbers en2 = en1.next;
998 en1.setNumbers(leftTargets.get(0), startInRoad, splitSegment, true);
999 en1.setNumbers(rightTargets.get(0), startInRoad, splitSegment, false);
1000 en2.setNumbers(leftTargets.get(1), splitSegment, endInRoad, true);
1001 en2.setNumbers(rightTargets.get(1), splitSegment, endInRoad, false);
1002
1003 log.info("zero length interval added in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());
1004 if (atStart && !en1.hasNumbers() || !atStart && !en2.hasNumbers()){
1005 log.error("internal error, zero length interval has no numbers in road",getRoad());
1006 }
1007 return en1;
1008
1009 }
1010
1011 private void setSegment(int segment, List<HousenumberMatch> houses) {
1012 for (HousenumberMatch house : houses){
1013 HousenumberGenerator.findClosestRoadSegment(house, getRoad(), segment,segment+1);
1014 if (house.getRoad() == null || house.getSegment() != segment){
1015 // should not happen
1016 log.error("internal error, house too far from forced segment in road",getRoad(),house,house.getElement().toBrowseURL());
1017 house.setIgnored(true);
1018 }
1019 }
1020
1021 }
1022
1023 /**
1024 * This should be called if a point was added to the road segment
1025 * covered by this interval. We have to recalculate the segment numbers
1026 * and fraction values.
1027 */
1028 private void recalcHousePositions(List<HousenumberMatch> houses){
1029 for (HousenumberMatch house : houses){
1030 HousenumberGenerator.findClosestRoadSegment(house, getRoad(), startInRoad, endInRoad);
1031 }
1032 if (houses.size() > 1)
1033 Collections.sort(houses, new HousenumberMatchByPosComparator());
1034 }
1035
1036
1037 /**
1038 * Create two empty intervals which will replace this.
1039 * @return
1040 */
1041 private ExtNumbers divide(){
1042 ExtNumbers en1 = new ExtNumbers(housenumberRoad);
1043 ExtNumbers en2 = new ExtNumbers(housenumberRoad);
1044 // maintain the linked list
1045 en1.prev = this.prev;
1046 if (prev != null)
1047 prev.next = en1;
1048 en1.next = en2;
1049 en2.prev = en1;
1050 en2.next = this.next;
1051 if (this.next != null)
1052 next.prev = en2;
1053 en1.setNodeIndex(nodeIndex);
1054 en2.setNodeIndex(nodeIndex+1);
1055 ExtNumbers work = en2.next;
1056 while (work != null){
1057 work.setNodeIndex(work.nodeIndex + 1);
1058 work = work.next;
1059 }
1060 return en1;
1061 }
1062
1063 /**
1064 * Add node to the road.
1065 * Maintain the positions of houses in the following intervals.
1066 * @param toAdd
1067 * @param pos
1068 * @return new start of next interval
1069 */
1070 private int addAsNumberNode(int pos, Coord toAdd){
1071 toAdd.setNumberNode(true);
1072 getRoad().getPoints().add(pos, toAdd);
1073
1074 ExtNumbers work = next;
1075 while (work != null){
1076 work.increaseNodeIndexes(startInRoad);
1077 work = work.next;
1078 }
1079 return endInRoad + 1;
1080 }
1081
1082 /**
1083 * This has to be called when a point is added to the road.
1084 * @param startPos
1085 */
1086 private void increaseNodeIndexes(int startPos){
1087 if (hasNumbers() == false)
1088 return;
1089 if (startInRoad > startPos){
1090 startInRoad++;
1091 endInRoad++;
1092 }
1093 for (int side = 0; side < 2; side++){
1094 boolean left = side == 0;
1095 for (HousenumberMatch house : getHouses(left)){
1096 int s = house.getSegment();
1097 if (s > startPos)
1098 house.setSegment(s+1);
1099 else
1100 assert false : "internal error " + getRoad() + " " + getHouses(Numbers.LEFT) + " " + getHouses(Numbers.RIGHT);
1101 }
1102 }
1103 }
1104
1105
1106 private void findGoodSplitPos(){
1107 badNum = -1;
1108 worstHouse = null;
1109 boolean multipleZipOrCity = false;
1110 for (int side = 0; side < 2; side++){
1111 boolean left = side == 0;
1112 List<HousenumberMatch> houses = getHouses(left);
1113 if (houses.size() <= 1)
1114 continue;
1115 if (multipleZipOrCity(left))
1116 multipleZipOrCity = true;
1117 for (HousenumberMatch house: houses){
1118 int hn = house.getHousenumber();
1119 if (countOccurence(houses, hn) > 1)
1120 continue;
1121 ExtNumbers modIvl = simulateRemovalOfHouseNumber(hn, left);
1122 if (modIvl.isPlausible()){
1123 badNum = hn;
1124 if (log.isDebugEnabled())
1125 log.debug("splitpos details: single remove of",badNum,"results in plausible interval");
1126 return;
1127 }
1128 }
1129 }
1130 if (multipleZipOrCity)
1131 return; // unlikely
1132 // log.debug("did not yet find good split position");
1133 Numbers ivl = getNumbers();
1134 int[] firstBad = {-1,-1};
1135 int[] lastBad = {-1,-1};
1136 for (int side = 0; side < 2; side++){
1137 boolean left = (side == 0);
1138 int step = 2;
1139 if (ivl.getNumberStyle(left) == NumberStyle.BOTH)
1140 step = 1;
1141 int s = ivl.getStart(left);
1142 int e = ivl.getEnd(left);
1143 int s2 = ivl.getStart(!left);
1144 int e2 = ivl.getEnd(!left );
1145 NumberStyle style2 = ivl.getNumberStyle(!left);
1146 for (int hn = Math.min(s, e); hn <= Math.max(s, e); hn += step){
1147 if (style2 == NumberStyle.EVEN && hn % 2 == 1 || style2 == NumberStyle.ODD && hn % 2 == 0 ){
1148 if (firstBad[side] < 0)
1149 firstBad[side] = hn;
1150 lastBad[side] = hn;
1151 continue;
1152 }
1153 if (hn < Math.min(s2, e2) || hn > Math.max(s2, e2)){
1154 if (firstBad[side] < 0)
1155 firstBad[side] = hn;
1156 lastBad[side] = hn;
1157 }
1158 }
1159 }
1160 if (firstBad[0] == lastBad[0]){
1161 badNum = firstBad[0];
1162 if (badNum >= 0)
1163 return;
1164 }
1165 if (firstBad[1] == lastBad[1]){
1166 badNum = firstBad[1];
1167 if (badNum >= 0)
1168 return;
1169 }
1170 badNum = Math.max(firstBad[0], lastBad[0]);
1171 if (badNum == -1)
1172 badNum = Math.min(firstBad[1], lastBad[1]);
1173 if (log.isDebugEnabled())
1174 log.debug("splitpos details",Arrays.toString(firstBad), Arrays.toString(lastBad),"gives badNum",badNum);
1175 }
1176
1177 private boolean multipleZipOrCity(boolean left) {
1178 RoadSide rs = left ? leftSide : rightSide;
1179 return rs == null ? false : rs.multipleCities || rs.multipleZipCodes;
1180 }
1181
1182 public ExtNumbers checkChainPlausibility(String streetName,
1183 List<HousenumberMatch> potentialNumbersThisRoad) {
1184 // we try to repair up to 10 times
1185 ExtNumbers head = this;
1186 for (int loop = 0; loop < 10; loop++){
1187 boolean anyChanges = false;
1188 for (ExtNumbers en1 = head; en1 != null; en1 = en1.next){
1189 if (anyChanges)
1190 break;
1191 if (en1.hasNumbers() == false)
1192 continue;
1193 for (ExtNumbers en2 = en1.next; en2 != null; en2 = en2.next){
1194 if (anyChanges)
1195 break;
1196 if (en2.hasNumbers() == false)
1197 continue;
1198
1199 int res = checkIntervals(streetName, en1, en2);
1200 switch (res) {
1201 case OK_NO_CHANGES:
1202 case NOT_OK_KEEP:
1203 break;
1204 case OK_AFTER_CHANGES:
1205 anyChanges = true;
1206 break;
1207 case NOT_OK_TRY_SPLIT:
1208 if (en1.needsSplit){
1209 ExtNumbers test = en1.tryChange(SR_FIX_ERROR);
1210 if (test != en1){
1211 housenumberRoad.setChanged(true);
1212 anyChanges = true;
1213 if (test.prev == null){
1214 head = test;
1215 }
1216 }
1217 }
1218 if (en2.needsSplit){
1219 ExtNumbers test = en2.tryChange(SR_FIX_ERROR);
1220 if (test != en2){
1221 anyChanges = true;
1222 housenumberRoad.setChanged(true);
1223 }
1224 }
1225 break;
1226 case NOT_OK_STOP:
1227 return head;
1228 default:
1229 break;
1230 }
1231 }
1232 }
1233 if (!anyChanges)
1234 break;
1235 }
1236 return head;
1237 }
1238
1239 public static final int OK_NO_CHANGES = 0;
1240 public static final int OK_AFTER_CHANGES = 1;
1241 public static final int NOT_OK_TRY_SPLIT = 2;
1242 public static final int NOT_OK_KEEP = 3;
1243 public static final int NOT_OK_STOP = 4;
1244
1245 /**
1246 * Check if two intervals are overlapping (all combinations of left + right)
1247 * @param streetName
1248 * @param en1
1249 * @param en2
1250 * @return true if something was changed
1251 */
1252 public static int checkIntervals(String streetName, ExtNumbers en1, ExtNumbers en2) {
1253 if (en1.getRoad() != en2.getRoad()){
1254 Coord cs1 = en1.getRoad().getPoints().get(en1.startInRoad);
1255 Coord ce1 = en1.getRoad().getPoints().get(en1.endInRoad);
1256 Coord ce2 = en2.getRoad().getPoints().get(en2.endInRoad);
1257 if (ce2 == cs1 || ce2 == ce1){
1258 ExtNumbers help = en1;
1259 en1 = en2;
1260 en2 = help;
1261 }
1262 }
1263 boolean allOK = true;
1264 Numbers ivl1 = en1.getNumbers();
1265 Numbers ivl2 = en2.getNumbers();
1266 for (int i = 0; i < 2; i++){
1267 boolean left1 = i == 0;
1268 NumberStyle style1 = ivl1.getNumberStyle(left1);
1269 if (style1 == NumberStyle.NONE)
1270 continue;
1271 int s1 = ivl1.getStart(left1);
1272 int e1 = ivl1.getEnd(left1);
1273 for (int j = 0; j < 2; j++){
1274 boolean left2 = (j == 0);
1275 NumberStyle style2 = ivl2.getNumberStyle(left2);
1276 if (style2 == NumberStyle.NONE)
1277 continue;
1278 int s2 = ivl2.getStart(left2);
1279 int e2 = ivl2.getEnd(left2);
1280 boolean ok = true;
1281 if (style1 == style2 || style1 == NumberStyle.BOTH || style2 == NumberStyle.BOTH)
1282 ok = checkIntervalBoundaries(s1, e1, s2, e2, left1 == left2 && en1.getRoad() == en2.getRoad());
1283 if (ok)
1284 continue;
1285 if (en1.getRoad() != en2.getRoad() && en1.hasGaps == false && en2.hasGaps == false){
1286 allOK = false;
1287 continue;
1288 }
1289
1290 if (s1 == e1){
1291 if (en1.getHouses(left1).get(0).isFarDuplicate()){
1292 allOK = false;
1293 continue;
1294 }
1295 }
1296 if (s2 == e2){
1297 if (en2.getHouses(left2).get(0).isFarDuplicate()){
1298 allOK = false;
1299 continue;
1300 }
1301 }
1302 List<HousenumberMatch> houses1 = en1.getHouses(left1);
1303 List<HousenumberMatch> houses2 = en2.getHouses(left2);
1304 if (log.isInfoEnabled()){
1305 log.info("detected unplausible combination of intervals in",streetName,
1306 s1 + ".." + e1, "and", s2 + ".." + e2, "houses:",
1307 (left1 ? "left:" : "right"), houses1,
1308 (left2 ? "left:" : "right"), houses2,
1309 (en1.getRoad() == en2.getRoad() ? "in road " + en1.getRoad() :
1310 "road id(s):" + en1.getRoad().getRoadDef().getId() + ", " + en2.getRoad().getRoadDef().getId()));
1311 }
1312 double smallestDelta = Double.POSITIVE_INFINITY;
1313 HousenumberMatch bestMoveOrig = null;
1314 HousenumberMatch bestMoveMod = null;
1315 ExtNumbers bestRemove = null;
1316 List<HousenumberMatch> possibleRemoves1 = new ArrayList<>();
1317 List<HousenumberMatch> possibleRemoves2 = new ArrayList<>();
1318
1319 if (en1.housenumberRoad.isRandom() == false && en2.housenumberRoad.isRandom() == false){
1320 // check if we can move a house from en1 to en2
1321 for (HousenumberMatch house : houses1){
1322 if (house.getGroup() != null)
1323 continue;
1324 int n = house.getHousenumber();
1325 if (countOccurence(houses1, n) > 1)
1326 continue;
1327
1328 if (n == s1 || n == e1) {
1329 Numbers modNumbers = en1.simulateRemovalOfHouseNumber(n, left1).getNumbers();
1330 int s1Mod = modNumbers.getStart(left1);
1331 int e1Mod = modNumbers.getEnd(left1);
1332 NumberStyle modStyle = modNumbers.getNumberStyle(left1);
1333 boolean ok2 = true;
1334 if (modStyle == style2 || modStyle == NumberStyle.BOTH || style2 == NumberStyle.BOTH)
1335 ok2 = checkIntervalBoundaries(s1Mod, e1Mod, s2, e2, left1 == left2 && en1.getRoad() == en2.getRoad());
1336 if (ok2){
1337 // the intervals don't overlap if house is removed from en1
1338 if (houses1.size() > 1)
1339 possibleRemoves1.add(house);
1340
1341 // check if it fits into en2
1342 HousenumberMatch test = checkMoveTo(house, en2, left2);
1343 if (test.getRoad() != null){
1344 double deltaDist = test.getDistance() - house.getDistance();
1345 if (deltaDist < smallestDelta){
1346 bestMoveMod = test;
1347 bestMoveOrig = house;
1348 smallestDelta = deltaDist;
1349 bestRemove = en1;
1350 }
1351 }
1352 }
1353 }
1354 }
1355 for (HousenumberMatch house : houses2){
1356 if (house.getGroup() != null)
1357 continue;
1358 int n = house.getHousenumber();
1359 if (countOccurence(houses2, n) > 1)
1360 continue;
1361
1362 if (n == s2 || n == e2) {
1363 Numbers modNumbers = en2.simulateRemovalOfHouseNumber(n, left2).getNumbers();
1364 int s2Mod = modNumbers.getStart(left2);
1365 int e2Mod = modNumbers.getEnd(left2);
1366 NumberStyle modStyle = modNumbers.getNumberStyle(left2);
1367 boolean ok2 = true;
1368 if (modStyle == style1 || modStyle == NumberStyle.BOTH || style1 == NumberStyle.BOTH)
1369 ok2 = checkIntervalBoundaries(s1, e1, s2Mod, e2Mod, left1 == left2 && en1.getRoad() == en2.getRoad());
1370 if (ok2){
1371 // the intervals don't overlap if house is removed from en2
1372 if (houses2.size() > 1)
1373 possibleRemoves2.add(house);
1374
1375
1376 // check if it fits into en1
1377 HousenumberMatch test = checkMoveTo(house, en1, left1);
1378 if (test.getRoad() != null){
1379 double deltaDist = test.getDistance() - house.getDistance();
1380 if (deltaDist < smallestDelta){
1381 bestMoveMod = test;
1382 bestMoveOrig = house;
1383 smallestDelta = deltaDist;
1384 bestRemove = en2;
1385 }
1386 }
1387 }
1388 }
1389 }
1390 if (bestMoveMod != null){
1391 if (bestMoveOrig.isDuplicate()){
1392 log.warn("duplicate number causes problems",streetName,bestMoveOrig.getSign(),bestMoveOrig.getElement().toBrowseURL() );
1393 }
1394 List<HousenumberMatch> fromHouses, toHouses;
1395 ExtNumbers from,to;
1396 if (bestRemove == en1){
1397 from = en1;
1398 to = en2;
1399 fromHouses = houses1;
1400 toHouses = houses2;
1401 bestMoveOrig.setLeft(left2);
1402
1403 } else {
1404 from = en2;
1405 to = en1;
1406 fromHouses = houses2;
1407 toHouses = houses1;
1408 bestMoveOrig.setLeft(left1);
1409 }
1410 if (bestMoveOrig.getMoved() >= 3){
1411 bestMoveMod = null;
1412 bestMoveOrig = null;
1413 bestRemove.housenumberRoad.setRandom(true);
1414 } else {
1415 if (log.isInfoEnabled()){
1416 if (to.getRoad() == from.getRoad())
1417 log.info("moving",streetName,bestMoveOrig.getSign(),bestMoveOrig.getElement().toBrowseURL(),"from",fromHouses,"to",toHouses,"in road",to.getRoad());
1418 else
1419 log.info("moving",streetName,bestMoveOrig.getSign(),bestMoveOrig.getElement().toBrowseURL(),"from",fromHouses,"in road",from.getRoad(),"to",toHouses,"in road",to.getRoad());
1420 }
1421 bestMoveOrig.incMoved();
1422 bestMoveOrig.setRoad(to.getRoad());
1423 bestMoveOrig.setHousenumberRoad(to.housenumberRoad);
1424 bestMoveOrig.setSegment(bestMoveMod.getSegment());
1425 bestMoveOrig.setDistance(bestMoveMod.getDistance());
1426 bestMoveOrig.setSegmentFrac(bestMoveMod.getSegmentFrac());
1427 from.housenumberRoad.getHouses().remove(bestMoveOrig);
1428 fromHouses.remove(bestMoveOrig);
1429 toHouses.add(bestMoveOrig);
1430 Collections.sort(toHouses, new HousenumberMatchByPosComparator());
1431 en1.reset();
1432 en2.reset();
1433 en1.setNumbers(houses1, en1.startInRoad, en1.endInRoad, left1);
1434 en2.setNumbers(houses2, en2.startInRoad, en2.endInRoad, left2);
1435 return OK_AFTER_CHANGES;
1436 }
1437 }
1438 }
1439 ExtNumbers toSplit = null;
1440 int splitNum = -1;
1441 int delta1 = Math.abs(e1-s1);
1442 int delta2 = Math.abs(e2-s2);
1443 if (delta1 > 0 && delta2 > 0){
1444 if (en1.hasGaps != en2.hasGaps){
1445 if (en1.hasGaps){
1446 if (possibleRemoves1.isEmpty() == false)
1447 splitNum = possibleRemoves1.get(0).getHousenumber();
1448 toSplit = en1;
1449 } else {
1450 if (possibleRemoves2.isEmpty() == false)
1451 splitNum = possibleRemoves2.get(0).getHousenumber();
1452 toSplit = en2;
1453 }
1454 } else if (possibleRemoves1.size() == 1){
1455 splitNum = possibleRemoves1.get(0).getHousenumber();
1456 toSplit = en1;
1457 } else if (possibleRemoves2.size() == 1){
1458 splitNum = possibleRemoves2.get(0).getHousenumber();
1459 toSplit = en2;
1460 } else if (possibleRemoves1.size() > 0){
1461 splitNum = possibleRemoves1.get(0).getHousenumber();
1462 toSplit = en1;
1463 } else if (possibleRemoves2.size() > 0){
1464 splitNum = possibleRemoves2.get(0).getHousenumber();
1465 toSplit = en2;
1466 } else {
1467 // intervals are overlapping, a single remove doesn't help
1468 if (ivl1.isContained(s2, left1) && ivl1.isContained(e2, left1)){
1469 // en2 is completely in en1
1470 toSplit = en1;
1471 splitNum = s2;
1472 } else if (ivl2.isContained(s1, left2) && ivl2.isContained(e1, left2)){
1473 // en1 is completely in en2
1474 toSplit = en2;
1475 splitNum = s1;
1476 }
1477 else {
1478 if (ivl1.isContained(s2, left1)){
1479 toSplit = en1;
1480 splitNum = s2;
1481 } else if (ivl1.isContained(e2, left1)){
1482 toSplit = en1;
1483 splitNum = e2;
1484 } else if (ivl2.isContained(s1, left2)){
1485 toSplit = en2;
1486 splitNum = s1;
1487 } else if (ivl2.isContained(e1, left2)){
1488 toSplit = en2;
1489 splitNum = e1;
1490 } else if (style1 == NumberStyle.BOTH){
1491 toSplit = en1;
1492 } else if (style2 == NumberStyle.BOTH){
1493 toSplit = en2;
1494 } else {
1495 toSplit = (delta1 >= delta2) ? en1 : en2;
1496 }
1497 }
1498 }
1499 }
1500 else if (delta1 == 0 && delta2 > 0 && countOccurence(houses2, s1) == 0){
1501 toSplit = en2;
1502 splitNum = s1;
1503 }
1504 else if (delta2 == 0 && delta1 > 0 && countOccurence(houses1, s2) == 0){
1505 toSplit = en1;
1506 splitNum = s2;
1507 }
1508 if (toSplit != null){
1509 toSplit.worstHouse = null;
1510 toSplit.badNum = splitNum;
1511 toSplit.setNeedsSplit(true);
1512 return NOT_OK_TRY_SPLIT;
1513
1514 }
1515 allOK = false;
1516 }
1517 }
1518 if (allOK)
1519 return OK_NO_CHANGES;
1520 return NOT_OK_KEEP;
1521 }
1522
1523
1524 private static HousenumberMatch checkMoveTo(HousenumberMatch house, ExtNumbers other, boolean otherLeft){
1525 HousenumberMatch test = new HousenumberMatch(house);
1526 Numbers otherIvl = other.getNumbers();
1527 int oStart = otherIvl.getStart(otherLeft);
1528 int oEnd = otherIvl.getEnd(otherLeft);
1529 // check if it fits into en2
1530 if (house.getHousenumber() <= Math.min(oStart, oEnd) ||house.getHousenumber() >= Math.max(oStart, oEnd))
1531 return test;
1532 boolean even = house.getHousenumber() % 2 == 0;
1533 NumberStyle oStyle = otherIvl.getNumberStyle(otherLeft);
1534 if (oStyle == NumberStyle.EVEN && !even || oStyle == NumberStyle.ODD && even)
1535 return test;
1536 HousenumberGenerator.findClosestRoadSegment(test, other.getRoad(), other.startInRoad, other.endInRoad);
1537
1538 if (test.getDistance() <= HousenumberGenerator.MAX_DISTANCE_TO_ROAD){
1539 Coord c1 = other.getRoad().getPoints().get(test.getSegment());
1540 Coord c2 = other.getRoad().getPoints().get(test.getSegment() + 1);
1541 if (c1.highPrecEquals(c2) || otherLeft == HousenumberGenerator.isLeft(c1, c2, house.getLocation())){
1542 test.setLeft(otherLeft);
1543 return test;
1544 }
1545 }
1546 test.setRoad(null);
1547 return test;
1548 }
1549
1550 /**
1551 * Check the start and end values of two consecutive number intervals
1552 * for plausibility.
1553 * @param s1 1st interval start
1554 * @param e1 1st interval end
1555 * @param s2 2nd interval start
1556 * @param e2 2nd interval end
1557 * @return
1558 */
1559 private static boolean checkIntervalBoundaries(int s1, int e1, int s2,int e2, boolean sameSide){
1560 boolean ok = false;
1561 // many cases, maybe someone finds simpler code?
1562 if (sameSide){
1563 // allow equal numbers at boundaries
1564 if (s1 == e1) {
1565 if (e1 == s2) ok = true; // 6 6 6 4 , 6 6 6 6 , 6 6 6 8
1566 else if (e1 < s2 && e1 < e2) ok = true; // 4 4 8 6, 4 4 6 8 1st equal, not in higher 2nd
1567 else if (e1 > s2 && e1 > e2) ok = true; // 6 6 4 2, 6 6 2 4 1st equal, not in lower 2nd
1568 } else if (s1 < e1){
1569 if (e1 <= s2 && s2 <= e2) ok = true; // 6 8 8 8, 6 8 8 10, 6 8 10 10, 6 8 10 12 up
1570 else if (s2 > e2 && e1 < e2) ok = true; // 6 8 12 10 up down, no overlap, 2nd is higher
1571 else if (s1 > s2 && s1 > e2) ok = true; // 6 8 4 4, 6 8 4 2, 6 8 2 4 up down, 2nd is lower
1572 } else { // s1 > e1
1573 if (e1 >= s2 && s2 >= e2) ok = true; // 8 6 6 6, 8 6 6 4, 8 6 4 4, 8 6 4 2 down
1574 else if (e1 > s2 && e1 > e2) ok = true; // 8 6 2 4 down up, no overlap,2nd is lower
1575 else if (s1 < s2 && s1 < e2) ok = true; // 8 6 10 10, 8 6 10 12, 8 6 12 10, 1st down, no overlap,2nd is higher
1576 }
1577 } else {
1578 // left and right: don't allow equal numbers in different intervals
1579 if (s1 == e1) {
1580 if (s2 == e2 && s1 != s2) ok = true; // 6 6 2 2, 6 6 8 8
1581 else if (s2 < e2 && (s1 < s2 || s1 > e2)) ok = true; // 6 6 2 4 , 6 6 8 10
1582 else if (s2 > e2 && (s1 > s2 || s1 < e2)) ok = true; // 6 6 4 2, 6 6 10 8
1583 } else if (s1 < e1){
1584 if (e1 < s2 && s2 <= e2) ok = true; // 6 8 10 10, 6 8 10 12 up
1585 else if (s2 > e2 && e1 < e2) ok = true; // 6 8 12 10 up down, no overlap, 2nd is higher
1586 else if (s1 > s2 && s1 > e2) ok = true; // 6 8 4 4, 6 8 4 2, 6 8 2 4 up down, 2nd is lower
1587 } else { // s1 > e1
1588 if (e1 > s2 && s2 >= e2) ok = true; // 8 6 4 4, 8 6 4 2 down
1589 else if (e1 > s2 && e1 > e2) ok = true; // 8 6 2 4 down up, no overlap,2nd is lower
1590 else if (s1 < s2 && s1 < e2) ok = true; // 8 6 10 10, 8 6 10 12, 8 6 12 10, 1st down, no overlap,2nd is higher
1591 }
1592 }
1593 if (!ok){
1594 // log.error("interval check not ok: ", s1,e1,s2,e2,"same side:",sameSide);
1595 }
1596 return ok;
1597 }
1598
1599 private ExtNumbers simulateRemovalOfHouseNumber(int hn, boolean left){
1600 ExtNumbers help = new ExtNumbers(housenumberRoad);
1601 help.prev = prev;
1602 help.next = next;
1603
1604 List<HousenumberMatch> modifiedHouses = new ArrayList<>(getHouses(left));
1605 Iterator<HousenumberMatch> iter = modifiedHouses.iterator();
1606 while (iter.hasNext()){
1607 HousenumberMatch house = iter.next();
1608 if (house.getHousenumber() == hn)
1609 iter.remove();
1610 }
1611 help.setNumbers(modifiedHouses, startInRoad, endInRoad, left);
1612 help.setNumbers(getHouses(!left), startInRoad, endInRoad, !left);
1613 return help;
1614 }
1615
1616 public boolean hasNumbers(){
1617 return getNumbers().isEmpty() == false;
1618 }
1619
1620 /**
1621 * Try to add node(s) to decrease the distance of the calculated
1622 * position of an address.
1623 * @return
1624 */
1625 public ExtNumbers splitLargeGaps(){
1626 if (hasNumbers() == false)
1627 return this;
1628 // calculate the length of each road segment and
1629 // the overall length covered by this interval
1630 int numSegments = endInRoad - startInRoad;
1631 double[] segmentLenghts = new double[numSegments];
1632 double fullLength = 0;
1633 for (int i = startInRoad; i < endInRoad; i++){
1634 Coord c1 = getRoad().getPoints().get(i);
1635 Coord c2 = getRoad().getPoints().get(i+1);
1636 double len = c1.distance(c2);
1637 segmentLenghts[i-startInRoad] = len;
1638 fullLength += len;
1639 }
1640 if (fullLength < MAX_LOCATE_ERROR){
1641 if (log.isDebugEnabled())
1642 log.debug("segment",this.getNumbers(), "with length",formatLen(fullLength),"is considered OK");
1643
1644 return this;
1645 }
1646 TreeMap<Integer, Double> searchPositions = new TreeMap<>();
1647 boolean ok = calcSearchPositions(fullLength, searchPositions);
1648
1649 if (!ok)
1650 return this;
1651
1652 double worstDelta = 0;
1653 worstHouse = null;
1654 for (int side = 0; side < 2; side++){
1655 boolean left = side == 0;
1656 List<HousenumberMatch> houses = getHouses(left);
1657 for (HousenumberMatch house : houses){
1658 double distToStart = 0;
1659 for (int k = startInRoad; k < house.getSegment(); k++)
1660 distToStart += segmentLenghts[k-startInRoad];
1661 if (house.getSegmentFrac() > 0){
1662 try {
1663 distToStart += Math.min(1, house.getSegmentFrac()) * segmentLenghts[house.getSegment() - startInRoad];
1664 } catch (Exception e) {
1665 log.error(e);
1666 }
1667
1668 }
1669 Double searchDist = searchPositions.get(house.getHousenumber());
1670 if (searchDist == null){
1671 log.warn("can't compute address search result of",house);
1672 } else {
1673 double delta = distToStart - searchDist;
1674 house.setSearchDist(delta);
1675 if (Math.abs(delta) > worstDelta){
1676 worstDelta = Math.abs(delta);
1677 worstHouse = house;
1678 }
1679 }
1680 }
1681 }
1682 if (worstDelta > MAX_LOCATE_ERROR){
1683 if (log.isInfoEnabled())
1684 log.info("trying to optimize address search for house number in road",getRoad(),worstHouse,"error before opt is",formatLen(worstDelta));
1685 return tryChange(SR_OPT_LEN);
1686 }
1687 if (log.isDebugEnabled())
1688 log.debug("segment",this.getNumbers(), "with length",formatLen(fullLength),"is OK, worst address search for house number in road",getRoad(),worstHouse,"error is",formatLen(worstDelta));
1689 return this;
1690 }
1691
1692 /**
1693 * Try to simulate a Garmin address search for each number covered by the interval.
1694 * @param fullLength
1695 * @param searchPositions filled by this routine
1696 * @return false if calculation failed
1697 */
1698 private boolean calcSearchPositions(double fullLength, TreeMap<Integer, Double> searchPositions){
1699 Numbers ivl = getNumbers();
1700 for (int side = 0; side < 2; side++){
1701 boolean left = side == 0;
1702 NumberStyle style = ivl.getNumberStyle(left);
1703 if (style != NumberStyle.NONE){
1704 int start = ivl.getStart(left);
1705 int end = ivl.getEnd(left);
1706 int step = style == NumberStyle.BOTH ? 1 : 2;
1707 if (step != 1 && start % 2 != end % 2){
1708 log.error("internal error, bad interval in optimization",this);
1709 return false;
1710 }
1711 if (start == end){
1712 searchPositions.put(start, fullLength / 2);
1713 } else {
1714 int parts = Math.abs(end - start) / step;
1715 double partLen = fullLength / parts;
1716 if (start > end)
1717 step = -step;
1718 int hn = start;
1719 double dist = 0;
1720 while (true) {
1721 searchPositions.put(hn, dist);
1722 if (hn == end)
1723 break;
1724 dist += partLen;
1725 hn += step;
1726 }
1727 if (parts > 1)
1728 assert Math.abs(fullLength - dist) < 0.1;
1729 }
1730 }
1731 }
1732 return true;
1733 }
1734
1735 /**
1736 * Use Bresenham algorithm to get the Garmin points which are close to the line
1737 * described by c1 and c2 and the point p.
1738 * @param c1
1739 * @param c2
1740 * @param p
1741 * @return point with smallest perpendicular distance to line
1742 */
1743 public static Coord rasterLineNearPoint(Coord c1, Coord c2, Coord p, boolean includeEndPoints){
1744 int x0 = c1.getLongitude();
1745 int y0 = c1.getLatitude();
1746 int x1 = c2.getLongitude();
1747 int y1 = c2.getLatitude();
1748 Coord c1Dspl = c1.getDisplayedCoord();
1749 Coord c2Dspl = c2.getDisplayedCoord();
1750 int x = x0, y = y0;
1751 int dx = Math.abs(x1-x), sx = x<x1 ? 1 : -1;
1752 int dy = -Math.abs(y1-y), sy = y<y1 ? 1 : -1;
1753 int err = dx+dy, e2; /* error value e_xy */
1754 double minDistLine = Double.MAX_VALUE;
1755 double minDistTarget = Double.MAX_VALUE;
1756 int bestX = Integer.MAX_VALUE, bestY = Integer.MAX_VALUE;
1757
1758 for(;;){ /* loop */
1759 if (!includeEndPoints && x==x1 && y==y1)
1760 break;
1761 if (Math.abs(y - p.getLatitude()) <= 1 || Math.abs(x - p.getLongitude()) <= 1){
1762 Coord t = new Coord(y, x);
1763 double distToTarget = t.distance(p);
1764
1765 if (includeEndPoints || x != x0 || y != y0){
1766 if (distToTarget < 10){
1767 double distLine = t.distToLineSegment(c1Dspl, c2Dspl);
1768 if (distLine < minDistLine || distLine == minDistLine && distToTarget < minDistTarget || distLine < 0.2 && distToTarget < minDistTarget){
1769 bestX = x;
1770 bestY = y;
1771 minDistLine = distLine;
1772 minDistTarget = distToTarget;
1773 }
1774 }
1775 }
1776 }
1777 if (x==x1 && y==y1) break;
1778 e2 = 2*err;
1779 if (e2 > dy) { err += dy; x += sx; } /* e_xy+e_x > 0 */
1780 if (e2 < dx) { err += dx; y += sy; } /* e_xy+e_y < 0 */
1781 }
1782 if (minDistLine == Double.MAX_VALUE)
1783 return null;
1784 Coord best = new Coord(bestY, bestX);
1785 return best;
1786 }
1787
1788
1789 /**
1790 * Use Bresenham algorithm to get the Garmin points which are close to the line
1791 * described by c1 and c2 and the point p.
1792 * @param c1
1793 * @param c2
1794 * @param p
1795 * @param maxBefore tolerated distance before p
1796 * @param maxAfter tolerated distance after p
1797 * @return sorted map with closest points
1798 */
1799 public static TreeMap<Double,List<Coord>> rasterLineNearPoint2(Coord c1, Coord c2, Coord p, double maxBefore, double maxAfter){
1800 int x0 = c1.getLongitude();
1801 int y0 = c1.getLatitude();
1802 int x1 = c2.getLongitude();
1803 int y1 = c2.getLatitude();
1804 Coord c1Dspl = c1.getDisplayedCoord();
1805 Coord c2Dspl = c2.getDisplayedCoord();
1806 int x = x0, y = y0;
1807 int dx = Math.abs(x1-x), sx = x<x1 ? 1 : -1;
1808 int dy = -Math.abs(y1-y), sy = y<y1 ? 1 : -1;
1809 int err = dx+dy, e2; /* error value e_xy */
1810
1811 TreeMap<Double,List<Coord>> sortedByDistToLine = new TreeMap<>();
1812 boolean beforeTarget = true;
1813 double lastDist = Double.NaN;
1814 for(;;){ /* loop */
1815 if (Math.abs(y - p.getLatitude()) <= 1 || Math.abs(x - p.getLongitude()) <= 1){
1816 Coord t = new Coord(y, x);
1817 double distToTarget = t.distance(p);
1818
1819 if (beforeTarget){
1820 if (Double.isNaN(lastDist) == false && lastDist < distToTarget)
1821 beforeTarget = false;
1822 }
1823 if (beforeTarget && distToTarget < maxBefore || !beforeTarget && distToTarget < maxAfter){
1824 Double distLine = t.distToLineSegment(c1Dspl, c2Dspl);
1825 List<Coord> list = sortedByDistToLine.get(distLine);
1826 if (list == null){
1827 list = new ArrayList<>();
1828 sortedByDistToLine.put(distLine, list);
1829 }
1830 list.add(t);
1831 }
1832 lastDist = distToTarget;
1833 }
1834 if (x==x1 && y==y1)
1835 break;
1836 e2 = 2*err;
1837 if (e2 > dy) { err += dy; x += sx; } /* e_xy+e_x > 0 */
1838 if (e2 < dx) { err += dx; y += sy; } /* e_xy+e_y < 0 */
1839 }
1840 return sortedByDistToLine;
1841 }
1842
1843 /**
1844 * Use Bresemham algorithm to get the Garmin points which are close to the line
1845 * described by c1 and c2 and the point p.
1846 * @param c1
1847 * @param c2
1848 * @param p
1849 * @return the list of points
1850 */
1851 public static List<Coord> rasterLineNearPoint3(Coord c1, Coord c2, double maxDistToLine){
1852 int x0 = c1.getLongitude();
1853 int y0 = c1.getLatitude();
1854 int x1 = c2.getLongitude();
1855 int y1 = c2.getLatitude();
1856 Coord c1Dspl = c1.getDisplayedCoord();
1857 Coord c2Dspl = c2.getDisplayedCoord();
1858 int x = x0, y = y0;
1859 int dx = Math.abs(x1-x), sx = x<x1 ? 1 : -1;
1860 int dy = -Math.abs(y1-y), sy = y<y1 ? 1 : -1;
1861 int err = dx+dy, e2; /* error value e_xy */
1862
1863 List<Coord> rendered = new ArrayList<>();
1864 for(;;){ /* loop */
1865 Coord t = new Coord(y, x);
1866 double distLine = t.distToLineSegment(c1Dspl, c2Dspl);
1867 if (distLine <= maxDistToLine)
1868 rendered.add(t);
1869 if (x==x1 && y==y1)
1870 break;
1871 e2 = 2*err;
1872 if (e2 > dy) { err += dy; x += sx; } /* e_xy+e_x > 0 */
1873 if (e2 < dx) { err += dx; y += sy; } /* e_xy+e_y < 0 */
1874 }
1875 return rendered;
1876 }
1877
1878 private static int countOccurence(List<HousenumberMatch> houses, int num){
1879 int count = 0;
1880 for (HousenumberMatch house : houses){
1881 if (house.getHousenumber() == num)
1882 count++;
1883 }
1884 return count;
1885 }
1886
1887 /**
1888 * @param length
1889 * @return string with length, e.g. "0.23 m" or "116.12 m"
1890 */
1891 private static String formatLen(double length){
1892 return HousenumberGenerator.formatLen(length);
1893 }
1894
1895 /**
1896 * Try to detect the case that many house numbers have no specific order.
1897 *
1898 */
1899 public void detectRandom() {
1900 int countFilledIvls = 0;
1901 int countFilledSides = 0;
1902 int countNotInOrder = 0;
1903 for (ExtNumbers curr = this; curr != null; curr = curr.next){
1904 if (curr.hasNumbers() == false)
1905 continue;
1906 countFilledIvls++;
1907 if (curr.notInOrder(Numbers.LEFT))
1908 countNotInOrder++;
1909 if (curr.notInOrder(Numbers.RIGHT))
1910 countNotInOrder++;
1911 if (curr.getHouses(Numbers.LEFT).size() > 1)
1912 ++countFilledSides;
1913 if (curr.getHouses(Numbers.RIGHT).size() > 1)
1914 ++countFilledSides;
1915
1916 }
1917 if (countNotInOrder > 0){
1918 if (countNotInOrder > countFilledIvls || countNotInOrder > 2 || countFilledSides == countNotInOrder)
1919 housenumberRoad.setRandom(true);
1920 }
1921 }
1922
1923 private boolean isPlausible(){
1924 if (getNumbers().isPlausible() == false)
1925 return false;
1926 if (multipleZipOrCity(true) || multipleZipOrCity(false))
1927 return false;
1928
1929 return true;
1930 }
1931
1932 /**
1933 * Split this segment into two.
1934 * @param splitSegment
1935 */
1936 private ExtNumbers split(int splitSegment){
1937 getRoad().getPoints().get(splitSegment).setNumberNode(true);
1938
1939 ExtNumbers first = divide();
1940 // distribute the houses. The caller has to make sure that
1941 // they are assigned to the wanted segment and sorted.
1942 for (int side = 0; side < 2; side++){
1943 boolean left = side == 0;
1944 List<HousenumberMatch> houses = getHouses(left);
1945 if (houses.isEmpty())
1946 continue;
1947 int used = first.setNumbers(houses, startInRoad, splitSegment, left);
1948 first.next.setNumbers(houses.subList(used, houses.size()), splitSegment, endInRoad, left);
1949 }
1950
1951 return first;
1952 }
1953
1954 public String toString(){
1955 return getRoad().toString() + getHouses(Numbers.LEFT).toString() + getHouses(Numbers.RIGHT).toString();
1956 }
1957
1958 }
1959
0 package uk.me.parabola.mkgmap.osmstyle.housenumber;
1
2 import uk.me.parabola.imgfmt.app.Coord;
3 import uk.me.parabola.mkgmap.general.CityInfo;
4 import uk.me.parabola.mkgmap.general.MapRoad;
5 import uk.me.parabola.mkgmap.general.ZipCodeInfo;
6 import uk.me.parabola.mkgmap.reader.osm.Element;
7 import uk.me.parabola.mkgmap.reader.osm.Node;
8 import uk.me.parabola.mkgmap.reader.osm.Way;
9 import uk.me.parabola.util.Locatable;
10
11 class HousenumberElem implements Locatable{
12 protected final Element element;
13 private int housenumber;
14 private String sign;
15 private String place;
16 private CityInfo cityInfo;
17 private ZipCodeInfo zipCode;
18 private String street;
19 //cache for Way elements to prevent calling Way.getCofG() repeatedly
20 private Coord location;
21
22 public HousenumberElem(Element el, CityInfo ci) {
23 this.element = el;
24 this.cityInfo = ci;
25 }
26
27 public HousenumberElem(HousenumberElem he) {
28 this.element = he.element;
29 this.housenumber = he.housenumber;
30 this.sign = he.sign;
31 this.street = he.street;
32 this.place = he.place;
33 this.cityInfo = he.cityInfo;
34 this.zipCode = he.zipCode;
35 this.location = he.location;
36 }
37
38 public Element getElement() {
39 return element;
40 }
41
42 public int getHousenumber() {
43 return housenumber;
44 }
45
46 public void setHousenumber(int housenumber) {
47 this.housenumber = housenumber;
48 }
49
50 public String getSign() {
51 return sign;
52 }
53
54 public void setSign(String sign) {
55 this.sign = sign;
56 }
57
58 public String getPlace() {
59 return place;
60 }
61
62 public void setPlace(String place) {
63 this.place = place;
64 }
65
66 public String getStreet() {
67 return street;
68 }
69
70 public void setStreet(String street) {
71 this.street = street;
72 }
73
74 public MapRoad getRoad(){
75 return null;
76 }
77
78 public CityInfo getCityInfo(){
79 return cityInfo;
80 }
81
82 public void setZipCode(ZipCodeInfo zip){
83 zipCode = zip;
84 }
85
86 public ZipCodeInfo getZipCode(){
87 return zipCode;
88 }
89
90 @Override
91 public Coord getLocation() {
92 if (location == null){
93 if (element instanceof Node)
94 location = ((Node) element).getLocation();
95 else
96 location = ((Way) element).getCofG();
97 }
98 return location;
99 }
100
101 @Override
102 public String toString() {
103 if (street != null)
104 return street + " " + sign;
105 if (place != null)
106 return place + " " + sign;
107 return "?" + " " + sign;
108 }
109 }
1212
1313 package uk.me.parabola.mkgmap.osmstyle.housenumber;
1414
15 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
16 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
17 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
18 import it.unimi.dsi.fastutil.longs.LongArrayList;
19
1520 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.BitSet;
1623 import java.util.Collections;
1724 import java.util.Comparator;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.LinkedHashMap;
29 import java.util.LinkedHashSet;
1830 import java.util.List;
1931 import java.util.Map;
2032 import java.util.Map.Entry;
21 import java.util.Properties;
33 import java.util.Set;
34 import java.util.TreeMap;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37
38 import uk.me.parabola.imgfmt.MapFailedException;
39 import uk.me.parabola.imgfmt.Utils;
2240 import uk.me.parabola.imgfmt.app.Coord;
23 import uk.me.parabola.imgfmt.app.CoordNode;
24 import uk.me.parabola.imgfmt.app.net.NumberStyle;
2541 import uk.me.parabola.imgfmt.app.net.Numbers;
2642 import uk.me.parabola.log.Logger;
43 import uk.me.parabola.mkgmap.general.CityInfo;
2744 import uk.me.parabola.mkgmap.general.LineAdder;
2845 import uk.me.parabola.mkgmap.general.MapRoad;
46 import uk.me.parabola.mkgmap.general.ZipCodeInfo;
2947 import uk.me.parabola.mkgmap.reader.osm.Element;
30 import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
48 import uk.me.parabola.mkgmap.reader.osm.HousenumberHooks;
3149 import uk.me.parabola.mkgmap.reader.osm.Node;
50 import uk.me.parabola.mkgmap.reader.osm.POIGeneratorHook;
3251 import uk.me.parabola.mkgmap.reader.osm.Relation;
52 import uk.me.parabola.mkgmap.reader.osm.TagDict;
3353 import uk.me.parabola.mkgmap.reader.osm.Way;
54 import uk.me.parabola.util.EnhancedProperties;
55 import uk.me.parabola.util.KdTree;
56 import uk.me.parabola.util.Locatable;
3457 import uk.me.parabola.util.MultiHashMap;
3558
3659 /**
3760 * Collects all data required for OSM house number handling and adds the
3861 * house number information to the roads.
3962 *
40 * @author WanMil
63 * @author WanMil, Gerd Petermann
4164 */
4265 public class HousenumberGenerator {
43
44 private static final Logger log = Logger
45 .getLogger(HousenumberGenerator.class);
66 private static final Logger log = Logger.getLogger(HousenumberGenerator.class);
4667
4768 /** Gives the maximum distance between house number element and the matching road */
48 private static final double MAX_DISTANCE_TO_ROAD = 150d;
69 public static final double MAX_DISTANCE_TO_ROAD = 150d;
4970
5071 private boolean numbersEnabled;
51
52 private MultiHashMap<String, MapRoad> roadByNames;
53 private List<MapRoad> roads;
54 private MultiHashMap<String, Element> houseNumbers;
55
56 public HousenumberGenerator(Properties props) {
57 this.roadByNames = new MultiHashMap<String,MapRoad>();
58 this.houseNumbers = new MultiHashMap<String,Element>();
59 this.roads = new ArrayList<MapRoad>();
72
73 // options for handling of unnamed (service?) roads
74 private int nameSearchDepth = 3;
75
76 private MultiHashMap<String, HousenumberIvl> interpolationWays;
77 private List<MapRoad> allRoads;
78 private Map<Long,Integer> interpolationNodes;
79 private List<HousenumberElem> houseElems;
80 private HashMap<CityInfo, CityInfo> cityInfos = new HashMap<>();
81 private HashMap<ZipCodeInfo, ZipCodeInfo> zipInfos = new HashMap<>();
82
83 private static final short housenumberTagKey1 = TagDict.getInstance().xlate("mkgmap:housenumber");
84 private static final short housenumberTagKey2 = TagDict.getInstance().xlate("addr:housenumber");
85 private static final short streetTagKey = TagDict.getInstance().xlate("mkgmap:street");
86 private static final short addrStreetTagKey = TagDict.getInstance().xlate("addr:street");
87 private static final short addrInterpolationTagKey = TagDict.getInstance().xlate("addr:interpolation");
88 private static final short addrPlaceTagKey = TagDict.getInstance().xlate("addr:place");
89 private static final short cityTagKey = TagDict.getInstance().xlate("mkgmap:city");
90 private static final short regionTagKey = TagDict.getInstance().xlate("mkgmap:region");
91 private static final short countryTagKey = TagDict.getInstance().xlate("mkgmap:country");
92 private static final short postalCodeTagKey = TagDict.getInstance().xlate("mkgmap:postal_code");
93 private static final short numbersTagKey = TagDict.getInstance().xlate("mkgmap:numbers");
94
95 public HousenumberGenerator(EnhancedProperties props) {
96 this.interpolationWays = new MultiHashMap<>();
97 this.allRoads = new ArrayList<>();
98 this.interpolationNodes = new HashMap<>();
99 this.houseElems = new ArrayList<>();
60100
61 numbersEnabled=props.containsKey("housenumbers");
101 numbersEnabled = props.containsKey("housenumbers");
102 int n = props.getProperty("name-service-roads", 3);
103 if (n != nameSearchDepth){
104 nameSearchDepth = Math.min(25, Math.max(0, n));
105 if (nameSearchDepth != n)
106 System.err.println("name-service-roads=" + n + " was changed to name-service-roads=" + nameSearchDepth);
107 }
62108 }
63109
64110 /**
67113 * @return the street name (or {@code null} if no street name set)
68114 */
69115 private static String getStreetname(Element e) {
70 String streetname = e.getTag("mkgmap:street");
116 String streetname = e.getTag(streetTagKey);
71117 if (streetname == null) {
72 streetname = e.getTag("addr:street");
118 streetname = e.getTag(addrStreetTagKey);
73119 }
74120 return streetname;
75121 }
76122
123 /**
124 * Retrieves the house number of this element.
125 * @param e an OSM element
126 * @return the house number (or {@code null} if no house number set)
127 */
128 public static String getHousenumber(Element e) {
129 String res = e.getTag(housenumberTagKey1);
130 if (res != null)
131 return res;
132 return e.getTag(housenumberTagKey2);
133 }
134
135 /**
136 * Parses the house number string. It accepts the first positive number part
137 * of a string. So all leading and preceding non number parts are ignored.
138 * So the following strings are accepted:
139 * <table>
140 * <tr>
141 * <th>Input</th>
142 * <th>Output</th>
143 * </tr>
144 * <tr>
145 * <td>23</td>
146 * <td>23</td>
147 * </tr>
148 * <tr>
149 * <td>-23</td>
150 * <td>23</td>
151 * </tr>
152 * <tr>
153 * <td>21-23</td>
154 * <td>21</td>
155 * </tr>
156 * <tr>
157 * <td>Abc 21</td>
158 * <td>21</td>
159 * </tr>
160 * <tr>
161 * <td>Abc 21.45</td>
162 * <td>21</td>
163 * </tr>
164 * <tr>
165 * <td>21 Main Street</td>
166 * <td>21</td>
167 * </tr>
168 * <tr>
169 * <td>Main Street</td>
170 * <td><i>IllegalArgumentException</i></td>
171 * </tr>
172 * </table>
173 * @throws IllegalArgumentException if parsing fails
174 */
175 private static Integer parseHousenumber(String housenumberString) {
176 if (housenumberString == null) {
177 return null;
178 }
179
180 // the housenumber must match against the pattern <anything>number<notnumber><anything>
181 int housenumber;
182 Pattern p = Pattern.compile("\\D*(\\d+)\\D?.*");
183 Matcher m = p.matcher(housenumberString);
184 if (m.matches() == false) {
185 return null;
186 }
187 try {
188 // get the number part and parse it
189 housenumber = Integer.parseInt(m.group(1));
190 } catch (NumberFormatException exp) {
191 return null;
192 }
193 return housenumber;
194 }
195
196
197 private HousenumberElem parseElement(Element el, String sign){
198 String city = el.getTag(cityTagKey);
199 String region = el.getTag(regionTagKey);
200 String country = el.getTag(countryTagKey);
201 CityInfo ci = getCityInfos(city,region,country);
202 HousenumberElem house = new HousenumberElem(el, ci);
203 if (house.getLocation() == null){
204 // there has been a report that indicates match.getLocation() == null
205 // could not reproduce so far but catching it here with some additional
206 // information. (WanMil)
207 log.error("OSM element seems to have no point.");
208 log.error("Element: " + el.toBrowseURL() + " " + el);
209 log.error("Please report on the mkgmap mailing list.");
210 log.error("Continue creating the map. This should be possible without a problem.");
211 return null;
212 }
213
214 house.setSign(sign);
215 Integer hn = parseHousenumber(sign);
216 if (hn == null){
217 if (log.isDebugEnabled())
218 log.debug("No housenumber (", el.toBrowseURL(), "): ", sign);
219 return null;
220 }
221 if (hn < 0 || hn > 1_000_000){
222 log.warn("Number looks wrong, is ignored",house.getSign(),hn,"element",el.toBrowseURL());
223 return null;
224 }
225 house.setHousenumber(hn);
226 house.setStreet(getStreetname(el));
227 house.setPlace(el.getTag(addrPlaceTagKey));
228 String zipStr = el.getTag(postalCodeTagKey);
229 ZipCodeInfo zip = getZipInfos(zipStr);
230 house.setZipCode(zip);
231 return house;
232 }
233
234 private CityInfo getCityInfos(String city, String region, String country) {
235 CityInfo ci = new CityInfo(city, region, country);
236 CityInfo ciOld = cityInfos.get(ci);
237 if (ciOld != null)
238 return ciOld;
239 // log.debug(ci);
240 cityInfos.put(ci, ci);
241 return ci;
242 }
243
244 private ZipCodeInfo getZipInfos(String zipStr) {
245 ZipCodeInfo zip = new ZipCodeInfo(zipStr);
246 ZipCodeInfo zipOld = zipInfos.get(zip);
247 if (zipOld != null)
248 return zipOld;
249 zipInfos.put(zip, zip);
250 return zip;
251 }
252
253 private HousenumberElem handleElement(Element el){
254 String sign = getHousenumber(el);
255 if (sign == null)
256 return null;
257
258 HousenumberElem he = parseElement(el, sign);
259 if (he == null)
260 return null;
261 houseElems.add(he);
262 return he;
263 }
77264 /**
78265 * Adds a node for house number processing.
79266 * @param n an OSM node
82269 if (numbersEnabled == false) {
83270 return;
84271 }
85 if (HousenumberMatch.getHousenumber(n) != null) {
86 String streetname = getStreetname(n);
87 if (streetname != null) {
88 houseNumbers.add(streetname, n);
89 } else {
90 if (log.isDebugEnabled())
91 log.debug(n.toBrowseURL()," ignored, doesn't contain a street name.");
92 }
93 }
272 if("false".equals(n.getTag(numbersTagKey)))
273 return;
274
275 if ("true".equals(n.getTag(POIGeneratorHook.AREA2POI_TAG))){
276 // ignore POI created for buildings
277 return;
278 }
279 HousenumberElem houseElem = handleElement(n);
280 if (houseElem == null)
281 return;
282 if (n.getTag(HousenumberHooks.partOfInterpolationTagKey) != null)
283 interpolationNodes.put(n.getId(),houseElems.size()-1);
94284 }
95285
96286 /**
101291 if (numbersEnabled == false) {
102292 return;
103293 }
104 if (HousenumberMatch.getHousenumber(w) != null) {
105 String streetname = getStreetname(w);
106 if (streetname != null) {
107 houseNumbers.add(streetname, w);
294 if("false".equals(w.getTag(numbersTagKey)))
295 return;
296
297 String ai = w.getTag(addrInterpolationTagKey);
298 if (ai != null){
299 // the way has the addr:interpolation=* tag, parse info
300 // created by the HousenumberHook
301 List<HousenumberElem> nodes = new ArrayList<>();
302 String nodeIds = w.getTag(HousenumberHooks.mkgmapNodeIdsTagKey);
303 if (nodeIds == null){
304 // way was rejected by hook
108305 } else {
109 if (log.isDebugEnabled()){
110 if (FakeIdGenerator.isFakeId(w.getId()))
111 log.debug("mp-created way ignored, doesn't contain a street name. Tags:",w.toTagString());
112 else
113 log.debug(w.toBrowseURL()," ignored, doesn't contain a street name.");
114 }
115 }
116 }
117 }
118
119
306 String[] ids = nodeIds.split(",");
307 for (String idString : ids){
308 Long id = Long.decode(idString);
309 Integer elemPos = interpolationNodes.get(id);
310 if (elemPos != null){
311 HousenumberElem node = houseElems.get(elemPos);
312 if (node != null){
313 assert node.getElement().getId() == id;
314 nodes.add(node);
315 }
316 }
317 }
318 interpretInterpolationWay(w, nodes);
319 }
320 return;
321 }
322
323 if (w.hasIdenticalEndPoints()){
324 // we are only interested in polygons now
325 handleElement(w);
326 }
327
328 }
329
330 /**
331 * Use the information provided by the addr:interpolation tag
332 * to generate additional house number elements. This increases
333 * the likelihood that a road segment is associated with the right
334 * number ranges.
335 * @param w the way
336 * @param nodes2
337 * @param nodes list of nodes
338 */
339 private void interpretInterpolationWay(Way w, List<HousenumberElem> nodes) {
340 int numNodes = nodes.size();
341 String addrInterpolationMethod = w.getTag(addrInterpolationTagKey);
342 int step = 0;
343 switch (addrInterpolationMethod) {
344 case "all":
345 case "1":
346 step = 1;
347 break;
348 case "even":
349 case "odd":
350 case "2":
351 step = 2;
352 break;
353 default:
354 break;
355 }
356 if (step == 0)
357 return; // should not happen here
358 int pos = 0;
359 List<HousenumberIvl> hivls = new ArrayList<>();
360 String streetName = null;
361 for (int i = 0; i+1 < numNodes; i++){
362 // the way have other points, find the sequence including the pair of nodes
363 HousenumberElem he1 = nodes.get(i);
364 HousenumberElem he2 = nodes.get(i+1);
365 int pos1 = -1, pos2 = -1;
366 for (int k = pos; k < w.getPoints().size(); k++){
367 if (w.getPoints().get(k) == he1.getLocation()){
368 pos1 = k;
369 break;
370 }
371 }
372 if (pos1 < 0){
373 log.error("addr:interpolation node not found in way",w);
374 return;
375 }
376 for (int k = pos1+1; k < w.getPoints().size(); k++){
377 if (w.getPoints().get(k) == he2.getLocation()){
378 pos2 = k;
379 break;
380 }
381 }
382 if (pos2 < 0){
383 log.error("addr:interpolation node not found in way",w);
384 return;
385 }
386 pos = pos2;
387 String street = he1.getStreet();
388 if (street != null && street.equals(he2.getStreet())){
389 if (streetName == null)
390 streetName = street;
391 else if (streetName.equals(street) == false){
392 log.warn(w.toBrowseURL(),"addr:interpolation=even is used with different street names",streetName,street);
393 return;
394 }
395
396 int start = he1.getHousenumber();
397 int end = he2.getHousenumber();
398 int steps;
399 if (start < end){
400 steps = (end - start) / step - 1;
401 } else {
402 steps = (start - end) / step - 1;
403 }
404 HousenumberIvl hivl = new HousenumberIvl(street, w, (Node)he1.element, (Node)he2.element);
405 hivl.setStart(start);
406 hivl.setEnd(end);
407 hivl.setStep(step);
408 hivl.setSteps(steps);
409 hivl.setPoints(w.getPoints().subList(pos1, pos2+1));
410 // if (pos1 > 0){
411 // double angle = Utils.getAngle(w.getPoints().get(pos1-1), w.getPoints().get(pos1), w.getPoints().get(pos1+1));
412 // if (Math.abs(angle) > 75){
413 // log.warn(w.toBrowseURL(),"addr:interpolation way has sharp angle at number",start,"cannot use it");
414 // return;
415 // }
416 //
417 // }
418
419 hivls.add(hivl);
420 if ("even".equals(addrInterpolationMethod) && (start % 2 != 0 || end % 2 != 0)){
421 log.warn(w.toBrowseURL(),"addr:interpolation=even is used with odd housenumber(s)",start,end);
422 return;
423 }
424 if ("odd".equals(addrInterpolationMethod) && (start % 2 == 0 || end % 2 == 0)){
425 log.warn(w.toBrowseURL(),"addr:interpolation=odd is used with even housenumber(s)",start,end);
426 return;
427 }
428
429 if (start == end && he1.getSign().equals(he2.getSign())){
430 // handle special case from CanVec imports
431 if (pos1 == 0 && pos2 +1 == w.getPoints().size()){
432 hivl.setEqualEnds();
433 log.warn(w.toBrowseURL(),"addr:interpolation way connects two points with equal numbers, numbers are ignored");
434 }
435 }
436 }
437 }
438 for (HousenumberIvl hivl : hivls)
439 interpolationWays.add(streetName, hivl);
440 }
441
442 private MapRoad firstRoadSameOSMWay = null;
120443 /**
121444 * Adds a road to be processed by the house number generator.
122445 * @param osmRoad the OSM way the defines the road
123446 * @param road a road
124447 */
125448 public void addRoad(Way osmRoad, MapRoad road) {
126 roads.add(road);
449 allRoads.add(road);
127450 if (numbersEnabled) {
128 String name = getStreetname(osmRoad);
129 if (name != null) {
130 if (log.isDebugEnabled())
131 log.debug("Housenumber - Streetname:", name, "Way:",osmRoad.getId(),osmRoad.toTagString());
132 roadByNames.add(name, road);
451 if("false".equals(osmRoad.getTag(numbersTagKey)))
452 road.setSkipHousenumberProcessing(true);
453
454 /*
455 * If the style adds the same OSM way as two or more routable ways, we use
456 * only the first. This ensures that we don't try to assign numbers from bad
457 * matches to these copies.
458 */
459 if(!road.isSkipHousenumberProcessing()){
460 if (firstRoadSameOSMWay != null){
461 if (firstRoadSameOSMWay.getRoadDef().getId() == road.getRoadDef().getId()){
462 if (firstRoadSameOSMWay.getPoints().equals(road.getPoints())){
463 road.setSkipHousenumberProcessing(true);
464 return;
465 }
466 }
467 }
468 firstRoadSameOSMWay = road;
469 String name = road.getStreet();
470 if (name != null) {
471 if (log.isDebugEnabled())
472 log.debug("Housenumber - Streetname:", name, "Way:",osmRoad.getId(),osmRoad.toTagString());
473 }
133474 }
134475 }
135476 }
173514 log.warn("Relation",r.toBrowseURL(),": role of member",w.toBrowseURL(),"unclear");
174515 break;
175516 default:
517 if ("associatedStreet".equals(relType))
518 log.warn("Relation",r.toBrowseURL(),": don't know how to handle member with role",role);
176519 break;
177520 }
178521 }
179 }
180 if (houses.isEmpty()){
181 if ("associatedStreet".equals(relType))
182 log.warn("Relation",r.toBrowseURL(),": ignored, found no houses");
183 return;
184522 }
185523 String streetName = r.getTag("name");
186524 String streetNameFromRoads = null;
525 List<Element> unnamedStreetElems = new ArrayList<>();
187526 boolean nameFromStreetsIsUnclear = false;
188527 if (streets.isEmpty() == false) {
189528 for (Element street : streets) {
190 String roadName = street.getTag("name");
191 if (roadName == null)
529 String roadName = street.getTag(streetTagKey);
530 if (roadName == null)
531 roadName = street.getTag("name");
532 if (roadName == null){
533 unnamedStreetElems.add(street);
192534 continue;
535 }
193536 if (streetNameFromRoads == null)
194537 streetNameFromRoads = roadName;
195538 else if (streetNameFromRoads.equals(roadName) == false)
207550 } else {
208551 if (streetNameFromRoads != null){
209552 if (nameFromStreetsIsUnclear == false && streetName.equals(streetNameFromRoads) == false){
553 if (unnamedStreetElems.isEmpty() == false){
554 log.warn("Relation",r.toBrowseURL(),": ignored, street name is not clear.");
555 return;
556 }
210557 log.warn("Relation",r.toBrowseURL(),": street name is not clear, using the name from the way, not that of the relation.");
211558 streetName = streetNameFromRoads;
212559 }
215562 }
216563 }
217564 }
218 int countOK = 0;
565 int countModHouses = 0;
219566 if (streetName != null && streetName.isEmpty() == false){
220567 for (Element house : houses) {
221568 if (addStreetTagFromRel(r, house, streetName) )
222 countOK++;
223 }
224 }
225 if (countOK > 0)
226 log.info("Relation",r.toBrowseURL(),": added tag mkgmap:street=",streetName,"to",countOK,"of",houses.size(),"house members");
227 else
228 log.info("Relation",r.toBrowseURL(),": ignored, the house members all have a addr:street or mkgmap:street tag");
569 countModHouses++;
570 }
571 for (Element street : unnamedStreetElems) {
572 street.addTag(streetTagKey, streetName);
573 }
574 }
575 if (log.isInfoEnabled()){
576 if (countModHouses > 0 || !unnamedStreetElems.isEmpty()){
577 if (countModHouses > 0)
578 log.info("Relation",r.toBrowseURL(),": added tag mkgmap:street=",streetName,"to",countModHouses,"of",houses.size(),"house members" );
579 if (!unnamedStreetElems.isEmpty())
580 log.info("Relation",r.toBrowseURL(),": added tag mkgmap:street=",streetName,"to",unnamedStreetElems.size(),"of",streets.size(),"street members" );
581 }
582 else
583 log.info("Relation",r.toBrowseURL(),": ignored, no house or street member was changed");
584 }
585
229586 }
230587 }
231588
233590 * Add the tag mkgmap:street=streetName to the element of the
234591 * relation if it does not already have a street name tag.
235592 */
236 private boolean addStreetTagFromRel(Relation r, Element house, String streetName){
593 private static boolean addStreetTagFromRel(Relation r, Element house, String streetName){
237594 String addrStreet = getStreetname(house);
238595 if (addrStreet == null){
239 house.addTag("mkgmap:street", streetName);
596 house.addTag(streetTagKey, streetName);
240597 if (log.isDebugEnabled())
241598 log.debug("Relation",r.toBrowseURL(),": adding tag mkgmap:street=" + streetName, "to house",house.toBrowseURL());
242599 return true;
243600 }
244601 else if (addrStreet.equals(streetName) == false){
245 if (house.getTag("mkgmap:street") != null){
602 if (house.getTag(streetTagKey) != null){
246603 log.warn("Relation",r.toBrowseURL(),": street name from relation doesn't match existing mkgmap:street tag for house",house.toBrowseURL(),"the house seems to be member of another type=associatedStreet relation");
247 house.deleteTag("mkgmap:street");
604 house.deleteTag(streetTagKey);
248605 }
249606 else
250607 log.warn("Relation",r.toBrowseURL(),": street name from relation doesn't match existing name for house",house.toBrowseURL());
253610 }
254611
255612
256 public void generate(LineAdder adder) {
613 /**
614 *
615 * @param adder
616 * @param naxNodeId the highest nodeId used before
617 */
618 public void generate(LineAdder adder, int naxNodeId) {
257619 if (numbersEnabled) {
258 for (Entry<String, List<Element>> numbers : houseNumbers.entrySet()) {
259 List<MapRoad> possibleRoads = roadByNames.get(numbers.getKey());
260
261 if (possibleRoads.isEmpty()) {
620 MultiHashMap<MapRoad,HousenumberMatch> initialHousesForRoads = findClosestRoadsToHouse();
621 identifyServiceRoads();
622
623 handleInterpolationWays(initialHousesForRoads);
624
625 List<HousenumberRoad> hnrList = createHousenumberRoads(initialHousesForRoads);
626 initialHousesForRoads = null;
627 log.info("found",hnrList.size(),"road candidates for address search");
628
629 useAddrPlaceTag(hnrList);
630 Map<MapRoad, HousenumberRoad> road2HousenumberRoadMap = new HashMap<>();
631 for (HousenumberRoad hnr : hnrList){
632 road2HousenumberRoadMap.put(hnr.getRoad(), hnr);
633 }
634 Int2ObjectOpenHashMap<HashSet<MapRoad>> nodeId2RoadLists = new Int2ObjectOpenHashMap<>();
635 for (MapRoad road : allRoads){
636 for (Coord co : road.getPoints()){
637 if (co.getId() == 0)
638 continue;
639 HashSet<MapRoad> connectedRoads = nodeId2RoadLists.get(co.getId());
640 if (connectedRoads == null){
641 connectedRoads = new HashSet<>();
642 nodeId2RoadLists.put(co.getId(), connectedRoads);
643 }
644 connectedRoads.add(road);
645 }
646 }
647 List<HousenumberRoad> addedRoads = new ArrayList<>();
648 Iterator<HousenumberRoad> iter = hnrList.iterator();
649 while (iter.hasNext()){
650 HousenumberRoad hnr = iter.next();
651
652 List<HousenumberMatch> lostHouses = hnr.checkStreetName(road2HousenumberRoadMap, nodeId2RoadLists);
653 for (HousenumberMatch house : lostHouses){
654 MapRoad r = house.getRoad();
655 if (r != null){
656 HousenumberRoad hnr2 = road2HousenumberRoadMap.get(r);
657 if (hnr2 == null){
658 CityInfo ci = getCityInfos(r.getCity(), r.getRegion(), r.getCountry());
659 hnr2 = new HousenumberRoad(r, ci, Arrays.asList(house));
660 if (r.getZip() != null)
661 hnr2.setZipCodeInfo(getZipInfos(r.getZip()));
662 road2HousenumberRoadMap.put(r,hnr2);
663 addedRoads.add(hnr2);
664 } else {
665 hnr2.addHouse(house);
666 }
667 }
668 }
669 if (hnr.getName() == null){
670 iter.remove();
671 for (HousenumberMatch house : hnr.getHouses()){
672 log.warn("found no plausible road name for address",house.getElement().toBrowseURL(),", closest road id:",house.getRoad());
673 }
674 }
675 }
676 hnrList.addAll(addedRoads);
677 // TODO: interpolate addr:interpolation houses
678 removeDupsGroupedByCityAndName(hnrList);
679
680 // group by street name and city
681 TreeMap<String, TreeMap<CityInfo, List<HousenumberRoad>>> streetnameCityRoadMap = new TreeMap<>();
682 for (HousenumberRoad hnr : hnrList){
683 TreeMap<CityInfo, List<HousenumberRoad>> cluster = streetnameCityRoadMap.get(hnr.getName());
684 if (cluster == null){
685 cluster = new TreeMap<>();
686 streetnameCityRoadMap.put(hnr.getName(), cluster);
687 }
688 List<HousenumberRoad> roadsInCluster = cluster.get(hnr.getRoadCityInfo());
689 if (roadsInCluster == null){
690 roadsInCluster = new ArrayList<>();
691 cluster.put(hnr.getRoadCityInfo(), roadsInCluster);
692 }
693 roadsInCluster.add(hnr);
694 }
695
696 for (Entry<String, TreeMap<CityInfo, List<HousenumberRoad>>> streetNameEntry : streetnameCityRoadMap.entrySet()){
697 String streetName = streetNameEntry.getKey();
698 List<HousenumberRoad> roadsWithStreetName = new ArrayList<>();
699 for (Entry<CityInfo, List<HousenumberRoad>> clusterEntry : streetNameEntry.getValue().entrySet()){
700 roadsWithStreetName.addAll(clusterEntry.getValue());
701 }
702 useInterpolationInfo(streetName, roadsWithStreetName, road2HousenumberRoadMap);
703
704 for (Entry<CityInfo, List<HousenumberRoad>> clusterEntry : streetNameEntry.getValue().entrySet()){
705 List<HousenumberRoad> roadsInCluster = clusterEntry.getValue();
706 if (log.isDebugEnabled()){
707 log.debug("processing road(s) with name",streetName,"in",clusterEntry.getKey() );
708 }
709 for (HousenumberRoad hnr : roadsInCluster){
710 hnr.buildIntervals();
711 }
712 boolean optimized = false;
713 for (int loop = 0; loop < 10; loop++){
714 for (HousenumberRoad hnr : roadsInCluster){
715 hnr.checkIntervals();
716 }
717 checkWrongRoadAssignmments(roadsInCluster);
718 boolean changed = hasChanges(roadsInCluster);
719 if (!optimized && !changed){
720 for (HousenumberRoad hnr : roadsInCluster){
721 hnr.improveSearchResults();
722 }
723 changed = hasChanges(roadsInCluster);
724 optimized = true;
725 }
726 if (!changed)
727 break;
728 }
729 for (HousenumberRoad hnr : roadsInCluster){
730 hnr.setNumbers();
731 }
732 }
733 }
734 }
735
736 if (log.isInfoEnabled()){
737 for (HousenumberElem house : houseElems){
738 if (house.getRoad() == null){
739 if (house.getStreet() != null)
740 log.info("found no plausible road for house number element",house.getElement().toBrowseURL(),house.getStreet(),house.getSign());
741 else
742 log.info("found no plausible road for house number element",house.getElement().toBrowseURL());
743 }
744 }
745 }
746 for (MapRoad r : allRoads) {
747 if (log.isDebugEnabled()){
748 List<Numbers> finalNumbers = r.getRoadDef().getNumbersList();
749 if (finalNumbers != null){
750 log.info("id:"+r.getRoadDef().getId(),", final numbers,",r,"in",r.getCity());
751 for (Numbers cn : finalNumbers){
752 if (cn.isEmpty())
753 continue;
754 log.info("id:"+r.getRoadDef().getId(),", Left: ",cn.getLeftNumberStyle(),cn.getIndex(),"Start:",cn.getLeftStart(),"End:",cn.getLeftEnd());
755 log.info("id:"+r.getRoadDef().getId(),", Right:",cn.getRightNumberStyle(),cn.getIndex(),"Start:",cn.getRightStart(),"End:",cn.getRightEnd());
756 }
757 }
758 }
759 adder.add(r);
760 }
761 }
762
763 private List<HousenumberRoad> createHousenumberRoads(
764 MultiHashMap<MapRoad, HousenumberMatch> initialHousesForRoads) {
765 List<HousenumberRoad> hnrList = new ArrayList<>();
766 for (MapRoad road : allRoads){
767 if (road.isSkipHousenumberProcessing())
768 continue;
769 List<HousenumberMatch> houses = initialHousesForRoads.get(road);
770 if (houses == null || houses.isEmpty())
771 continue;
772 CityInfo ci = getCityInfos(road.getCity(), road.getRegion(), road.getCountry());
773 HousenumberRoad hnr = new HousenumberRoad(road, ci, houses);
774 if (road.getZip() != null)
775 hnr.setZipCodeInfo(getZipInfos(road.getZip()));
776
777 hnrList.add(hnr);
778 }
779 return hnrList;
780 }
781
782 private MultiHashMap<MapRoad, HousenumberMatch> findClosestRoadsToHouse() {
783 // build road index
784 long t1 = System.currentTimeMillis();
785 RoadSegmentIndex roadSegmentIndex = new RoadSegmentIndex(allRoads, MAX_DISTANCE_TO_ROAD);
786 long t2 = System.currentTimeMillis();
787 log.debug("creation of road index took",t2-t1,"ms");
788
789 long t3 = System.currentTimeMillis();
790 MultiHashMap<MapRoad,HousenumberMatch> initialHousesForRoads = new MultiHashMap<>();
791 for (int i = 0; i < houseElems.size(); i++){
792 HousenumberElem house = houseElems.get(i);
793 HousenumberMatch bestMatch = roadSegmentIndex.createHousenumberMatch(house);
794 houseElems.set(i, bestMatch);
795 if (bestMatch.getRoad() == null){
796 bestMatch.setIgnored(true); // XXX maybe create a pseudo road with zero length?
797 // log.warn("found no plausible road for house number element",house.getElement().toBrowseURL());
798 continue;
799 }
800 initialHousesForRoads.add(bestMatch.getRoad(), bestMatch);
801 }
802 long t4 = System.currentTimeMillis();
803 log.debug("identification of closest road for each house took",t4-t3,"ms");
804
805 return initialHousesForRoads;
806 }
807
808 private void useAddrPlaceTag(List<HousenumberRoad> hnrList){
809 HashMap<CityInfo,MultiHashMap<String,HousenumberMatch>> cityPlaceHouseMap = new LinkedHashMap<>();
810 for (int i = 0; i < houseElems.size(); i++){
811 HousenumberElem house = houseElems.get(i);
812 if (house.getRoad() == null)
813 continue;
814 if (house.getPlace() == null)
815 continue;
816 MultiHashMap<String, HousenumberMatch> subMap = cityPlaceHouseMap.get(house.getCityInfo());
817 if (subMap == null){
818 subMap = new MultiHashMap<>();
819 cityPlaceHouseMap.put(house.getCityInfo(), subMap);
820 }
821 subMap.add(house.getPlace(), (HousenumberMatch) house);
822 }
823 log.info("analysing",cityPlaceHouseMap.size(),"cities with addr:place=* houses" );
824 for (Entry<CityInfo, MultiHashMap<String, HousenumberMatch>> topEntry : cityPlaceHouseMap.entrySet()){
825 CityInfo cityInfo = topEntry.getKey();
826 List<String> placeNames = new ArrayList<>(topEntry.getValue().keySet());
827 Collections.sort(placeNames);
828 for (String placeName : placeNames){
829 List<HousenumberMatch> placeHouses = topEntry.getValue().get(placeName);
830 HashSet<HousenumberRoad> roads = new LinkedHashSet<>();
831 Int2IntOpenHashMap usedNumbers = new Int2IntOpenHashMap();
832 HashMap<String,Integer> usedSigns = new HashMap<>();
833 int dupSigns = 0;
834 int dupNumbers = 0;
835 int housesWithStreet = 0;
836 int housesWithMatchingStreet = 0;
837 int roadsWithNames = 0;
838 int unnamedCloseRoads = 0;
839
840 for (HousenumberMatch house : placeHouses){
841 if (house.getStreet() != null ){
842 ++housesWithStreet;
843 if (house.getStreet().equals(house.getRoad().getStreet())){
844 ++housesWithMatchingStreet;
845 }
846
847 } else {
848 if (house.getRoad().getStreet() == null)
849 ++unnamedCloseRoads;
850 }
851 boolean added = roads.add(house.getHousenumberRoad());
852 if (added && house.getRoad().getStreet() != null)
853 ++roadsWithNames;
854 int oldCount = usedNumbers.put(house.getHousenumber(),1);
855 if (oldCount != 0){
856 usedNumbers.put(house.getHousenumber(), oldCount + 1);
857 ++dupNumbers;
858 }
859 Integer oldSignCount = usedSigns.put(house.getSign(), 1);
860 if (oldSignCount != null){
861 usedSigns.put(house.getSign(), oldSignCount + 1);
862 ++dupSigns;
863 }
864 }
865
866 if (log.isDebugEnabled()){
867 log.debug("place",placeName,"in city",cityInfo, ":", "houses:", placeHouses.size(),
868 ",duplicate numbers/signs:", dupNumbers+"/"+dupSigns,
869 ",roads (named/unnamed):", roads.size(),"("+roadsWithNames+"/"+(roads.size()- roadsWithNames)+")",
870 ",houses without addr:street:", placeHouses.size() - housesWithStreet,
871 ",street = name of closest road:", housesWithMatchingStreet,
872 ",houses without addr:street near named road:", unnamedCloseRoads);
873 }
874 if ((float) dupSigns / placeHouses.size() < 0.25 ){
875 if (log.isDebugEnabled())
876 log.debug("will not use gaps in intervals for roads in",placeName );
877 for (HousenumberRoad hnr : roads){
878 hnr.setRemoveGaps(true);
879 }
880 }
881 if (placeHouses.size() > housesWithStreet){ // XXX: threshold value?
882 LongArrayList ids = new LongArrayList();
883 for (HousenumberRoad hnr : roads){
884 ids.add(hnr.getRoad().getRoadDef().getId());
885 hnr.addPlaceName(placeName);
886 }
887 if (log.isDebugEnabled())
888 log.debug("detected",placeName,"as potential address name for roads",ids);
889 } else {
890 if (log.isDebugEnabled())
891 log.debug("will ignore addr:place for address search in",placeName,"in city",cityInfo);
892 }
893 }
894 }
895 }
896
897 /**
898 * Update the house number information in the interpolation intervals and
899 * use them to correct wrong road assignments.
900 * @param initialHousesForRoads map that is updated when wrong road assignments were found
901 */
902 private void handleInterpolationWays(MultiHashMap<MapRoad, HousenumberMatch> initialHousesForRoads) {
903 for (Entry<String, List<HousenumberIvl>> entry : interpolationWays.entrySet()){
904 List<HousenumberIvl> infos = entry.getValue();
905 for (HousenumberIvl info : infos){
906 if (info.isBad()){
262907 continue;
263908 }
264
265 match(numbers.getKey(), numbers.getValue(), possibleRoads);
909 boolean isOK = info.setNodeRefs(interpolationNodes, houseElems);
910 if (!isOK)
911 continue;
912 HousenumberMatch[] houses = info.getHouseNodes();
913 MapRoad uncheckedRoads[] = new MapRoad[houses.length];
914 for (int i = 0 ; i < houses.length; i++)
915 uncheckedRoads[i] = houses[i].getRoad();
916
917 isOK = info.checkRoads();
918 if (!isOK)
919 continue;
920 // check if houses are assigned to different roads now
921 houses = info.getHouseNodes();
922 for (int i = 0 ; i < houses.length; i++){
923 if (houses[i].getRoad() != uncheckedRoads[i]){
924 initialHousesForRoads.removeMapping(uncheckedRoads[i], houses[i]);
925 if (houses[i].isIgnored() == false)
926 initialHousesForRoads.add(houses[i].getRoad(), houses[i]);
927 }
928 }
929 }
930 }
931 }
932
933 private void removeDupsGroupedByCityAndName(List<HousenumberRoad> hnrList){
934 HashMap<CityInfo,MultiHashMap<String,HousenumberMatch>> cityNameHouseMap = new LinkedHashMap<>();
935 for (int i = 0; i < houseElems.size(); i++){
936 HousenumberElem house = houseElems.get(i);
937 if (house.getRoad() == null)
938 continue;
939 if (house instanceof HousenumberMatch){
940 HousenumberMatch hm = (HousenumberMatch) house;
941 if (hm.isIgnored())
942 continue;
943 HousenumberRoad hnr = hm.getHousenumberRoad();
944 if (hnr == null || hnr.getName() == null)
945 continue;
946 MultiHashMap<String, HousenumberMatch> subMap = cityNameHouseMap.get(hm.getCityInfo());
947 if (subMap == null){
948 subMap = new MultiHashMap<>();
949 cityNameHouseMap.put(hm.getCityInfo(), subMap);
950 }
951 subMap.add(hnr.getName(), hm);
266952 }
267953 }
268954
269 for (MapRoad r : roads) {
270 adder.add(r);
271 }
955 for (Entry<CityInfo, MultiHashMap<String, HousenumberMatch>> topEntry : cityNameHouseMap.entrySet()){
956 for (Entry<String, List<HousenumberMatch>> entry : topEntry.getValue().entrySet()){
957 markSimpleDuplicates(entry.getKey(), entry.getValue());
958 }
959
960 }
961 }
962
963
964 private static void checkSegment(HousenumberMatch house, MapRoad road, int seg){
965 Coord cx = house.getLocation();
966 Coord c0 = road.getPoints().get(seg);
967 Coord c1 = road.getPoints().get(seg + 1);
968 double frac = getFrac(c0, c1, cx);
969 double dist = distanceToSegment(c0,c1,cx,frac);
970 if (dist < house.getDistance()){
971 house.setDistance(dist);
972 house.setRoad(road);
973 house.setSegment(seg);
974 house.setSegmentFrac(frac);
975 }
976 }
977
978
979 /**
980 * process option --x-name-service-roads=n
981 * The program identifies unnamed roads which are only connected to one
982 * road with a name or to multiple roads with the same name. The process is
983 * repeated n times. If n > 1 the program will also use unnamed roads which
984 * are connected to unnamed roads if those are connected to named roads.
985 * Higher values for n mean deeper search, but reasonable values are
986 * probably between 1 and 5.
987 *
988 * These roads are then used for house number processing like the named
989 * ones. If house numbers are assigned to these roads, they are named so
990 * that address search will find them.
991 */
992 private void identifyServiceRoads() {
993 Int2ObjectOpenHashMap<String> roadNamesByNodeIds = new Int2ObjectOpenHashMap<>();
994 HashMap<MapRoad, List<Coord>> coordNodesUnnamedRoads = new HashMap<>();
995 HashSet<Integer> unclearNodeIds = new HashSet<>();
996
997 long t1 = System.currentTimeMillis();
998
999 List<MapRoad> unnamedRoads = new ArrayList<>();
1000 for (MapRoad road : allRoads){
1001 if (road.isSkipHousenumberProcessing())
1002 continue;
1003
1004 if (road.getStreet() == null){
1005 // if a road has a label but getStreet() returns null,
1006 // the road probably has a ref. We assume these are not service roads.
1007 if (road.getName() == null){
1008 unnamedRoads.add(road);
1009 List<Coord> nodes = new ArrayList<>();
1010 for (Coord co : road.getPoints()){
1011 if (co.getId() != 0)
1012 nodes.add(co);
1013 }
1014 coordNodesUnnamedRoads.put(road, nodes);
1015 }
1016 } else {
1017 identifyNodes(road.getPoints(), road.getStreet(), roadNamesByNodeIds, unclearNodeIds);
1018 }
1019 }
1020 int numUnnamedRoads = unnamedRoads.size();
1021 long t2 = System.currentTimeMillis();
1022 if (log.isDebugEnabled())
1023 log.debug("identifyServiceRoad step 1 took",(t2-t1),"ms, found",roadNamesByNodeIds.size(),"nodes to check and",numUnnamedRoads,"unnamed roads" );
1024 long t3 = System.currentTimeMillis();
1025 int named = 0;
1026 for (int pass = 1; pass <= nameSearchDepth; pass ++){
1027 int unnamed = 0;
1028 List<MapRoad> namedRoads = new ArrayList<>();
1029 for (int j = 0; j < unnamedRoads.size(); j++){
1030 MapRoad road = unnamedRoads.get(j);
1031 if (road == null)
1032 continue;
1033 unnamed++;
1034 List<Coord> coordNodes = coordNodesUnnamedRoads.get(road);
1035 String name = null;
1036 for (Coord co : coordNodes){
1037 if (unclearNodeIds.contains(co.getId())){
1038 name = null;
1039 unnamedRoads.set(j, null); // don't process again
1040 break;
1041 }
1042 String possibleName = roadNamesByNodeIds.get(co.getId());
1043 if (possibleName == null)
1044 continue;
1045 if (name == null)
1046 name = possibleName;
1047 else if (name.equals(possibleName) == false){
1048 name = null;
1049 unnamedRoads.set(j, null); // don't process again
1050 break;
1051 }
1052 }
1053 if (name != null){
1054 named++;
1055 road.setStreet(name);
1056 namedRoads.add(road);
1057 unnamedRoads.set(j, null); // don't process again
1058 }
1059 }
1060 for (MapRoad road : namedRoads){
1061 road.setNamedByHousenumberProcessing(true);
1062 String name = road.getStreet();
1063 if (log.isDebugEnabled())
1064 log.debug("pass",pass,"using unnamed road for housenumber processing,id=",road.getRoadDef().getId(),":",name);
1065 List<Coord> coordNodes = coordNodesUnnamedRoads.get(road);
1066 identifyNodes(coordNodes, name, roadNamesByNodeIds, unclearNodeIds);
1067 }
1068
1069 if (namedRoads.isEmpty())
1070 break;
1071 if (log.isDebugEnabled())
1072 log.debug("pass",pass,unnamed,named);
1073 }
1074 long t4 = System.currentTimeMillis();
1075 if (log.isDebugEnabled()){
1076 log.debug("indentifyServiceRoad step 2 took",(t4-t3),"ms, found a name for",named,"of",numUnnamedRoads,"roads" );
1077 }
1078 return;
1079 }
1080
1081 private void identifyNodes(List<Coord> roadPoints,
1082 String streetName, Int2ObjectOpenHashMap<String> roadNamesByNodeIds, HashSet<Integer> unclearNodes) {
1083 for (Coord co : roadPoints){
1084 if (co.getId() != 0){
1085 String prevName = roadNamesByNodeIds.put(co.getId(), streetName);
1086 if (prevName != null){
1087 if (prevName.equals(streetName) == false)
1088 unclearNodes.add(co.getId());
1089 }
1090 }
1091 }
1092 }
1093
1094
1095 /**
1096 * Find house number nodes which are parts of addr:interpolation ways.
1097 * Check each addr:interpolation way for plausibility.
1098 * If the check is OK, interpolate the numbers, if not, ignore
1099 * also the numbers connected to the implausible way.
1100 *
1101 * XXX: Known problem: Doesn't work well when the road was
1102 * clipped at the tile boundary.
1103 * @param streetName
1104 * @param housesNearCluster
1105 * @param roadsInCluster
1106 * @param road2HousenumberRoadMap
1107 * @param interpolationInfos
1108 */
1109 private void useInterpolationInfo(String streetName,
1110 List<HousenumberRoad> roadsInCluster, Map<MapRoad, HousenumberRoad> road2HousenumberRoadMap) {
1111 List<HousenumberIvl> interpolationInfos = interpolationWays.get(streetName);
1112 if (interpolationInfos.isEmpty())
1113 return;
1114 List<HousenumberMatch> housesWithIvlInfo = new ArrayList<>();
1115 for (HousenumberRoad hnr : roadsInCluster){
1116 for (HousenumberMatch house : hnr.getHouses()){
1117 if (house.getIntervalInfoRefs() > 0)
1118 housesWithIvlInfo.add(house);
1119 }
1120 }
1121 if (housesWithIvlInfo.isEmpty())
1122 return;
2721123
273 houseNumbers.clear();
274 roadByNames.clear();
275 roads.clear();
276 }
277
1124 HashSet<String> simpleDupCheckSet = new HashSet<>();
1125 HashSet<HousenumberIvl> badIvls = new HashSet<>();
1126 Long2ObjectOpenHashMap<HousenumberIvl> id2IvlMap = new Long2ObjectOpenHashMap<>();
1127 Int2ObjectOpenHashMap<HousenumberMatch> interpolatedNumbers = new Int2ObjectOpenHashMap<>();
1128 Int2ObjectOpenHashMap<HousenumberMatch> existingNumbers = new Int2ObjectOpenHashMap<>();
1129 HashMap<HousenumberIvl, List<HousenumberMatch>> housesToAdd = new LinkedHashMap<>();
1130
1131 for (HousenumberRoad hnr : roadsInCluster){
1132 for (HousenumberMatch house : hnr.getHouses())
1133 existingNumbers.put(house.getHousenumber(), house);
1134 }
1135
1136 int inCluster = 0;
1137 boolean allOK = true;
1138 for (HousenumberIvl hivl : interpolationInfos){
1139 if (hivl.inCluster(housesWithIvlInfo) == false || hivl.ignoreForInterpolation())
1140 continue;
1141 ++inCluster;
1142 String hivlDesc = hivl.getDesc();
1143 if (simpleDupCheckSet.contains(hivlDesc)){
1144 // happens often in Canada (CanVec imports): two or more addr:interpolation ways with similar meaning
1145 // sometimes at completely different road parts, sometimes at exactly the same
1146 log.warn("found additional addr:interpolation way with same meaning, is ignored:",streetName, hivl);
1147 badIvls.add(hivl);
1148 allOK = false;
1149 continue;
1150 }
1151 simpleDupCheckSet.add(hivlDesc);
1152
1153 id2IvlMap.put(hivl.getId(), hivl);
1154 List<HousenumberMatch> interpolatedHouses = hivl.getInterpolatedHouses();
1155 if (interpolatedHouses.isEmpty() == false){
1156 if (interpolatedHouses.get(0).getRoad() == null){
1157 // the interpolated houses are not all along one road
1158 findRoadForInterpolatedHouses(streetName, interpolatedHouses, roadsInCluster);
1159 }
1160
1161 boolean foundDup = false;
1162 for (HousenumberMatch house : interpolatedHouses){
1163 if (house.getRoad() == null || house.getDistance() > HousenumberIvl.MAX_INTERPOLATION_DISTANCE_TO_ROAD)
1164 continue;
1165 boolean ignoreGenOnly = false;
1166 HousenumberMatch old = interpolatedNumbers.put(house.getHousenumber(), house);
1167 if (old == null){
1168 ignoreGenOnly = true;
1169 old = existingNumbers.get(house.getHousenumber());
1170 }
1171 if (old != null){
1172 // forget both or only one ? Which one?
1173 house.setIgnored(true);
1174 if (old.getLocation().distance(house.getLocation()) > 5){
1175 foundDup = true;
1176 if (!ignoreGenOnly){
1177 old.setIgnored(true);
1178 long ivlId = old.getElement().getOriginalId();
1179 HousenumberIvl bad = id2IvlMap.get(ivlId);
1180 if (bad != null)
1181 badIvls.add(bad);
1182 }
1183 }
1184 }
1185 }
1186 if (foundDup)
1187 badIvls.add(hivl);
1188 else
1189 housesToAdd.put(hivl, interpolatedHouses);
1190 }
1191 }
1192 if (inCluster == 0)
1193 return;
1194 for (HousenumberIvl badIvl: badIvls){
1195 allOK = false;
1196 badIvl.ignoreNodes();
1197 housesToAdd.remove(badIvl);
1198 }
1199 Iterator<Entry<HousenumberIvl, List<HousenumberMatch>>> iter = housesToAdd.entrySet().iterator();
1200 while (iter.hasNext()){
1201 Entry<HousenumberIvl, List<HousenumberMatch>> entry = iter.next();
1202 if (log.isInfoEnabled())
1203 log.info("using generated house numbers from addr:interpolation way",entry.getKey());
1204 for (HousenumberMatch house : entry.getValue()){
1205 if (house.getRoad() != null && house.isIgnored() == false){
1206 HousenumberRoad hnr = road2HousenumberRoadMap.get(house.getRoad());
1207 if (hnr == null){
1208 log.error("internal error: found no housenumber road for interpolated house",house.getElement().toBrowseURL());
1209 continue;
1210 }
1211 hnr.addHouse(house);
1212 }
1213 }
1214 }
1215 if (log.isDebugEnabled()){
1216 if (allOK)
1217 log.debug("found no problems with interpolated numbers from addr:interpolations ways for roads with name",streetName);
1218 else
1219 log.debug("found problems with interpolated numbers from addr:interpolations ways for roads with name",streetName);
1220 }
1221 }
1222
1223 private static void findRoadForInterpolatedHouses(String streetName,
1224 List<HousenumberMatch> houses,
1225 List<HousenumberRoad> roadsInCluster) {
1226 if (houses.isEmpty())
1227 return;
1228 Collections.sort(houses, new HousenumberMatchByNumComparator());
1229
1230 HousenumberMatch prev = null;
1231 for (HousenumberMatch house : houses) {
1232 if (house.isIgnored())
1233 continue;
1234 house.setDistance(Double.POSITIVE_INFINITY); // make sure that we don't use an old match
1235 house.setRoad(null);
1236 List<HousenumberMatch> matches = new ArrayList<>();
1237 for (HousenumberRoad hnr : roadsInCluster){
1238 MapRoad r = hnr.getRoad();
1239 // make sure that we use the street info if available
1240 if (house.getPlace() != null){
1241 if (house.getStreet() != null && r.getStreet() != null && house.getStreet().equals(r.getStreet()) == false)
1242 continue;
1243 }
1244 HousenumberMatch test = new HousenumberMatch(house);
1245 findClosestRoadSegment(test, r);
1246 if (test.getRoad() != null && test.getGroup() != null || test.getDistance() < MAX_DISTANCE_TO_ROAD){
1247 matches.add(test);
1248 }
1249 }
1250 if (matches.isEmpty()){
1251 house.setIgnored(true);
1252 continue;
1253 }
1254
1255
1256 HousenumberMatch closest, best;
1257 best = closest = matches.get(0);
1258
1259 if (matches.size() > 1){
1260 // multiple roads, we assume that the closest is the best
1261 // but we may have to check the alternatives as well
1262
1263 Collections.sort(matches, new HousenumberGenerator.HousenumberMatchByDistComparator());
1264 closest = matches.get(0);
1265 best = checkAngle(closest, matches);
1266 }
1267 house.setDistance(best.getDistance());
1268 house.setSegmentFrac(best.getSegmentFrac());
1269 house.setRoad(best.getRoad());
1270 house.setSegment(best.getSegment());
1271 for (HousenumberMatch altHouse : matches){
1272 if (altHouse.getRoad() != best.getRoad() && altHouse.getDistance() < MAX_DISTANCE_TO_ROAD)
1273 house.addAlternativeRoad(altHouse.getRoad());
1274 }
1275
1276 if (house.getRoad() == null) {
1277 house.setIgnored(true);
1278 } else {
1279 house.calcRoadSide();
1280 }
1281 // plausibility check for duplicate house numbers
1282 if (prev != null && prev.getHousenumber() == house.getHousenumber()){
1283 // duplicate number (e.g. 10 and 10 or 10 and 10A or 10A and 10B)
1284 if (prev.getSign().equals(house.getSign())){
1285 prev.setDuplicate(true);
1286 house.setDuplicate(true);
1287 }
1288 }
1289
1290 if (house.getRoad() == null) {
1291 if (house.isIgnored() == false)
1292 log.warn("found no plausible road for house number element",house.getElement().toBrowseURL(),"(",streetName,house.getSign(),")");
1293 }
1294 if (!house.isIgnored())
1295 prev = house;
1296 }
1297 }
1298
1299
1300 private static void markSimpleDuplicates(String streetName, List<HousenumberMatch> housesNearCluster) {
1301 List<HousenumberMatch> sortedHouses = new ArrayList<>(housesNearCluster);
1302 Collections.sort(sortedHouses, new HousenumberMatchByNumComparator());
1303 int n = sortedHouses.size();
1304 for (int i = 1; i < n; i++){
1305 HousenumberMatch house1 = sortedHouses.get(i-1);
1306 if (house1.isIgnored())
1307 continue;
1308 HousenumberMatch house2 = sortedHouses.get(i);
1309 if (house2.isIgnored())
1310 continue;
1311 if (house1.getHousenumber() != house2.getHousenumber())
1312 continue;
1313 if (house1.getRoad() == house2.getRoad()){
1314 if (house1.isFarDuplicate())
1315 house2.setFarDuplicate(true);
1316 continue; // handled later
1317 }
1318 // we have two equal house numbers in different roads
1319 // check if they should be treated alike
1320 boolean markFarDup = false;
1321 double dist = house1.getLocation().distance(house2.getLocation());
1322 if (dist > 100)
1323 markFarDup = true;
1324 else {
1325 CityInfo city1 = house1.getCityInfo();
1326 CityInfo city2 = house2.getCityInfo();
1327 if (city1 != null && city1.equals(city2) == false){
1328 markFarDup = true;
1329 }
1330 }
1331 if (markFarDup){
1332 if (log.isDebugEnabled())
1333 log.debug("keeping duplicate numbers assigned to different roads in cluster ", streetName, house1,house2);
1334 house1.setFarDuplicate(true);
1335 house2.setFarDuplicate(true);
1336 continue;
1337 }
1338 boolean ignore2nd = false;
1339 if (dist < 30){
1340 ignore2nd = true;
1341 } else {
1342 Coord c1s = house1.getRoad().getPoints().get(house1.getSegment());
1343 Coord c1e = house1.getRoad().getPoints().get(house1.getSegment() + 1);
1344 Coord c2s = house2.getRoad().getPoints().get(house2.getSegment());
1345 Coord c2e = house2.getRoad().getPoints().get(house2.getSegment() + 1);
1346 if (c1s == c2s || c1s == c2e || c1e == c2s || c1e == c2e){
1347 // roads are directly connected
1348 ignore2nd = true;
1349 }
1350 }
1351 if (ignore2nd){
1352 house2.setIgnored(true);
1353 if (log.isDebugEnabled()){
1354 if (house1.getSign().equals(house2.getSign()))
1355 log.debug("duplicate number is ignored",streetName,house2.getSign(),house2.getElement().toBrowseURL() );
1356 else
1357 log.info("using",streetName,house1.getSign(), "in favor of",house2.getSign(),"as target for address search");
1358 }
1359 } else {
1360 if (log.isDebugEnabled())
1361 log.debug("keeping duplicate numbers assigned to different roads in cluster ", streetName, house1,house2);
1362 house1.setFarDuplicate(true);
1363 house2.setFarDuplicate(true);
1364 }
1365 }
1366 }
1367
1368
1369 /**
1370 * If we find a sequence of house numbers like 1,3,5 or 1,2,3
1371 * where the house in the middle is assigned to a different road,
1372 * it is likely that this match is wrong.
1373 * This typically happens when a house is rather far away from two
1374 * possible roads, but a bit closer to the wrong match. The two roads
1375 * typically form an L, U, or O shape.
1376 * @param streetName common name tag (for debugging)
1377 * @param sortedHouses house number elements sorted by number
1378 * @param roadNumbers the existing map which should be corrected
1379 */
1380 private static void checkDubiousRoadMatches(String streetName,
1381 List<HousenumberMatch> sortedHouses,
1382 MultiHashMap<MapRoad, HousenumberMatch> roadNumbers) {
1383 int n = sortedHouses.size();
1384 for (int pos1 = 0; pos1 < n; pos1++){
1385 HousenumberMatch house1 = sortedHouses.get(pos1);
1386 if (house1.isIgnored() || house1.hasAlternativeRoad() == false)
1387 continue;
1388 int confirmed = 0;
1389 int falsified = 0;
1390 int pos2 = pos1;
1391 HousenumberMatch bestAlternative = null;
1392 double bestAlternativeDist = Double.POSITIVE_INFINITY;
1393
1394 while (pos2 > 0){
1395 HousenumberMatch house2 = sortedHouses.get(pos2);
1396 if (house1.getHousenumber() - house2.getHousenumber() > 2)
1397 break;
1398 --pos2;
1399 }
1400 for (; pos2 < n; pos2++){
1401 if (confirmed > 0)
1402 break;
1403 if (pos2 == pos1)
1404 continue;
1405 HousenumberMatch house2 = sortedHouses.get(pos2);
1406 if (house2.isIgnored() || house2.getRoad() == null)
1407 continue;
1408 int deltaNum = house2.getHousenumber() - house1.getHousenumber();
1409 if (deltaNum > 2)
1410 break;
1411 if (deltaNum < -2)
1412 continue;
1413 double distHouses = house1.getLocation().distance(house2.getLocation());
1414 if (house2.getRoad() == house1.getRoad()){
1415 if (Math.abs(house1.getSegment() - house2.getSegment()) < 2){
1416 if (distHouses < 1.5 * bestAlternativeDist)
1417 confirmed++;
1418 }
1419 continue;
1420 }
1421
1422 Coord c1 = house2.getRoad().getPoints().get(house2.getSegment());
1423 Coord c2 = house2.getRoad().getPoints().get(house2.getSegment()+1);
1424 double frac2 = getFrac(c1,c2, house1.getLocation());
1425 double dist2 = distanceToSegment(c1,c2,house1.getLocation(),frac2);
1426 if (distHouses > dist2)
1427 continue;
1428 if (distHouses > house1.getDistance())
1429 continue;
1430 Coord c3 = house1.getRoad().getPoints().get(house1.getSegment());
1431 Coord c4 = house1.getRoad().getPoints().get(house1.getSegment()+1);
1432 if (c1 == c3 && Math.abs(Utils.getAngle(c2, c1, c4)) < 10 ||
1433 c1 == c4 && Math.abs(Utils.getAngle(c2, c1, c3)) < 10 ||
1434 c2 == c3 && Math.abs(Utils.getAngle(c1, c2, c4)) < 10 ||
1435 c2 == c4 && Math.abs(Utils.getAngle(c1, c2, c3)) < 10){
1436 confirmed++;
1437 continue;
1438 }
1439 ++falsified;
1440 if (bestAlternative == null || dist2 < bestAlternativeDist){
1441 bestAlternative = house2;
1442 bestAlternativeDist = dist2;
1443 }
1444 if (log.isDebugEnabled())
1445 log.debug("road check house-1:",house1.getRoad(),house1,house1.getDistance(),",house-2:", house2.getRoad(),house2,house2.getDistance(),distHouses,dist2,frac2,"house-1 is falsified");
1446 }
1447 if (confirmed == 0 && falsified > 0){
1448 if (log.isInfoEnabled())
1449 log.info("house number element assigned to road",house1.getRoad(),house1,house1.getElement().toBrowseURL(),"is closer to more plausible houses at road",bestAlternative.getRoad());
1450 roadNumbers.removeMapping(house1.getRoad(), house1);
1451 Coord c1 = bestAlternative.getRoad().getPoints().get(bestAlternative.getSegment());
1452 Coord c2 = bestAlternative.getRoad().getPoints().get(bestAlternative.getSegment()+1);
1453 double frac2 = getFrac(c1,c2, house1.getLocation());
1454 double dist2 = distanceToSegment(c1,c2,house1.getLocation(),frac2);
1455 if (dist2 >= MAX_DISTANCE_TO_ROAD){
1456 log.info("house number element assigned to road",house1.getRoad(),house1,house1.getElement().toBrowseURL(),"is too far from more plausible road, is ignored");
1457 house1.setIgnored(true);
1458 } else {
1459 house1.setRoad(bestAlternative.getRoad());
1460 house1.setSegment(bestAlternative.getSegment());
1461 house1.setSegmentFrac(frac2);
1462 house1.setDistance(dist2);
1463 house1.setLeft(isLeft(c1, c2, house1.getLocation()));
1464 roadNumbers.add(house1.getRoad(), house1);
1465 }
1466 } else if (confirmed == 0 && house1.isDuplicate()){
1467 // special ?
1468 }
1469 }
1470 }
1471
1472 public static void findClosestRoadSegment(HousenumberMatch house, MapRoad r) {
1473 findClosestRoadSegment(house, r, 0, r.getPoints().size());
1474 }
1475
1476 /**
1477 * Fill/overwrite the fields in house which depend on the assigned road.
1478 */
1479 public static void findClosestRoadSegment(HousenumberMatch house, MapRoad r, int firstSeg, int stopSeg) {
1480 Coord cx = house.getLocation();
1481 double oldDist = house.getDistance();
1482 MapRoad oldRoad = house.getRoad();
1483 house.setRoad(null);
1484 house.setDistance(Double.POSITIVE_INFINITY);
1485 boolean foundGroupLink = false;
1486 int end = Math.min(r.getPoints().size(), stopSeg+1);
1487 for (int node = firstSeg; node + 1 < end; node++){
1488 Coord c1 = r.getPoints().get(node);
1489 Coord c2 = r.getPoints().get(node + 1);
1490 double frac = getFrac(c1, c2, cx);
1491 double dist = distanceToSegment(c1,c2,cx,frac);
1492 if (house.getGroup() != null && house.getGroup().linkNode == c1){
1493 if (c1.highPrecEquals(c2) == false){
1494 log.debug("block doesn't have zero length segment! Road:",r,house);
1495 }
1496 foundGroupLink = true;
1497 house.setDistance(dist);
1498 house.setSegmentFrac(frac);
1499 house.setRoad(r);
1500 house.setSegment(node);
1501 break;
1502 } else if (dist < house.getDistance()) {
1503 house.setDistance(dist);
1504 house.setSegmentFrac(frac);
1505 house.setRoad(r);
1506 house.setSegment(node);
1507 }
1508 }
1509
1510 if (house.getGroup() != null && house.getGroup().linkNode != null && foundGroupLink == false){
1511 log.debug(r,house,"has a group but the link was not found, should only happen after split of zero-length-segment");
1512 }
1513 if (oldRoad == r){
1514 if (house.getDistance() > MAX_DISTANCE_TO_ROAD + 2.5 && oldDist <= MAX_DISTANCE_TO_ROAD ){
1515 log.warn("line distorted? Road segment was moved by more than",
1516 String.format("%.2f m", 2.5), ", from address", r, house.getSign());
1517 }
1518 }
1519 }
1520
1521 private static boolean hasChanges(
1522 List<HousenumberRoad> housenumberRoads) {
1523 for (HousenumberRoad hnr : housenumberRoads){
1524 if (hnr.isChanged())
1525 return true;
1526 }
1527 return false;
1528 }
1529
1530 /**
1531 *
1532 * @param housenumberRoads
1533 */
1534 private static void checkWrongRoadAssignmments(List<HousenumberRoad> housenumberRoads) {
1535 if (housenumberRoads.size() < 2)
1536 return;
1537 for (int loop = 0; loop < 10; loop++){
1538 boolean changed = false;
1539 for (int i = 0; i+1 < housenumberRoads.size(); i++){
1540 HousenumberRoad hnr1 = housenumberRoads.get(i);
1541 hnr1.setChanged(false);
1542 for (int j = i+1; j < housenumberRoads.size(); j++){
1543 HousenumberRoad hnr2 = housenumberRoads.get(j);
1544 hnr2.setChanged(false);
1545 hnr1.checkWrongRoadAssignmments(hnr2);
1546 if (hnr1.isChanged()){
1547 changed = true;
1548 hnr1.checkIntervals();
1549 }
1550 if (hnr2.isChanged()){
1551 changed = true;
1552 hnr2.checkIntervals();
1553 }
1554 }
1555 }
1556 if (!changed)
1557 return;
1558 }
1559 }
1560
2781561 /**
2791562 * Sorts house numbers by roads, road segments and position of the house number.
2801563 * @author WanMil
2811564 */
282 private static class HousenumberMatchComparator implements Comparator<HousenumberMatch> {
1565 public static class HousenumberMatchByPosComparator implements Comparator<HousenumberMatch> {
2831566
2841567 public int compare(HousenumberMatch o1, HousenumberMatch o2) {
2851568 if (o1 == o2) {
2861569 return 0;
2871570 }
288
1571 if (o1.getRoad() == null || o2.getRoad() == null){
1572 log.error("road is null in sort comparator",o1,o2);
1573 throw new MapFailedException("internal error in housenumber processing");
1574 }
2891575 if (o1.getRoad() != o2.getRoad()) {
290 return o1.getRoad().hashCode() - o2.getRoad().hashCode();
1576 // should not happen
1577 return o1.getRoad().getRoadId() - o2.getRoad().getRoadId();
2911578 }
2921579
2931580 int dSegment = o1.getSegment() - o2.getSegment();
3001587 return (int)Math.signum(dFrac);
3011588 }
3021589
1590 int d = o1.getHousenumber() - o2.getHousenumber();
1591 if (d != 0)
1592 return d;
1593 d = o1.getSign().compareTo(o2.getSign());
1594 if (d != 0)
1595 return d;
1596 return 0;
1597 }
1598
1599 }
1600
1601 /**
1602 * Sorts house numbers by house number and segment
1603 * @author Gerd Petermann
1604 */
1605 public static class HousenumberMatchByNumComparator implements Comparator<HousenumberMatch> {
1606 public int compare(HousenumberMatch o1, HousenumberMatch o2) {
1607 if (o1 == o2)
1608 return 0;
1609 int d = o1.getHousenumber() - o2.getHousenumber();
1610 if (d != 0)
1611 return d;
1612 d = o1.getSign().compareTo(o2.getSign());
1613 if (d != 0)
1614 return d;
1615 d = o1.getSegment() - o2.getSegment();
1616 if (d != 0)
1617 return d;
3031618 double dDist = o1.getDistance() - o2.getDistance();
3041619 if (dDist != 0d) {
3051620 return (int)Math.signum(dDist);
3061621 }
307
1622 if (d != 0)
1623 return d;
1624 d = Long.compare(o1.getElement().getId(), o2.getElement().getId());
1625 return d;
1626 }
1627 }
1628 /**
1629 * Sorts house numbers by distance. If eqaul, compare segment and road to produce
1630 * predictable results.
1631 * @author Gerd Petermann
1632 */
1633 public static class HousenumberMatchByDistComparator implements Comparator<HousenumberMatch> {
1634 public int compare(HousenumberMatch o1, HousenumberMatch o2) {
1635 if (o1 == o2)
1636 return 0;
1637 int d = Double.compare(o1.getDistance(), o2.getDistance());
1638 if (d != 0)
1639 return d;
1640 d = Integer.compare(o1.getSegment(), o2.getSegment());
1641 if (d != 0)
1642 return d;
1643 d = Integer.compare(o1.getRoad().getRoadId(), o2.getRoad().getRoadId());
1644 if (d != 0)
1645 return d;
3081646 return 0;
3091647 }
1648 }
1649
1650 private static List<HousenumberMatch> checkPlausibility(String streetName, List<MapRoad> clusteredRoads,
1651 List<HousenumberMatch> housesNearCluster) {
1652 int countError = 0;
1653 int countTested = 0;
1654 List<HousenumberMatch> failed = new ArrayList<>();
1655 Int2IntOpenHashMap tested = new Int2IntOpenHashMap();
1656 tested.defaultReturnValue(-1);
1657 for (HousenumberMatch house : housesNearCluster){
1658 if (house.isIgnored())
1659 continue;
1660 ++countTested;
1661 int num = house.getHousenumber();
1662 int countPlaces = 0;
1663 int countRoads = 0;
1664 int prevRes = tested.get(num);
1665 if (prevRes == 0)
1666 continue;
1667 boolean reported = false;
1668 for (MapRoad r : clusteredRoads){
1669 int countMatches = checkRoad(r, house.getHousenumber());
1670 if (countMatches == 0)
1671 continue;
1672 countRoads++;
1673 if (countMatches > 1){
1674 log.warn(streetName,house.getSign(),house.getElement().toBrowseURL(),"is coded in",countMatches,"different road segments");
1675 reported = true;
1676 }
1677 countPlaces += countMatches;
1678 }
1679 if (countPlaces == 1){
1680 tested.put(num,0);
1681 continue;
1682 }
1683 failed.add(house);
1684 ++countError;
1685
1686 if (countPlaces == 0 && house.getRoad() != null) {
1687 log.warn(streetName, house.getSign(), house.getElement().toBrowseURL(), "is not found in expected road", house.getRoad());
1688 reported = true;
1689 }
1690 if (countRoads > 1){
1691 log.warn(streetName, house.getSign(), house.getElement().toBrowseURL(), "is coded in", countRoads, "different roads");
1692 reported = true;
1693 }
1694 if (!reported)
1695 log.error(streetName, house.getSign(), house.getElement().toBrowseURL(), "unexpected result in plausibility check, counters:",countRoads, countPlaces);
1696 }
1697 if (countTested == 0)
1698 log.warn("plausibility check for road cluster found no valid numbers",clusteredRoads );
1699 else if (countError > 0)
1700 log.warn("plausibility check for road cluster failed with", countError, "detected problems:", clusteredRoads);
1701 else if (log.isInfoEnabled())
1702 log.info("plausibility check for road cluster found no problems", clusteredRoads);
1703 return failed;
1704 }
1705
1706 /**
1707 * Count all segments that contain the house number
1708 * @param r
1709 * @param hn
1710 * @return
1711 */
1712 private static int checkRoad(MapRoad r, int hn) {
1713 if (r.getNumbers() == null)
1714 return 0;
1715
1716 int matches = 0;
1717 Numbers last = null;
1718 Numbers firstMatch = null;
1719 for (Numbers numbers : r.getNumbers()){
1720 if (numbers.isEmpty())
1721 continue;
1722 int n = numbers.countMatches(hn);
1723 if (n > 0 && firstMatch == null)
1724 firstMatch = numbers;
1725
1726 if (n == 1 && matches > 0){
1727 if (last.getLeftEnd() == numbers.getLeftStart() && last.getLeftEnd() == hn ||
1728 last.getRightEnd() == numbers.getRightStart() && last.getRightEnd() == hn ||
1729 last.getLeftStart() == numbers.getLeftEnd() && last.getLeftStart() == hn||
1730 last.getRightStart() == numbers.getRightEnd() && last.getRightStart() == hn){
1731 n = 0; // intervals are overlapping, probably two houses (e.g. 2a,2b) at a T junction
1732 }
1733 }
1734
1735 matches += n;
1736 last = numbers;
1737 }
1738 return matches;
1739 }
1740
1741 /**
1742 * If the closest point to a road is a junction, try to find the road
1743 * segment that forms a right angle with the house
1744 * @param closestMatch one match that is closest to the node
1745 * @param otherMatches the other matches with the same distance
1746 * @return the best match
1747 */
1748 private static HousenumberMatch checkAngle(HousenumberMatch closestMatch,
1749 List<HousenumberMatch> otherMatches) {
3101750
311 }
312
313 /**
314 * Matches the house numbers of one street name to its OSM elements and roads.
315 * @param streetname name of street
316 * @param elements a list of OSM elements belonging to this street name
317 * @param roads a list of roads with the given street name
318 */
319 private static void match(String streetname, List<Element> elements, List<MapRoad> roads) {
320 List<HousenumberMatch> numbersList = new ArrayList<HousenumberMatch>(
321 elements.size());
322 for (Element element : elements) {
323 try {
324 HousenumberMatch match = new HousenumberMatch(element);
325 if (match.getLocation() == null) {
326 // there has been a report that indicates match.getLocation() == null
327 // could not reproduce so far but catching it here with some additional
328 // information. (WanMil)
329 log.error("OSM element seems to have no point.");
330 log.error("Element: "+element.toBrowseURL()+" " +element);
331 log.error("Please report on the mkgmap mailing list.");
332 log.error("Continue creating the map. This should be possible without a problem.");
333 } else {
334 numbersList.add(match);
335 }
336 } catch (IllegalArgumentException exp) {
337 log.debug(exp);
338 }
339 }
340
341 MultiHashMap<MapRoad, HousenumberMatch> roadNumbers = new MultiHashMap<MapRoad, HousenumberMatch>();
342
343 for (HousenumberMatch n : numbersList) {
344
345 for (MapRoad r : roads) {
346 int node = -1;
347 Coord c1 = null;
348 for (Coord c2 : r.getPoints()) {
349 if (c1 != null) {
350 Coord cx = n.getLocation();
351 double frac = getFrac(c1, c2, cx);
352 double dist = distanceToSegment(c1,c2,cx,frac);
353 if (dist <= MAX_DISTANCE_TO_ROAD && dist < n.getDistance()) {
354 n.setDistance(dist);
355 n.setSegmentFrac(frac);
356 n.setRoad(r);
357 n.setSegment(node);
358 }
359 }
360 c1 = c2;
361 node++;
362 }
363 }
364
365 if (n.getRoad() != null) {
366 Coord c1 = n.getRoad().getPoints().get(n.getSegment());
367 Coord c2 = n.getRoad().getPoints().get(n.getSegment()+1);
368
369 n.setLeft(isLeft(c1, c2, n.getLocation()));
370 roadNumbers.add(n.getRoad(), n);
371 }
372 }
373
374 // go through all roads and apply the house numbers
375 for (Entry<MapRoad, List<HousenumberMatch>> roadX : roadNumbers.entrySet()) {
376 MapRoad r = roadX.getKey();
377 if (roadX.getValue().isEmpty()) {
1751 if (otherMatches.isEmpty())
1752 return closestMatch;
1753 HousenumberMatch bestMatch = closestMatch;
1754 for (HousenumberMatch alternative : otherMatches){
1755 if (alternative == closestMatch)
3781756 continue;
379 }
380
381 List<HousenumberMatch> leftNumbers = new ArrayList<HousenumberMatch>();
382 List<HousenumberMatch> rightNumbers = new ArrayList<HousenumberMatch>();
383 for (HousenumberMatch hr : roadX.getValue()) {
384 if (hr.isLeft()) {
385 leftNumbers.add(hr);
386 } else {
387 rightNumbers.add(hr);
388 }
389 }
390
391 Collections.sort(leftNumbers, new HousenumberMatchComparator());
392 Collections.sort(rightNumbers, new HousenumberMatchComparator());
393
394 List<Numbers> numbersListing = new ArrayList<Numbers>();
395
396 log.info("Housenumbers for",r.getName(),r.getCity());
397 log.info("Numbers:",roadX.getValue());
398
399 int n = 0;
400 int nodeIndex = 0;
401 int lastRoutableNodeIndex = 0;
402 for (Coord p : r.getPoints()) {
403 if (n == 0) {
404 assert p instanceof CoordNode;
405 }
406
407 // An ordinary point in the road.
408 if (p.getId() == 0) {
409 n++;
410 continue;
411 }
412
413 // The first time round, this is guaranteed to be a CoordNode
414 if (n == 0) {
415 nodeIndex++;
416 n++;
417 continue;
418 }
419
420 // Now we have a CoordNode and it is not the first one.
421 Numbers numbers = new Numbers();
422 numbers.setNodeNumber(0);
423 numbers.setRnodNumber(lastRoutableNodeIndex);
424
425 applyNumbers(numbers,leftNumbers,n,true);
426 applyNumbers(numbers,rightNumbers,n,false);
427
428 if (log.isInfoEnabled()) {
429 log.info("Left: ",numbers.getLeftNumberStyle(),numbers.getRnodNumber(),"Start:",numbers.getLeftStart(),"End:",numbers.getLeftEnd(), "Remaining: "+leftNumbers);
430 log.info("Right:",numbers.getRightNumberStyle(),numbers.getRnodNumber(),"Start:",numbers.getRightStart(),"End:",numbers.getRightEnd(), "Remaining: "+rightNumbers);
431 }
432
433 numbersListing.add(numbers);
434
435 lastRoutableNodeIndex = nodeIndex;
436 nodeIndex++;
437 n++;
438 }
439
440 r.setNumbers(numbersListing);
441 }
442 }
443
444 /**
445 * Apply the given house numbers to the numbers object.
446 * @param numbers the numbers object to be configured
447 * @param housenumbers a list of house numbers
448 * @param maxSegment the highest segment number to use
449 * @param left {@code true} the left side of the street; {@code false} the right side of the street
450 */
451 private static void applyNumbers(Numbers numbers, List<HousenumberMatch> housenumbers, int maxSegment, boolean left) {
452 NumberStyle style = NumberStyle.NONE;
453
454 if (housenumbers.isEmpty() == false) {
455 // get the sublist of housenumbers
456 int maxN = -1;
457 boolean even = false;
458 boolean odd = false;
459 for (int i = 0; i< housenumbers.size(); i++) {
460 HousenumberMatch hn = housenumbers.get(i);
461 if (hn.getSegment() >= maxSegment) {
462 break;
463 } else {
464 maxN = i;
465 if (hn.getHousenumber() % 2 == 0) {
466 even = true;
467 } else {
468 odd = true;
469 }
470 }
471 }
472
473 if (maxN >= 0) {
474 if (even && odd) {
475 style = NumberStyle.BOTH;
476 } else if (even) {
477 style = NumberStyle.EVEN;
478 } else {
479 style = NumberStyle.ODD;
480 }
481
482 int start = housenumbers.get(0).getHousenumber();
483 int end = housenumbers.get(maxN).getHousenumber();
484 if (left) {
485 numbers.setLeftStart(start);
486 numbers.setLeftEnd(end);
487 } else {
488 numbers.setRightStart(start);
489 numbers.setRightEnd(end);
490 }
491
492 housenumbers.subList(0, maxN+1).clear();
493 }
494 }
495
496 if (left)
497 numbers.setLeftNumberStyle(style);
498 else
499 numbers.setRightNumberStyle(style);
500
501 }
502
1757 if (closestMatch.getDistance() < alternative.getDistance())
1758 break;
1759 // a house has the same distance to different road objects
1760 // if this happens at a T-junction, make sure not to use the end of the wrong road
1761 Coord c1 = closestMatch.getRoad().getPoints().get(closestMatch.getSegment());
1762 Coord c2 = closestMatch.getRoad().getPoints().get(closestMatch.getSegment()+1);
1763 Coord cx = closestMatch.getLocation();
1764 double dist = closestMatch.getDistance();
1765 double dist1 = cx.distance(c1);
1766 double angle, altAngle;
1767 if (dist1 == dist)
1768 angle = Utils.getAngle(c2, c1, cx);
1769 else
1770 angle = Utils.getAngle(c1, c2, cx);
1771 Coord c3 = alternative.getRoad().getPoints().get(alternative.getSegment());
1772 Coord c4 = alternative.getRoad().getPoints().get(alternative.getSegment()+1);
1773
1774 double dist3 = cx.distance(c3);
1775 if (dist3 == dist)
1776 altAngle = Utils.getAngle(c4, c3, cx);
1777 else
1778 altAngle = Utils.getAngle(c3, c4, cx);
1779 double delta = 90 - Math.abs(angle);
1780 double deltaAlt = 90 - Math.abs(altAngle);
1781 if (delta > deltaAlt){
1782 bestMatch = alternative;
1783 c1 = c3;
1784 c2 = c4;
1785 }
1786 }
1787 if (log.isDebugEnabled()){
1788 if (closestMatch.getRoad() != bestMatch.getRoad()){
1789 log.debug("check angle: using road",bestMatch.getRoad().getRoadDef().getId(),"instead of",closestMatch.getRoad().getRoadDef().getId(),"for house number",bestMatch.getSign(),bestMatch.getElement().toBrowseURL());
1790 } else if (closestMatch != bestMatch){
1791 log.debug("check angle: using road segment",bestMatch.getSegment(),"instead of",closestMatch.getSegment(),"for house number element",bestMatch.getElement().toBrowseURL());
1792 }
1793 }
1794 return bestMatch;
1795 }
1796
5031797 /**
5041798 * Evaluates if the given point lies on the left side of the line spanned by spoint1 and spoint2.
5051799 * @param spoint1 first point of line
5071801 * @param point the point to check
5081802 * @return {@code true} point lies on the left side; {@code false} point lies on the right side
5091803 */
510 private static boolean isLeft(Coord spoint1, Coord spoint2, Coord point) {
1804 public static boolean isLeft(Coord spoint1, Coord spoint2, Coord point) {
1805 if (spoint1.distance(spoint2) == 0){
1806 log.warn("road segment length is 0 in left/right evaluation");
1807 }
1808
5111809 return ((spoint2.getHighPrecLon() - spoint1.getHighPrecLon())
5121810 * (point.getHighPrecLat() - spoint1.getHighPrecLat()) - (spoint2
5131811 .getHighPrecLat() - spoint1.getHighPrecLat())
5211819 * @param point point
5221820 * @return the distance in meter
5231821 */
524 private static double distanceToSegment(Coord spoint1, Coord spoint2, Coord point, double frac) {
1822 public static double distanceToSegment(Coord spoint1, Coord spoint2, Coord point, double frac) {
5251823
5261824 if (frac <= 0) {
5271825 return spoint1.distance(point);
5341832 }
5351833
5361834 /**
537 * Calculates the fraction at which the given point is closest to the line segment.
1835 * Calculates the fraction at which the given point is closest to
1836 * the infinite line going through both points.
5381837 * @param spoint1 segment point 1
5391838 * @param spoint2 segment point 2
5401839 * @param point point
541 * @return the fraction
542 */
543 private static double getFrac(Coord spoint1, Coord spoint2, Coord point) {
1840 * @return the fraction (can be <= 0 or >= 1 if the perpendicular is not on
1841 * the line segment between spoint1 and spoint2)
1842 */
1843 public static double getFrac(Coord spoint1, Coord spoint2, Coord point) {
5441844 int aLon = spoint1.getHighPrecLon();
5451845 int bLon = spoint2.getHighPrecLon();
5461846 int pLon = point.getHighPrecLon();
5641864 return (deltaLonAP * deltaLon + (pLat - aLat) * deltaLat) / (deltaLon * deltaLon + deltaLat * deltaLat);
5651865 }
5661866 }
1867
1868 /**
1869 * @param length
1870 * @return string with length, e.g. "0.23 m" or "116.12 m"
1871 */
1872 public static String formatLen(double length){
1873 return String.format("%.2f m", length);
1874 }
1875
1876
1877 private static class RoadPoint implements Locatable{
1878 final Coord p;
1879 final MapRoad r;
1880 final int segment;
1881 final int partOfSeg;
1882
1883 public RoadPoint(MapRoad road, Coord co, int s, int part) {
1884 this.p = co;
1885 this.r = road;
1886 this.segment = s;
1887 this.partOfSeg = part;
1888 }
1889 @Override
1890 public Coord getLocation() {
1891 return p;
1892 }
1893 @Override
1894 public String toString() {
1895 return r + " " + segment + " " + partOfSeg;
1896 }
1897 }
1898
1899 /**
1900 * A performance critical part:
1901 * Index all road segments to be able to find all road segments within a given range
1902 * around a point.
1903 * @author Gerd Petermann
1904 *
1905 */
1906 class RoadSegmentIndex {
1907 private final KdTree<RoadPoint> kdTree = new KdTree<>();
1908 private final Int2ObjectOpenHashMap<Set<RoadPoint>> nodeId2RoadPointMap = new Int2ObjectOpenHashMap<>();
1909 private final double range;
1910 private final double maxSegmentLength;
1911 private final double kdSearchRange;
1912
1913 public RoadSegmentIndex(List<MapRoad> roads, double rangeInMeter) {
1914 this.range = rangeInMeter;
1915 this.maxSegmentLength = range * 2 / 3;
1916 this.kdSearchRange = Math.sqrt(Math.pow(rangeInMeter, 2) + Math.pow(maxSegmentLength/2, 2));
1917 build(roads);
1918
1919 }
1920
1921 public void build(List<MapRoad> roads){
1922 for (MapRoad road : roads){
1923 if (road.isSkipHousenumberProcessing())
1924 continue;
1925 List<Coord> points = road.getPoints();
1926 if (points.size() < 2)
1927 continue;
1928
1929 List<RoadPoint> roadPoints = new ArrayList<>();
1930 RoadPoint rp;
1931 for (int i = 0; i + 1 < points.size(); i++){
1932 Coord c1 = points.get(i);
1933 Coord c2 = points.get(i + 1);
1934 int part = 0;
1935 rp = new RoadPoint(road, c1, i, part++);
1936 roadPoints.add(rp);
1937 while (true){
1938 double segLen = c1.distance(c2);
1939 double frac = maxSegmentLength / segLen;
1940 if (frac >= 1)
1941 break;
1942 // if points are not close enough, add extra point
1943 c1 = c1.makeBetweenPoint(c2, frac);
1944 rp = new RoadPoint(road, c1, i, part++);
1945 roadPoints.add(rp);
1946 segLen -= maxSegmentLength;
1947 }
1948 }
1949 int last = points.size() - 1;
1950 rp = new RoadPoint(road, points.get(last) , last, -1);
1951 roadPoints.add(rp);
1952
1953 Collections.shuffle(roadPoints);
1954 for (RoadPoint toAdd : roadPoints){
1955 int id = toAdd.p.getId();
1956 if (id == 0)
1957 kdTree.add(toAdd);
1958 else {
1959 // Coord node, add only once to KD-tree with all roads
1960 Set<RoadPoint> set = nodeId2RoadPointMap.get(id);
1961 if (set == null){
1962 set = new LinkedHashSet<>();
1963 nodeId2RoadPointMap.put(id, set);
1964 kdTree.add(toAdd);
1965 }
1966 set.add(toAdd);
1967 }
1968 }
1969 }
1970 }
1971
1972 public List<RoadPoint> getCLoseRoadPoints(HousenumberElem house){
1973 Set<RoadPoint> closeRoadPoints = kdTree.findNextPoint(house, kdSearchRange);
1974 List<RoadPoint> result = new ArrayList<>();
1975 for (RoadPoint rp : closeRoadPoints){
1976 int id = rp.p.getId();
1977 if (id != 0)
1978 result.addAll(nodeId2RoadPointMap.get(id));
1979 else
1980 result.add(rp);
1981 }
1982 return result;
1983 }
1984
1985 /**
1986 * Find closest road segment and other plausible roads for a house
1987 * @param house
1988 * @return null if no road was found, else a {@link HousenumberMatch} instance
1989 */
1990 public HousenumberMatch createHousenumberMatch(HousenumberElem house){
1991 HousenumberMatch closest = new HousenumberMatch(house);
1992 List<RoadPoint> closeRoadPoints = getCLoseRoadPoints(house);
1993 if (closeRoadPoints.isEmpty())
1994 return closest;
1995 Collections.sort(closeRoadPoints, new Comparator<RoadPoint>() {
1996 // sort by distance (smallest first)
1997 public int compare(RoadPoint o1, RoadPoint o2) {
1998 if (o1 == o2)
1999 return 0;
2000 int d = Integer.compare(o1.r.getRoadId(), o2.r.getRoadId());
2001 if (d != 0)
2002 return d;
2003 d = Integer.compare(o1.segment, o2.segment);
2004 if (d != 0)
2005 return d;
2006 return Integer.compare(o1.partOfSeg, o2.partOfSeg);
2007 }
2008 });
2009
2010 List<HousenumberMatch> matches = new ArrayList<>(40);
2011 BitSet testedSegments = new BitSet();
2012 MapRoad lastRoad = null;
2013 HousenumberMatch hnm = null;
2014 for (RoadPoint rp : closeRoadPoints){
2015 if (house.getStreet() != null && house.getStreet().equals(rp.r.getStreet()) == false){
2016 if (rp.r.getStreet() != null){
2017 continue;
2018 }
2019 }
2020 if (rp.r != lastRoad){
2021 hnm = new HousenumberMatch(house);
2022 testedSegments.clear();
2023 matches.add(hnm);
2024 lastRoad = rp.r;
2025 }
2026 double oldDist = hnm.getDistance();
2027 if (rp.partOfSeg >= 0){
2028 // rp.p is at start or before end of segment
2029 if (testedSegments.get(rp.segment) == false){
2030 testedSegments.set(rp.segment);
2031 checkSegment(hnm, rp.r, rp.segment);
2032 }
2033 }
2034 if (rp.partOfSeg < 0){
2035 // rp is at end of road, check (also) the preceding segment
2036 if (rp.segment < 1){
2037 log.error("internal error: trying to use invalid roadPoint",rp);
2038 } else if (testedSegments.get(rp.segment - 1) == false){
2039 testedSegments.set(rp.segment-1);
2040 checkSegment(hnm, rp.r, rp.segment-1);
2041 }
2042 }
2043 if (oldDist == hnm.getDistance())
2044 continue;
2045 }
2046 if (matches.isEmpty())
2047 return closest;
2048 Collections.sort(matches, new HousenumberGenerator.HousenumberMatchByDistComparator());
2049 closest = matches.get(0);
2050 closest = checkAngle(closest, matches);
2051 closest.calcRoadSide();
2052 HousenumberMatch bestMatchingName = null;
2053 if (closest.getStreet() != null && closest.getStreet().equals(closest.getRoad().getStreet()))
2054 bestMatchingName = closest;
2055 for (HousenumberMatch altHouse : matches){
2056 if (altHouse.getDistance() >= MAX_DISTANCE_TO_ROAD)
2057 break;
2058 if (altHouse.getRoad() != closest.getRoad()){
2059 if (house.getStreet() != null && altHouse.getDistance() > closest.getDistance()){
2060 if (house.getStreet().equals(altHouse.getRoad().getStreet())){
2061 if (bestMatchingName == null || bestMatchingName.getDistance() > altHouse.getDistance())
2062 bestMatchingName = altHouse;
2063 } else {
2064 if (bestMatchingName != null && altHouse.getDistance() > bestMatchingName.getDistance())
2065 continue;
2066 }
2067 }
2068 closest.addAlternativeRoad(altHouse.getRoad());
2069 }
2070 }
2071 if (closest == bestMatchingName || bestMatchingName == null || bestMatchingName.getDistance() > MAX_DISTANCE_TO_ROAD)
2072 return closest;
2073
2074 double ratio = closest.getDistance() / bestMatchingName.getDistance();
2075 if (ratio < 0.25)
2076 return closest;
2077 HousenumberMatch best = closest;
2078 if (ratio > 0.75){
2079 // prefer the road with the matching name
2080 for (MapRoad r : closest.getAlternativeRoads()){
2081 if (house.getStreet().equals(r.getStreet()))
2082 bestMatchingName.addAlternativeRoad(r);
2083 }
2084 best = bestMatchingName;
2085 best.calcRoadSide();
2086 } else {
2087 if (log.isDebugEnabled()){
2088 log.debug("further checks needed for address", closest.getStreet(), closest.getSign(), closest.getElement().toBrowseURL(),
2089 formatLen(closest.getDistance()), formatLen(bestMatchingName.getDistance()));
2090 }
2091
2092 }
2093 return best;
2094 }
2095 }
5672096 }
2097
2098
0 /*
1 * Copyright (C) 2015 Gerd Petermann
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
13 package uk.me.parabola.mkgmap.osmstyle.housenumber;
14
15 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
16
17 import java.util.ArrayList;
18 import java.util.List;
19
20 import uk.me.parabola.imgfmt.Utils;
21 import uk.me.parabola.imgfmt.app.Coord;
22 import uk.me.parabola.log.Logger;
23 import uk.me.parabola.mkgmap.filters.LineSplitterFilter;
24 import uk.me.parabola.mkgmap.general.MapRoad;
25
26 /**
27 * Combine two or more HousenumberMatch instances that
28 * can be found at the same point on the road
29 * (with a tolerance of a few meters).
30 * We will create a single Numbers instance for them.
31 *
32 * @author Gerd Petermann
33 *
34 */
35 public class HousenumberGroup {
36
37 private static final Logger log = Logger.getLogger(HousenumberGroup.class);
38 final HousenumberRoad hnr;
39 final List<HousenumberMatch> houses = new ArrayList<>();
40 Int2IntOpenHashMap usedNumbers;
41 int minNum, maxNum;
42 int minSeg , maxSeg;
43 double minFrac, maxFrac;
44 HousenumberMatch closestHouseToRoad ;
45 HousenumberMatch farthestHouseToRoad;
46 int odd,even;
47 Coord linkNode;
48 boolean findSegmentWasCalled;
49
50 public HousenumberGroup(HousenumberRoad hnr, List<HousenumberMatch> housesToUse) {
51 this.hnr = hnr;
52 reset();
53 for (HousenumberMatch house : housesToUse){
54 addHouse(house);
55 }
56 }
57
58 private void addHouse(HousenumberMatch house){
59 int num = house.getHousenumber();
60 if (num % 2 == 0)
61 ++even;
62 else
63 ++odd;
64 int count = usedNumbers.get(num);
65 usedNumbers.put(num, count + 1);
66
67 if (houses.isEmpty()){
68 minNum = maxNum = house.getHousenumber();
69 minSeg = maxSeg = house.getSegment();
70 minFrac = maxFrac = house.getSegmentFrac();
71 closestHouseToRoad = farthestHouseToRoad = house;
72 } else {
73 if (house.getSegment() < minSeg){
74 minSeg = house.getSegment();
75 minFrac = house.getSegmentFrac();
76 } else if (house.getSegment() > maxSeg){
77 maxSeg = house.getSegment();
78 maxFrac = house.getSegmentFrac();
79 } else if (house.getSegment() == minSeg ){
80 minFrac = Math.min(minFrac, house.getSegmentFrac());
81 } else if (house.getSegment() == maxSeg ){
82 maxFrac = Math.max(maxFrac, house.getSegmentFrac());
83 }
84 minNum = Math.min(minNum, num);
85 maxNum = Math.max(maxNum, num);
86 if (house.getDistance() < closestHouseToRoad.getDistance())
87 closestHouseToRoad = house;
88 if (house.getDistance() > farthestHouseToRoad.getDistance())
89 farthestHouseToRoad = house;
90 }
91 houses.add(house);
92 }
93
94 private static final double MIN_DISTANCE_TO_EXISTING_POINT = 7.5;
95
96 /**
97 * find place for the group, change to or add number nodes
98 * @param nodesForLinks
99 * @return true if one or two nodes were added
100 */
101 public boolean findSegment(String streetName, List<HousenumberGroup> groups){
102 if (minSeg < 0 || maxSeg < 0){
103 log.error("internal error: group is not valid:",this);
104 return false;
105 }
106 findSegmentWasCalled = true;
107 linkNode = null;
108 List<Coord> points = getRoad().getPoints();
109 Coord pointToUse = null;
110 int seg = closestHouseToRoad.getSegment();
111 Coord c1 = points.get(seg);
112 Coord c2 = points.get(seg + 1);
113 if (c1.highPrecEquals(c2)){
114 boolean useExisting = true;
115 // already a zero length segment
116 for (HousenumberGroup hg : groups){
117 if (hg == this)
118 continue;
119 if (hg.linkNode == c1){
120 if (hg.closestHouseToRoad.isLeft() != this.closestHouseToRoad.isLeft()){
121 // attach this group to the same segment on the other road side
122 linkNode = c1;
123 return false;
124 } else {
125 log.warn("two groups on same side of road share same point, group 1:",hg,"group 2:",this,"in road",getRoad());
126 useExisting = false;
127 break;
128 }
129 }
130 }
131 if (useExisting){
132 c1.setNumberNode(true);
133 c2.setNumberNode(true);
134 linkNode = c1;
135 return false;
136 }
137 }
138 int timesToAdd = 1;
139 double frac = closestHouseToRoad.getSegmentFrac();
140 if (frac < 0) frac = 0;
141 if (frac > 1) frac = 1;
142 double segLen = c1.distance(c2);
143 pointToUse = c1.makeBetweenPoint(c2, frac);
144 double len1 = segLen * frac;
145 double len2 = (1 - Math.min(1, frac)) * segLen;
146 if (Math.min(len1, len2) < MIN_DISTANCE_TO_EXISTING_POINT){
147 pointToUse = (len1 <= len2) ? c1 : c2;
148 } else {
149 Coord optPoint = ExtNumbers.rasterLineNearPoint(c1, c2, pointToUse, true);
150 double opt1Dist = c1.distance(optPoint);
151 double opt2Dist = c2.distance(optPoint);
152 pointToUse = optPoint;
153 if (Math.min(opt1Dist, opt2Dist) <= MIN_DISTANCE_TO_EXISTING_POINT){
154 pointToUse = (opt1Dist < opt2Dist) ? c1 : c2;
155 }
156 else {
157 timesToAdd = 2;
158 }
159 }
160 if (points.size() + timesToAdd > LineSplitterFilter.MAX_POINTS_IN_LINE)
161 return false;
162 pointToUse.setNumberNode(true);
163 if (timesToAdd == 2){
164 // add two new points between c1 and c2
165 points.add(seg + 1, pointToUse);
166 pointToUse = new Coord (pointToUse);
167 pointToUse.setNumberNode(true);
168 points.add(seg + 1, pointToUse);
169 linkNode = pointToUse;
170 } else {
171 // copy it
172 pointToUse = new Coord(pointToUse);
173 pointToUse.setNumberNode(true);
174 // add copy before c2
175 points.add(seg + 1, pointToUse);
176 if (pointToUse.highPrecEquals(c1)){
177 linkNode = c1;
178 } else {
179 // link to the copy of c2 which is before c2
180 linkNode = pointToUse;
181 }
182 }
183 return true;
184 }
185
186 public boolean verify(){
187 if (findSegmentWasCalled)
188 return true;
189
190 if (minSeg < 0 || maxSeg < 0)
191 return false;
192 int step = 1;
193 if (odd == 0 || even == 0)
194 step = 2;
195 boolean ok = false;
196 if (usedNumbers.size() == (maxNum - minNum) / step + 1)
197 ok = true;
198
199 // final check:
200 double deltaDist = Math.abs(closestHouseToRoad.getDistance() - farthestHouseToRoad.getDistance());
201 if (houses.size() > 2 && deltaDist < houses.size() * 3 ){
202 // more than two houses: make sure that they are really not parallel to the road
203 // for each house we calculate 3m so that a group is kept if it forms an angle of 45° or more
204 // with the road, presuming that the road is rather straight
205 ok = false;
206 }
207 for (HousenumberMatch house : houses){
208 // forget the group, it will not improve search
209 house.setGroup(ok ? this : null);
210 }
211 return ok;
212 }
213
214 public MapRoad getRoad(){
215 return hnr.getRoad();
216 }
217
218 private final static double CLOSE_HOUSES_DIST = 10;
219 public static boolean housesFormAGroup(HousenumberMatch house1, HousenumberMatch house2) {
220 if (house1.isIgnored() || house2.isIgnored())
221 return false;
222 if (house1.getRoad() != house2.getRoad()){
223 log.error("internal error, group check with houses on different roads?",house1.getElement().getId(),house2.getElement().getId());
224 return false;
225 }
226 // assert house1.getRoad() == house2.getRoad();
227
228
229 if (house1.getSegment() > house2.getSegment()){
230 HousenumberMatch help = house1;
231 house1 = house2;
232 house2 = help;
233 }
234 double distBetweenHouses = house1.getLocation().distance(house2.getLocation());
235 if (distBetweenHouses == 0)
236 return true;
237 double minDistToRoad = Math.min(house1.getDistance(), house2.getDistance());
238 double maxDistToRoad = Math.max(house1.getDistance(), house2.getDistance());
239 double distOnRoad = house2.getDistOnRoad(house1);
240
241 if (house1.getSegment() != house2.getSegment()){
242 if (minDistToRoad > 40 && distBetweenHouses < CLOSE_HOUSES_DIST)
243 return true;
244
245 // not the same segment, the distance on road may be misleading when segments have a small angle
246 // and the connection point is a bit more away
247 Coord c1 = house1.getLocation();
248 Coord c2 = house2.getLocation();
249 Coord closest1 = house1.getClosestPointOnRoad();
250 Coord closest2 = house2.getClosestPointOnRoad();
251 double frac1 = HousenumberGenerator.getFrac(closest1, closest2, c1);
252 double frac2 = HousenumberGenerator.getFrac(closest1, closest2, c2);
253 double segLen = closest1.distance(closest2);
254 if (frac1 < 0) frac1 = 0;
255 if (frac2 < 0) frac2 = 0;
256 if (frac1 > 1) frac1 = 1;
257 if (frac2 > 1) frac2 = 1;
258 double distOnRoadSimple = (Math.max(frac1, frac2) - Math.min(frac1, frac2)) * segLen;
259 if (distOnRoadSimple != distOnRoad){
260 // log.debug("distOnRoad recalculation:", house1.getRoad(),house1,house2,distOnRoad,"--->",distOnRoadSimple);
261 distOnRoad = distOnRoadSimple;
262 }
263 }
264 if (distOnRoad <= 0){
265 return true;
266 }
267
268 // two houses form a group when the distance on road is short
269 // how short? The closer the houses are to the road, the shorter
270 double toleranceDistOnRoad = 5 + maxDistToRoad/ 10;
271
272 if (distOnRoad > toleranceDistOnRoad){
273 return false;
274 }
275
276 double deltaDistToRoad = maxDistToRoad - minDistToRoad;
277 double ratio2 = deltaDistToRoad / distBetweenHouses;
278 // a ratio2 near or higher 1 means that the two houses and the closest point on the
279 // road are on a straight line
280 if (ratio2 > 0.9)
281 return true;
282 if (ratio2 < 0.666)
283 return false;
284 return true;
285 }
286
287 public boolean tryAddHouse(HousenumberMatch house) {
288 if (house.isInterpolated() || house.getRoad() == null || house.isIgnored())
289 return false;
290 int num = house.getHousenumber();
291 int step = 1;
292 if (odd == 0 || even == 0)
293 step = 2;
294 if (num - maxNum != step)
295 return false;
296 HousenumberMatch last = houses.get(houses.size()-1);
297 if (last.getGroup() != null){
298 if (last.getGroup() == house.getGroup()){
299 addHouse(house);
300 return true;
301 } else
302 return false;
303 }
304 if (last.getDistance() + 3 < house.getDistance() && last.isDirectlyConnected(house)){
305 addHouse(house);
306 return true;
307 }
308
309 if (housesFormAGroup(house, last) == false){
310 return false;
311 }
312 if (houses.size() > 1){
313 HousenumberMatch first = houses.get(0);
314 if (housesFormAGroup(house, first) == false){
315 HousenumberMatch preLast = houses.get(houses.size()-2);
316 double angle = Utils.getAngle(house.getLocation(), last.getLocation(), preLast.getLocation());
317 if (Math.abs(angle) > 30)
318 return false;
319 }
320 }
321 addHouse(house);
322 return true;
323 }
324
325
326 public boolean recalcPositions(){
327 List<HousenumberMatch> saveHouses = new ArrayList<>(houses);
328 reset();
329 for (HousenumberMatch house : saveHouses)
330 addHouse(house);
331 if (!verify()){
332 for (HousenumberMatch house : houses){
333 HousenumberGenerator.findClosestRoadSegment(house, getRoad());
334 }
335 return false;
336 }
337 return true;
338 }
339
340 private void reset() {
341 usedNumbers = new Int2IntOpenHashMap();
342 minNum = Integer.MAX_VALUE;
343 maxNum = -1;
344 minSeg = Integer.MAX_VALUE;
345 maxSeg = -1;
346 minFrac = maxFrac = Double.NaN;
347 closestHouseToRoad = null;
348 farthestHouseToRoad = null;
349 odd = even = 0;
350 houses.clear();
351 }
352
353 public String toString(){
354 return houses.toString();
355 }
356 }
0 /*
1 * Copyright (C) 2015 Gerd Petermann
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
13 package uk.me.parabola.mkgmap.osmstyle.housenumber;
14
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.Map;
19 import uk.me.parabola.imgfmt.Utils;
20 import uk.me.parabola.imgfmt.app.Coord;
21 import uk.me.parabola.log.Logger;
22 import uk.me.parabola.mkgmap.general.MapRoad;
23 import uk.me.parabola.mkgmap.reader.osm.Node;
24 import uk.me.parabola.mkgmap.reader.osm.TagDict;
25 import uk.me.parabola.mkgmap.reader.osm.Way;
26
27 /**
28 * Represents a (part of an) addr:interpolation way.
29 * It contains the points between two house number elements
30 * and the information how numbers are interpolated along
31 * the way that is described by these points.
32 *
33 * We try to use the information to find
34 * a) the right road for the houses
35 * b) the right road segment
36 * c) the position of the interpolated houses
37 *
38 * We have to be aware of several special cases so we use some
39 * flags to say for which of the above points the information
40 * can be used.
41 *
42 * @author Gerd Petermann
43 *
44 */
45 public class HousenumberIvl {
46 private static final Logger log = Logger.getLogger(HousenumberIvl.class);
47
48 /** Gives the maximum distance between house number element and the matching road
49 * when the number is part of an addr:interpolation way */
50 public static final double MAX_INTERPOLATION_DISTANCE_TO_ROAD = 75.0;
51
52 private final String streetName;
53 private final Way interpolationWay;
54 private MapRoad roadForInterpolatedHouses;
55 private final Node n1,n2;
56 private List<Coord> points;
57 private int step, start, end, steps;
58 private HousenumberMatch[] knownHouses = {null, null};
59
60 private boolean hasMultipleRoads;
61 private boolean foundCluster;
62 private int interpolated; // counter to detect wrong double use
63 private boolean ignoreForInterpolation;
64
65 private boolean equalEnds;
66 private static final short streetTagKey = TagDict.getInstance().xlate("mkgmap:street");
67 private static final short housenumberTagKey = TagDict.getInstance().xlate("mkgmap:housenumber");
68 private static final short addrInterpolationTagKey = TagDict.getInstance().xlate("addr:interpolation");
69
70
71 public HousenumberIvl(String steetName, Way interpolationWay, Node n1, Node n2) {
72 this.streetName = steetName;
73 this.interpolationWay = interpolationWay;
74 this.n1 = n1;
75 this.n2 = n2;
76 }
77
78 public void setPoints(List<Coord> points) {
79 this.points = new ArrayList<Coord>(points);
80 }
81 public void setStep(int step) {
82 this.step = step;
83 }
84 public int getStep() {
85 return step;
86 }
87 public void setStart(int start) {
88 this.start = start;
89 }
90
91 public int getStart() {
92 return start;
93 }
94 public void setEnd(int end) {
95 this.end = end;
96 }
97 public int getEnd() {
98 return end;
99 }
100 public void setSteps(int steps) {
101 this.steps = steps;
102 }
103
104
105 public Node getNode1() {
106 return n1;
107 }
108
109 public Node getNode2() {
110 return n2;
111 }
112
113 // public boolean needsSplit(){
114 // return needsSplit;
115 // }
116
117 public void addHousenumberMatch(HousenumberMatch house) {
118 if (house.getElement() == n1)
119 knownHouses[0] = house;
120 else if (house.getElement() == n2)
121 knownHouses[1] = house;
122 else {
123 log.error("cannot add",house,"to",this);
124 }
125 }
126
127 public boolean checkRoads(){
128 boolean res = checkRoads2();
129 if (!res || equalEnds){
130 // the interval is not ok --> ignore the numbers as well
131 ignoreNodes();
132 }
133 return res;
134 }
135
136 private boolean checkRoads2(){
137 for (int i = 0; i < 2; i++){
138 if (knownHouses[i] == null ){
139 log.error("internal error: housenumber matches not properly set", this);
140 return false;
141 }
142 if (knownHouses[i].getRoad() == null || knownHouses[i].getDistance() > 100 ){
143 log.warn("cannot find any reasonable road for both nodes, ignoring them",streetName,this);
144 return false;
145 }
146 }
147 if (knownHouses[0].getRoad().getRoadDef().getId() == knownHouses[1].getRoad().getRoadDef().getId()){
148 if (knownHouses[0].getRoad() != knownHouses[1].getRoad()){
149 // special case: interval goes along clipped road, data is probably OK
150 hasMultipleRoads = true;
151 return true;
152 }
153 for (MapRoad r : knownHouses[0].getAlternativeRoads()){
154 if (r.getRoadDef().getId() == knownHouses[0].getRoad().getRoadDef().getId()){
155 // special case: interval may go along clipped road, data is probably OK
156 hasMultipleRoads = true;
157 return true;
158 }
159 }
160 }
161 MapRoad bestRoad = null;
162 // make sure that the closest road is one with a matching name
163 for (int i = 0; i < 2; i++){
164 while (streetName.equals(knownHouses[i].getRoad().getStreet()) == false && knownHouses[i].hasAlternativeRoad()){
165 HousenumberMatch testx = new HousenumberMatch(knownHouses[i]);
166 MapRoad r = knownHouses[i].getAlternativeRoads().remove(0);
167 HousenumberGenerator.findClosestRoadSegment(testx, r);
168 if (testx.getDistance() < MAX_INTERPOLATION_DISTANCE_TO_ROAD){
169 copyRoadData(testx, knownHouses[i]);
170 }
171 }
172 }
173 List<MapRoad> toTest = new ArrayList<>();
174 toTest.add(knownHouses[0].getRoad());
175 toTest.add(knownHouses[1].getRoad());
176 for (MapRoad r : knownHouses[0].getAlternativeRoads()){
177 if (knownHouses[1].getAlternativeRoads().contains(r))
178 toTest.add(r);
179 }
180 HousenumberMatch[] test = new HousenumberMatch[2];
181 HousenumberMatch[] closest = new HousenumberMatch[2];
182 boolean foundSingleRoad = false;
183 for (MapRoad r : toTest){
184 if (streetName.equals(r.getStreet()) == false)
185 continue;
186 foundSingleRoad = true;
187 for (int i = 0; i < 2; i++){
188 test[i] = knownHouses[i];
189 if (test[i].getRoad() != r){
190 test[i] = new HousenumberMatch(knownHouses[i]);
191 HousenumberGenerator.findClosestRoadSegment(test[i], r);
192 test[i].calcRoadSide();
193 }
194 if (test[i].getRoad() == null || test[i].getDistance() > MAX_INTERPOLATION_DISTANCE_TO_ROAD ){
195 foundSingleRoad = false;
196 break;
197 }
198 }
199 if (foundSingleRoad){
200 if (test[0].isLeft() != test[1].isLeft()){
201 foundSingleRoad = false;
202 continue;
203 }
204 int s0 = test[0].getSegment();
205 int s1 = test[1].getSegment();
206 // check if the road and the addr:interpolation way are nearly parallel lines
207 double angle1 = Utils.getAngle(test[0].getClosestPointOnRoad(), points.get(0),points.get(1));
208 if (Math.abs(angle1) < 30){
209 foundSingleRoad = false;
210 HousenumberMatch testx = new HousenumberMatch(test[0]);
211 for (int s = Math.min(s0,s1); s <= Math.max(s0, s1); s++){
212 if (s != test[0].getSegment()){
213 HousenumberGenerator.findClosestRoadSegment(testx, r, s,s+1);
214 angle1 = Utils.getAngle(testx.getClosestPointOnRoad(), points.get(0),points.get(1));
215 if (Math.abs(angle1) >= 30 && testx.getDistance() < 2*test[0].getDistance()){
216 test[0] = testx;
217 foundSingleRoad = true;
218 break;
219 }
220 }
221 }
222 }
223 double angle2 = Utils.getAngle(points.get(points.size()-2),points.get(points.size()-1),test[1].getClosestPointOnRoad());
224 if (Math.abs(angle2) < 30){
225 foundSingleRoad = false;
226 HousenumberMatch testx = new HousenumberMatch(test[1]);
227 for (int s = Math.min(s0,s1); s <= Math.max(s0, s1); s++){
228 if (s != test[1].getSegment()){
229 HousenumberGenerator.findClosestRoadSegment(testx, r, s,s+1);
230 angle2 = Utils.getAngle(points.get(points.size()-2),points.get(points.size()-1),testx.getClosestPointOnRoad());
231 if (Math.abs(angle2) >= 30 && testx.getDistance() < 2*test[1].getDistance()){
232 test[1] = testx;
233 foundSingleRoad = true;
234 break;
235 }
236 }
237 }
238 }
239 }
240 if (foundSingleRoad){
241 if (r.isNamedByHousenumberProcessing() == false)
242 break;
243 // the closest road was originally unnamed , try to find one that is named in OSM
244 if (bestRoad == null){
245 bestRoad = r;
246 closest[0] = test[0];
247 closest[1] = test[1];
248 }
249 }
250 }
251 if (!foundSingleRoad && bestRoad != null){
252 // not matching road name in original OSM data, use the closest
253 foundSingleRoad = true;
254 test[0] = closest[0];
255 test[1] = closest[1];
256 }
257 if (!foundSingleRoad){
258 if (streetName.equals(knownHouses[0].getRoad().getStreet()) == false || streetName.equals(knownHouses[1].getRoad().getStreet()) == false){
259 log.warn("cannot find reasonable road for both nodes",streetName,this);
260 return false;
261 }
262 hasMultipleRoads = true;
263 return true;
264 }
265 // we found the road that should be used for interpolation
266 roadForInterpolatedHouses = test[0].getRoad();
267
268 // we found a single plausible road, make sure that both nodes are using it
269 for (int i = 0; i < 2; i++){
270 if (knownHouses[i].getRoad() != test[i].getRoad() || knownHouses[i].getSegment() != test[i].getSegment()){
271 copyRoadData(test[i], knownHouses[i]);
272 knownHouses[i].forgetAlternativeRoads();
273 }
274 if (knownHouses[i].getSegmentFrac() < 0 || knownHouses[i].getSegmentFrac() > 1){
275 hasMultipleRoads = true;
276 }
277 }
278 if (knownHouses[0].isLeft() != knownHouses[1].isLeft()){
279 log.warn("addr:interpolation way crosses road",streetName,this);
280 return false;
281 }
282 return true;
283 }
284
285 private void copyRoadData(HousenumberMatch source, HousenumberMatch dest) {
286 if (log.isInfoEnabled()){
287 if (source.getRoad() != dest.getRoad())
288 log.info("moving",streetName,dest.getSign(),dest.getElement().toBrowseURL(),"from road",dest.getRoad(),"to road",source.getRoad());
289 else
290 log.info("moving",streetName,dest.getSign(),dest.getElement().toBrowseURL(),"from segment",dest.getSegment(),"to ",source.getSegment(),"in road",source.getRoad());
291 }
292 dest.setRoad(source.getRoad());
293 dest.setSegment(source.getSegment());
294 dest.setSegmentFrac(source.getSegmentFrac());
295 dest.setDistance(source.getDistance());
296 dest.calcRoadSide();
297 }
298
299 public List<HousenumberMatch> getInterpolatedHouses(){
300 List<HousenumberMatch> houses = new ArrayList<>();
301 if (ignoreForInterpolation|| start == end || steps <= 0)
302 return houses;
303 List<Coord> interpolatedPoints = getInterpolatedPoints();
304 int usedStep = (start < end) ? step : -step;
305 int hn = start;
306 boolean distanceWarningIssued = false;
307 for (Coord co : interpolatedPoints){
308 hn += usedStep;
309 Node generated = new Node(interpolationWay.getId(), co);
310 generated.setFakeId();
311 generated.addTag(streetTagKey, streetName);
312 String number = String.valueOf(hn);
313 generated.addTag(housenumberTagKey, number);
314 // TODO: maybe add check that city info and zip code of both houses is equal ?
315 // what if not ?
316 HousenumberElem houseElem = new HousenumberElem(generated, knownHouses[0].getCityInfo());
317 houseElem.setHousenumber(hn);
318 houseElem.setZipCode(knownHouses[0].getZipCode());
319 houseElem.setStreet(streetName);
320 houseElem.setSign(number);
321 HousenumberMatch house = new HousenumberMatch(houseElem);
322 if (roadForInterpolatedHouses != null){
323 HousenumberGenerator.findClosestRoadSegment(house, roadForInterpolatedHouses);
324 if (house.getRoad() == null || house.getDistance() > MAX_INTERPOLATION_DISTANCE_TO_ROAD ){
325 if (distanceWarningIssued == false){
326 log.warn("interpolated house is not close to expected road",this,house);
327 distanceWarningIssued = true;
328 }
329 continue;
330 }
331 house.calcRoadSide();
332 }
333 house.setInterpolated(true);
334 houses.add(house);
335 }
336 if (getId() == 37881402){
337 long dd = 4;
338 }
339
340 if (log.isDebugEnabled()){
341 String addrInterpolationMethod = interpolationWay.getTag(addrInterpolationTagKey);
342 if (hasMultipleRoads == false)
343 log.debug(this,"generated",addrInterpolationMethod,"interpolated number(s) for",knownHouses[0].getRoad());
344 else
345 log.debug(this,"generated",addrInterpolationMethod,"interpolated number(s) for",streetName);
346 }
347 return houses;
348 }
349 /**
350 * Calculate the wanted number of coords on a way so that they have
351 * similar distances to each other (and to the first and last point
352 * of the way).
353 * @param points list of points that build the way
354 * @param num the wanted number
355 * @return a list with the number of points or the empty list in
356 * case of errors
357 */
358 public List<Coord> getInterpolatedPoints(){
359 if (interpolated > 0){
360 log.debug("interpolating numbers again for", this );
361 }
362 interpolated++;
363 if (steps < 1 || points.size() < 2)
364 return Collections.emptyList();
365
366 List<Coord> interpolated = new ArrayList<>(steps);
367 double wayLen = 0;
368 for (int i = 0; i+1 < points.size(); i++){
369 wayLen += points.get(i).distance(points.get(i+1));
370 }
371 double ivlLen = wayLen / (steps+1);
372 if (ivlLen < 0.1){
373 if (log.isInfoEnabled())
374 log.info("addr:interpolation",interpolationWay.toBrowseURL(),"segment ignored, would generate",steps,"houses with distance of",ivlLen,"m");
375 return interpolated;
376 }
377 int pos = 0;
378 double rest = 0;
379 while (pos+1 < points.size()){
380 Coord c1 = points.get(pos);
381 Coord c2 = points.get(pos+1);
382 pos++;
383 double neededPartOfSegment = 0;
384 double segmentLen = c1.distance(c2);
385 for(;;){
386 neededPartOfSegment += ivlLen - rest;
387 if (neededPartOfSegment <= segmentLen){
388 double fraction = neededPartOfSegment / segmentLen;
389 Coord c = c1.makeBetweenPoint(c2, fraction);
390 interpolated.add(c);
391 if (interpolated.size() >= steps){
392 // GpxCreator.createGpx("e:/ld/road", knownHouses[0].getRoad().getPoints());
393 // GpxCreator.createGpx("e:/ld/test", interpolated, Arrays.asList(points.get(0),points.get(points.size()-1)));
394 return interpolated;
395 }
396 rest = 0;
397 } else {
398 rest = segmentLen - neededPartOfSegment + ivlLen;
399 break;
400 }
401 }
402
403 }
404 log.warn("addr:interpolation",interpolationWay.toBrowseURL(),"interpolation for segment with nodes",n1.getId(),n2.getId(),"failed");
405 return interpolated;
406 }
407
408 public String toString() {
409 return interpolationWay.toBrowseURL() + " " + start + ".." + end + ", step=" + step;
410 }
411
412 public String getDesc() {
413 return streetName + "_" + start + ".." + end + "_" + step;
414 }
415
416 // public boolean setNodeRefs(HashMap<Element, HousenumberMatch> houses) {
417 // knownHouses[0] = houses.get(n1);
418 // knownHouses[1] = houses.get(n2);
419 // if (knownHouses[0] == null || knownHouses[1] == null)
420 // return false;
421 // knownHouses[0].incIntervalInfoRefs();
422 // knownHouses[1].incIntervalInfoRefs();
423 // return true;
424 // }
425 //
426 public void ignoreNodes() {
427 for (int i = 0; i < 2; i++){
428 if (knownHouses[i] != null){
429 knownHouses[i].decIntervalInfoRefs();
430 if (knownHouses[i].getIntervalInfoRefs() == 0)
431 knownHouses[i].setIgnored(true);
432 }
433 }
434 }
435
436 public long getId() {
437 return interpolationWay.getId();
438 }
439
440
441 public boolean ignoreForInterpolation() {
442 return ignoreForInterpolation;
443 }
444
445 public void setIgnoreForInterpolation(boolean ignoreForInterpolation) {
446 this.ignoreForInterpolation = ignoreForInterpolation;
447 }
448
449 public boolean isBad() {
450 return false;
451 }
452
453
454 public boolean inCluster(List<HousenumberMatch> housesNearCluster) {
455 int count = 0;
456 for (HousenumberMatch house : housesNearCluster){
457 if (knownHouses[0] == house || knownHouses[1] == house){
458 ++count;
459
460 }
461 if (count == 2)
462 break;
463 }
464 if (count > 0){
465 foundCluster = true;
466 return true;
467 }
468 return false;
469 }
470
471 public boolean foundCluster() {
472 return foundCluster;
473 }
474
475 public void setEqualEnds() {
476 this.equalEnds = true;
477
478 }
479
480 public boolean setNodeRefs(Map<Long, Integer> interpolationNodes,
481 List<HousenumberElem> houseElems) {
482 for (int i = 0; i < 2; i++){
483 long id = (i == 0) ? n1.getId(): n2.getId();
484 Integer elemPos = interpolationNodes.get(id);
485 if (elemPos == null || elemPos >= houseElems.size())
486 return false;
487 HousenumberElem he = houseElems.get(elemPos);
488 if (he instanceof HousenumberMatch == false)
489 return false;
490 if (he.getElement().getId() != id)
491 return false;
492 knownHouses[i] = (HousenumberMatch) he;
493 knownHouses[i].incIntervalInfoRefs();
494 }
495 return true;
496 }
497
498 public HousenumberMatch[] getHouseNodes (){
499 return knownHouses;
500 }
501 }
1212
1313 package uk.me.parabola.mkgmap.osmstyle.housenumber;
1414
15 import java.util.regex.Matcher;
16 import java.util.regex.Pattern;
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.List;
1718
1819 import uk.me.parabola.imgfmt.app.Coord;
1920 import uk.me.parabola.mkgmap.general.MapRoad;
20 import uk.me.parabola.mkgmap.reader.osm.Element;
21 import uk.me.parabola.mkgmap.reader.osm.Node;
22 import uk.me.parabola.mkgmap.reader.osm.TagDict;
2321 import uk.me.parabola.mkgmap.reader.osm.Way;
22 import uk.me.parabola.util.Locatable;
2423
2524 /**
2625 * Stores the matching data between a housenumber and its road.
2726 * @author WanMil
2827 */
29 public class HousenumberMatch {
30
31 private final Element element;
32
28 public class HousenumberMatch extends HousenumberElem implements Locatable {
3329 private MapRoad road;
30 private HousenumberRoad housenumberRoad;
3431
3532 private double distance = Double.POSITIVE_INFINITY;
3633 private int segment = -1;
3835
3936 private double segmentFrac;
4037
41 private int housenumber;
42
43 /**
44 * Instantiates a new housenumber match element.
45 * @param element the OSM element tagged with mkgmap:housenumber
46 * @throws IllegalArgumentException if the housenumber cannot be parsed
47 */
48 public HousenumberMatch(Element element) {
49 this.element = element;
50 parseHousenumber();
51 }
52
53 /**
54 * Retrieves the location of the housenumber.
55 * @return location of housenumber
56 */
57 public Coord getLocation() {
58 return (element instanceof Node ? ((Node)element).getLocation() : ((Way)element).getCofG());
59 }
60
61 /**
62 * Retrieves the house number of this element.
63 * @param e an OSM element
64 * @return the house number (or {@code null} if no house number set)
65 */
66 private static final short housenumberTagKey1 = TagDict.getInstance().xlate("mkgmap:housenumber");
67 private static final short housenumberTagKey2 = TagDict.getInstance().xlate("addr:housenumber");
68 public static String getHousenumber(Element e) {
69 String res = e.getTag(housenumberTagKey1);
70 if (res != null)
71 return res;
72 return e.getTag(housenumberTagKey2);
73 }
74
75 /**
76 * Parses the house number string. It accepts the first positive number part
77 * of a string. So all leading and preceding non number parts are ignored.
78 * So the following strings are accepted:
79 * <table>
80 * <tr>
81 * <th>Input</th>
82 * <th>Output</th>
83 * </tr>
84 * <tr>
85 * <td>23</td>
86 * <td>23</td>
87 * </tr>
88 * <tr>
89 * <td>-23</td>
90 * <td>23</td>
91 * </tr>
92 * <tr>
93 * <td>21-23</td>
94 * <td>21</td>
95 * </tr>
96 * <tr>
97 * <td>Abc 21</td>
98 * <td>21</td>
99 * </tr>
100 * <tr>
101 * <td>Abc 21.45</td>
102 * <td>21</td>
103 * </tr>
104 * <tr>
105 * <td>21 Main Street</td>
106 * <td>21</td>
107 * </tr>
108 * <tr>
109 * <td>Main Street</td>
110 * <td><i>IllegalArgumentException</i></td>
111 * </tr>
112 * </table>
113 * @throws IllegalArgumentException if parsing fails
114 */
115 private void parseHousenumber() {
116 String housenumberString = getHousenumber(element);
117
118 if (housenumberString == null) {
119 throw new IllegalArgumentException("No housenumber found in "+element.toBrowseURL());
120 }
121
122 // the housenumber must match against the pattern <anything>number<notnumber><anything>
123 Pattern p = Pattern.compile("\\D*(\\d+)\\D?.*");
124 Matcher m = p.matcher(housenumberString);
125 if (m.matches() == false) {
126 throw new IllegalArgumentException("No housenumber ("+element.toBrowseURL()+"): "+housenumberString);
127 }
128 try {
129 // get the number part and parse it
130 housenumber = Integer.parseInt(m.group(1));
131 } catch (NumberFormatException exp) {
132 throw new IllegalArgumentException("No housenumber ("+element.toBrowseURL()+"): "+housenumberString);
133 }
134
135 // a housenumber must be > 0
136 if (housenumber <= 0) {
137 throw new IllegalArgumentException("No housenumber ("+element.toBrowseURL()+"): "+housenumberString);
138 }
38 private boolean ignored;
39 private boolean isDuplicate;
40 private boolean interpolated;
41 private int moved;
42 // distance in m between closest point on road and the point that is found in the address search
43 private double searchDist = Double.NaN;
44 private boolean isFarDuplicate;
45 private HousenumberGroup group;
46 private List<MapRoad> alternativeRoads;
47 private int intervalInfoRefs; // counter
48
49 public HousenumberMatch(HousenumberElem he) {
50 super(he);
13951 }
14052
14153 public MapRoad getRoad() {
208120 this.segmentFrac = segmentFrac;
209121 }
210122
211 /**
212 * Retrieve the house number
213 * @return the house number
214 */
215 public int getHousenumber() {
216 return housenumber;
217 }
218
219 /**
220 * Set the house number.
221 * @param housenumber house number
222 */
223 public void setHousenumber(int housenumber) {
224 this.housenumber = housenumber;
225 }
226
227 /**
228 * Retrieve the OSM element that defines the house number.
229 * @return the OSM element
230 */
231 public Element getElement() {
232 return element;
233 }
234
123 public boolean hasAlternativeRoad() {
124 return alternativeRoads != null && alternativeRoads.isEmpty() == false;
125 }
126
127 public boolean isIgnored() {
128 return ignored;
129 }
130
131 public void setIgnored(boolean ignored) {
132 this.ignored = ignored;
133 }
134
135 public boolean isDuplicate() {
136 return isDuplicate;
137 }
138
139 public void setDuplicate(boolean isDuplicate) {
140 this.isDuplicate = isDuplicate;
141 }
142
143 public boolean isInterpolated() {
144 return interpolated;
145 }
146
147 public void setInterpolated(boolean interpolated) {
148 this.interpolated = interpolated;
149 }
150
151 public int getMoved() {
152 return moved;
153 }
154
155 public void incMoved() {
156 this.moved++;
157 }
158
159 public double getSearchDist() {
160 return searchDist;
161 }
162
163 public void setSearchDist(double searchDist) {
164 this.searchDist = searchDist;
165 }
166
235167 public String toString() {
236 return String.valueOf(housenumber)+"("+segment+")";
237 }
168 String s1 = String.valueOf(getHousenumber());
169 if (getSign().length() > 2 + s1.length())
170 return s1 + "("+segment+")";
171 return getSign() + "("+segment+")";
172 }
173
174 public void setFarDuplicate(boolean b) {
175 this.isFarDuplicate = b;
176
177 }
178
179 public boolean isFarDuplicate() {
180 return isFarDuplicate;
181 }
182
183 /**
184 * @return either an existing point on the road
185 * or the calculated perpendicular. In the latter case
186 * the highway count is zero.
187 *
188 */
189 public Coord getClosestPointOnRoad(){
190 if (segmentFrac <= 0)
191 return getRoad().getPoints().get(segment);
192 if (segmentFrac >= 1)
193 return getRoad().getPoints().get(segment+1);
194 Coord c1 = getRoad().getPoints().get(segment);
195 Coord c2 = getRoad().getPoints().get(segment+1);
196 return c1.makeBetweenPoint(c2, segmentFrac);
197 }
198
199 /**
200 * @param other a different house on the same road
201 * @return the distance in m between the perpendiculars on the road
202 * of two houses.
203 */
204 public double getDistOnRoad(HousenumberMatch other) {
205 if (getRoad() != other.getRoad()){
206 assert false : "cannot compute distance on road for different roads";
207 }
208 List<Coord> points = getRoad().getPoints();
209 HousenumberMatch house1 = this;
210 HousenumberMatch house2 = other;
211 if (house1.segment > house2.segment || house1.segment == house2.segment && house1.segmentFrac > house2.segmentFrac){
212 house1 = other;
213 house2 = this;
214 }
215 int s1 = house1.segment;
216 int s2 = house2.segment;
217 double distOnRoad = 0;
218 while (s1 < s2){
219 double segLen = points.get(s1).distance(points.get(s1 + 1));
220 if (s1 == house1.getSegment() && house1.getSegmentFrac() > 0){
221 // rest of first segment
222 distOnRoad += Math.max(0, 1-house1.getSegmentFrac()) * segLen;
223 } else
224 distOnRoad += segLen;
225 s1++;
226 }
227 double segLen = points.get(s1).distance(points.get(s1 + 1));
228 if (house2.getSegmentFrac() > 0)
229 distOnRoad += Math.min(1, house2.getSegmentFrac()) * segLen;
230 if (house1.getSegmentFrac() > 0 && s1 == house1.segment)
231 distOnRoad -= Math.min(1, house1.getSegmentFrac()) * segLen;
232 return distOnRoad;
233 }
234
235 public HousenumberRoad getHousenumberRoad() {
236 return housenumberRoad;
237 }
238
239 public void setHousenumberRoad(HousenumberRoad housenumberRoad) {
240 this.housenumberRoad = housenumberRoad;
241 }
242
243 public void setGroup(HousenumberGroup housenumberBlock) {
244 this.group = housenumberBlock;
245 }
246
247 public HousenumberGroup getGroup() {
248 return group;
249 }
250
251 public void addAlternativeRoad(MapRoad road2) {
252 if (alternativeRoads == null){
253 alternativeRoads = new ArrayList<>();
254 }
255 alternativeRoads.add(road2);
256 }
257 public List<MapRoad> getAlternativeRoads() {
258 if (alternativeRoads == null)
259 return Collections.emptyList();
260 return alternativeRoads;
261 }
262 public void forgetAlternativeRoads(){
263 alternativeRoads = null;
264 }
265
266 public int getIntervalInfoRefs() {
267 return intervalInfoRefs;
268 }
269
270 public void incIntervalInfoRefs() {
271 intervalInfoRefs++;
272 }
273
274 public void decIntervalInfoRefs() {
275 if (intervalInfoRefs > 0)
276 --intervalInfoRefs;
277 }
278
279 public boolean isDirectlyConnected(HousenumberMatch other){
280 if (getElement() instanceof Way && other.getElement() instanceof Way){
281 List<Coord> s1 = ((Way) getElement()).getPoints();
282 List<Coord> s2 = ((Way) other.getElement()).getPoints();
283 for (int i = 0; i+1 < s1.size(); i++){
284 Coord co = s1.get(i);
285 co.setPartOfShape2(false);
286 }
287 for (int i = 0; i+1 < s2.size(); i++){
288 Coord co = s2.get(i);
289 co.setPartOfShape2(true);
290 }
291 for (int i = 0; i+1 < s1.size(); i++){
292 Coord co = s1.get(i);
293 if (co.isPartOfShape2())
294 return true;
295 }
296 }
297 return false;
298 }
299
300
301 public void calcRoadSide(){
302 if (getRoad() == null)
303 return;
304 Coord c1 = getRoad().getPoints().get(getSegment());
305 Coord c2 = getRoad().getPoints().get(getSegment()+1);
306 setLeft(HousenumberGenerator.isLeft(c1, c2, getLocation()));
307
308 }
309
310 public boolean isEqualAddress(HousenumberElem other){
311 if (getRoad() != other.getRoad())
312 return false;
313 if (getSign().equals(other.getSign()) == false)
314 return false;
315 if (getZipCode() != null && other.getZipCode() != null){
316 if (getZipCode().equals(other.getZipCode()) == false)
317 return false;
318 }
319 if (getCityInfo() != null && other.getCityInfo() != null){
320 if (getCityInfo().equals(other.getCityInfo()) == false)
321 return false;
322 }
323 return true;
324 }
325
238326 }
327
0 /*
1 * Copyright (C) 2015 Gerd Petermann
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
13 package uk.me.parabola.mkgmap.osmstyle.housenumber;
14
15 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
16
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.LinkedHashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27
28 import uk.me.parabola.imgfmt.app.Coord;
29 import uk.me.parabola.imgfmt.app.CoordNode;
30 import uk.me.parabola.log.Logger;
31 import uk.me.parabola.mkgmap.general.CityInfo;
32 import uk.me.parabola.mkgmap.general.MapRoad;
33 import uk.me.parabola.mkgmap.general.ZipCodeInfo;
34 import uk.me.parabola.mkgmap.osmstyle.housenumber.HousenumberGenerator.HousenumberMatchByNumComparator;
35 import uk.me.parabola.mkgmap.osmstyle.housenumber.HousenumberGenerator.HousenumberMatchByPosComparator;
36
37 /**
38 * Helper class to combine house numbers with MapRoad instances
39 * @author Gerd Petermann
40 *
41 */
42 public class HousenumberRoad {
43 private static final Logger log = Logger.getLogger(HousenumberRoad.class);
44 private String streetName;
45 private final MapRoad road;
46 private CityInfo roadCityInfo;
47 private ZipCodeInfo roadZipCode;
48 private ExtNumbers extNumbersHead;
49 private final List<HousenumberMatch> houseNumbers;
50 private boolean changed;
51 private boolean isRandom;
52 private boolean removeGaps;
53 private LinkedHashSet<String> furtherNames;
54
55
56 public HousenumberRoad(MapRoad r, CityInfo ci, List<HousenumberMatch> potentialNumbersThisRoad) {
57 this.streetName = r.getStreet();
58 this.road = r;
59 this.roadCityInfo = ci;
60 this.houseNumbers = new ArrayList<>(potentialNumbersThisRoad);
61 for (HousenumberMatch house : houseNumbers){
62 house.setHousenumberRoad(this);
63 }
64 }
65
66
67 public void addPlaceName(String name) {
68 if (furtherNames == null){
69 furtherNames = new LinkedHashSet<>();
70 }
71 furtherNames.add(name);
72 }
73
74 public String getName (){
75 return streetName;
76 }
77
78 public void buildIntervals() {
79 Collections.sort(houseNumbers, new HousenumberMatchByNumComparator());
80 if (log.isInfoEnabled())
81 log.info("Initial housenumbers for",road,"in",road.getCity(),houseNumbers);
82
83 filterRealDuplicates();
84 filterGroups();
85 if (houseNumbers.isEmpty())
86 return;
87 List<HousenumberMatch> leftNumbers = new ArrayList<HousenumberMatch>();
88 List<HousenumberMatch> rightNumbers = new ArrayList<HousenumberMatch>();
89
90 for (HousenumberMatch house : houseNumbers) {
91 if (house.getRoad() == null || house.isIgnored()){
92 continue;
93 }
94 if (house.getHousenumberRoad() != this || house.getHousenumberRoad().getRoad() != house.getRoad()){
95 log.error("internal error, road links are not correct",house.getElement().toBrowseURL());
96 }
97 if (house.isLeft()) {
98 leftNumbers.add(house);
99 } else {
100 rightNumbers.add(house);
101 }
102 }
103 detectGroups(leftNumbers, rightNumbers);
104 Collections.sort(leftNumbers, new HousenumberMatchByPosComparator());
105 Collections.sort(rightNumbers, new HousenumberMatchByPosComparator());
106
107
108 int currNodePos = 0;
109 int nodeIndex = 0;
110 int prevNumberNodeIndex = 0;
111 int prevNodePos = 0;
112 extNumbersHead = null;
113 ExtNumbers currNumbers = null;
114 for (Coord p : road.getPoints()) {
115 if (currNodePos == 0) {
116 if (road.skipAddToNOD() == false)
117 assert p instanceof CoordNode;
118 }
119
120 // An ordinary point in the road.
121 if (p.isNumberNode() == false) {
122 currNodePos++;
123 continue;
124 }
125
126 // The first time round, this is guaranteed to be a CoordNode
127 if (currNodePos == 0) {
128 nodeIndex++;
129 currNodePos++;
130 continue;
131 }
132
133 // Now we have a CoordNode and it is not the first one.
134 ExtNumbers numbers = new ExtNumbers(this);
135 numbers.setNodeIndex(prevNumberNodeIndex);
136 int leftUsed = numbers.setNumbers(leftNumbers, prevNodePos, currNodePos, true);
137 int rightUsed = numbers.setNumbers(rightNumbers, prevNodePos, currNodePos, false);
138 prevNodePos = currNodePos;
139 // maintain chain
140 numbers.prev = currNumbers;
141 if (currNumbers != null)
142 currNumbers.next = numbers;
143 else {
144 extNumbersHead = numbers;
145 }
146 currNumbers = numbers;
147 leftNumbers.subList(0, leftUsed).clear();
148 rightNumbers.subList(0, rightUsed).clear();
149
150 prevNumberNodeIndex = nodeIndex;
151 nodeIndex++;
152 currNodePos++;
153 }
154 }
155
156 /**
157 * Try to detect groups of houses with continues numbers
158 * which should be attached to a zero-length segment.
159 * Very useful when a service road connects eg.
160 * numbers 7..15 to the named road, but also for just two numbers.
161 * @param depth
162 * @param leftNumbers
163 * @param rightNumbers
164 */
165 private void detectGroups(List<HousenumberMatch> leftNumbers, List<HousenumberMatch> rightNumbers) {
166 List<HousenumberGroup> groups = new ArrayList<>();
167
168 for (int side = 0; side < 2; side++){
169 boolean left = side == 0;
170 List<HousenumberMatch> houses = left ? leftNumbers : rightNumbers;
171 HousenumberGroup group = null;
172 for (int j = 1; j < houses.size(); j++){
173 HousenumberMatch house = houses.get(j);
174 if (group == null){
175 if (house.isInterpolated())
176 continue;
177 HousenumberMatch predHouse = houses.get(j-1);
178 int deltaNum = predHouse.getHousenumber() - house.getHousenumber();
179 if (Math.abs(deltaNum) > 2)
180 continue;
181 if (HousenumberGroup.housesFormAGroup(predHouse, house))
182 group = new HousenumberGroup(this, houses.subList(j-1, j+1));
183 } else {
184 if (group.tryAddHouse(house) == false){
185 if(group.verify())
186 groups.add(group);
187 group = null;
188 }
189 }
190 }
191 if (group != null && group.verify()){
192 groups.add(group);
193 }
194 }
195 if (groups.isEmpty())
196 return;
197 boolean nodesAdded = false;
198 for (HousenumberGroup group : groups){
199 int oldNumPoints = getRoad().getPoints().size();
200 if (nodesAdded){
201 if (group.recalcPositions() == false)
202 continue;
203 }
204 if (group.findSegment(streetName, groups)){
205 nodesAdded = true;
206 if (log.isDebugEnabled())
207 log.debug("added",getRoad().getPoints().size() - oldNumPoints,"number node(s) at",group.linkNode.toDegreeString(),"for group",group,"in road",getRoad());
208 oldNumPoints = getRoad().getPoints().size();
209 int minSeg = group.minSeg;
210 for (HousenumberMatch house : this.houseNumbers){
211 if (house.getSegment() >= minSeg)
212 HousenumberGenerator.findClosestRoadSegment(house, getRoad());
213 }
214 group.recalcPositions();
215 } else {
216 if(group.linkNode != null){
217 if (log.isDebugEnabled())
218 log.debug("used existing zero-length-segment at",group.linkNode.toDegreeString(),"for group",group,"in road",getRoad());
219 }
220 }
221 }
222 return;
223 }
224
225 /**
226 */
227 public void checkIntervals(){
228 if (extNumbersHead == null)
229 return;
230 boolean anyChanges = false;
231
232 extNumbersHead.detectRandom();
233 for (int loop = 0; loop < 10; loop++){
234 if (loop > 4){
235 // TODO: 3,4,5 ?
236 setRandom(true);
237 }
238 setChanged(false);
239 extNumbersHead = extNumbersHead.checkSingleChainSegments(streetName, removeGaps);
240 extNumbersHead = extNumbersHead.checkChainPlausibility(streetName, houseNumbers);
241 if (isChanged())
242 anyChanges = true;
243 else
244 break;
245 }
246 setChanged(anyChanges);
247 }
248
249
250 /**
251 * Identify duplicate numbers and ignore those which are close together
252 * and those which are probably wrong.
253 */
254 private void filterRealDuplicates() {
255 List<HousenumberMatch> toIgnore = new ArrayList<>();
256 final int TO_SEARCH = 6;
257 int oddLeft = 0, oddRight = 0, evenLeft = 0, evenRight = 0;
258 for (HousenumberMatch house: houseNumbers){
259 if (house.isIgnored())
260 continue;
261 if (house.isLeft()){
262 if (house.getHousenumber() % 2 == 0)
263 evenLeft++;
264 else
265 oddLeft++;
266 } else {
267 if (house.getHousenumber() % 2 == 0)
268 evenRight++;
269 else
270 oddRight++;
271 }
272 }
273 HousenumberMatch usedForCalc = null;
274 for (int i = 1; i < houseNumbers.size(); i++){
275 HousenumberMatch house1 = houseNumbers.get(i - 1);
276 HousenumberMatch house2 = houseNumbers.get(i);
277 if (house1.getSign().equals(house2.getSign()) == false){
278 usedForCalc = null;
279 } else {
280 if (house1.isEqualAddress(house2) == false)
281 continue;
282 // found a duplicate address (e.g. 2 and 2 or 1b and 1b in same road,city etc.)
283 double distBetweenHouses = house2.getLocation().distance(house1.getLocation());
284 double distToUsed = (usedForCalc == null) ? distBetweenHouses : house2.getLocation().distance(usedForCalc.getLocation());
285 if (usedForCalc == null)
286 usedForCalc = (house1.getDistance() < house2.getDistance()) ? house1 : house2;
287 else {
288 house1 = usedForCalc;
289 }
290 boolean sameSide = (house2.isLeft() == house1.isLeft());
291 if (log.isDebugEnabled())
292 log.debug("analysing duplicate address",streetName,house1.getSign(),"for road with id",getRoad().getRoadDef().getId());
293 if (sameSide && (distBetweenHouses < 100 || distToUsed < 100)){
294 HousenumberMatch obsolete = house1 == usedForCalc ? house2 : house1;
295 if (log.isDebugEnabled())
296 log.debug("house",obsolete,obsolete.getElement().toBrowseURL(),"is close to other element and on the same road side, is ignored");
297 toIgnore.add(obsolete);
298 continue;
299 }
300
301 if (!sameSide){
302 if (log.isDebugEnabled())
303 log.debug("oddLeft, oddRight, evenLeft, evenRight:",oddLeft, oddRight, evenLeft, evenRight);
304 HousenumberMatch wrongSide = null;
305 if (house2.getHousenumber() % 2 == 0){
306 if (evenLeft == 1 && (oddLeft > 1 || evenRight > 0 && oddRight == 0)){
307 wrongSide = house2.isLeft() ? house2: house1;
308 }
309 if (evenRight == 1 && (oddRight > 1 || evenLeft > 0 && oddLeft == 0)){
310 wrongSide = !house2.isLeft() ? house2: house1;
311 }
312 } else {
313 if (oddLeft == 1 && (evenLeft > 1 || oddRight > 0 && evenRight == 0)){
314 wrongSide = house2.isLeft() ? house2: house1;
315 }
316 if (oddRight == 1 && (evenRight > 1 || oddLeft > 0 && evenLeft == 0)){
317 wrongSide = !house2.isLeft() ? house2: house1;
318 }
319 }
320 if (wrongSide != null){
321 if (log.isDebugEnabled())
322 log.debug("house",streetName,wrongSide.getSign(),"from",wrongSide.getElement().toBrowseURL(),"seems to be wrong, is ignored");
323 toIgnore.add(wrongSide);
324 continue;
325 }
326 }
327
328 double[] sumDist = new double[2];
329 double[] sumDistSameSide = new double[2];
330 int[] confirmed = new int[2];
331 int[] falsified = new int[2];
332 int[] found = new int[2];
333 List<HousenumberMatch> dups = Arrays.asList(house2, house1);
334 for (int k = 0; k < dups.size(); k++){
335 HousenumberMatch other, curr;
336 if (k == 0){
337 curr = dups.get(0);
338 other = dups.get(1);
339 } else {
340 curr = dups.get(1);
341 other = dups.get(0);
342 }
343 int pos = houseNumbers.indexOf(curr);
344
345 int left = pos - 1;
346 int right = pos + 1;
347 HousenumberMatch nearHouse;
348 int stillToFind = TO_SEARCH;
349 while (stillToFind > 0){
350 int oldDone = stillToFind;
351 if (left >= 0){
352 nearHouse = houseNumbers.get(left);
353 if (nearHouse != other){
354 double dist = curr.getLocation().distance(nearHouse.getLocation());
355 sumDist[k] += dist;
356 if (nearHouse.isLeft() == curr.isLeft()){
357 sumDistSameSide[k] += dist;
358 }
359 if (curr.getHousenumber() == nearHouse.getHousenumber()){
360 if (dist < 20)
361 confirmed[k]++;
362 } else {
363 if (dist < 10 )
364 falsified[k]++;
365 }
366 }
367 --left;
368 stillToFind--;
369 if (stillToFind == 0)
370 break;
371 }
372 if (right < houseNumbers.size()){
373 nearHouse = houseNumbers.get(right);
374 if (nearHouse != other){
375 double dist = curr.getLocation().distance(nearHouse.getLocation());
376 sumDist[k] += dist;
377 if (nearHouse.isLeft() == curr.isLeft()){
378 sumDistSameSide[k] += dist;
379 }
380 if (curr.getHousenumber() == nearHouse.getHousenumber()){
381 if (dist < 40)
382 confirmed[k]++;
383 } else {
384 if (dist < 10 )
385 falsified[k]++;
386 }
387 }
388 stillToFind--;
389 right++;
390 }
391 if (oldDone == stillToFind)
392 break;
393 }
394 found[k] = TO_SEARCH - 1 - stillToFind;
395 }
396 if (log.isDebugEnabled()){
397 log.debug("dup check 1:", streetName, house1, house1.getElement().toBrowseURL());
398 log.debug("dup check 2:", streetName, house2, house2.getElement().toBrowseURL());
399 log.debug("confirmed",Arrays.toString(confirmed),"falsified",Arrays.toString(falsified),"sum-dist",Arrays.toString(sumDist),"sum-dist-same-side",Arrays.toString(sumDistSameSide));
400 }
401 HousenumberMatch bad = null;
402 if (confirmed[1] > 0 && confirmed[0] == 0 && falsified[1] == 0)
403 bad = dups.get(0);
404 else if (confirmed[0] > 0 && confirmed[1] == 0 && falsified[0] == 0)
405 bad = dups.get(1);
406 else if (found[0] > 3 && sumDist[0] > sumDist[1] && sumDistSameSide[0] > sumDistSameSide[1])
407 bad = dups.get(0);
408 else if (found[1] > 3 && sumDist[1] > sumDist[0] && sumDistSameSide[1] > sumDistSameSide[0])
409 bad = dups.get(1);
410 if (bad != null){
411 toIgnore.add(bad);
412 } else {
413 if (log.isDebugEnabled())
414 log.debug("duplicate house number, don't know which one to use, ignoring both");
415 toIgnore.add(house1);
416 toIgnore.add(house2);
417 house2.setIgnored(true);
418 house1.setIgnored(true);
419 }
420 }
421 }
422 for (HousenumberMatch house : toIgnore){
423 if (log.isInfoEnabled())
424 log.info("duplicate housenumber",streetName,house.getSign(),"is ignored for road with id",house.getRoad().getRoadDef().getId(),",house:",house.getElement().toBrowseURL());
425 houseNumbers.remove(house);
426 }
427 }
428
429 /**
430 * Identify groups of buildings with numbers like 1a,1b,1c.
431 * The list in housenumbers is sorted so that 2 appears before 2a and
432 * 2b appears before 2c.
433 * XXX This is quite aggressive, maybe we have to add more logic here.
434 */
435 private void filterGroups() {
436 if (houseNumbers.size() <= 1)
437 return;
438 HousenumberMatch prev = houseNumbers.get(0);
439 HousenumberMatch used = null;
440 for (int i = 1; i < houseNumbers.size(); i++){
441 HousenumberMatch house = houseNumbers.get(i);
442 if (house.getHousenumber() != prev.getHousenumber())
443 used = null;
444 else {
445 if (used == null)
446 used = prev;
447 if (prev.getSign().equals(house.getSign()) && prev.isEqualAddress(house) == false){
448 // we want to keep these duplicates
449 } else {
450 house.setIgnored(true);
451 if (log.isInfoEnabled())
452 log.info("using",streetName,used.getSign(), "in favor of",house.getSign(),"as target for address search");
453 }
454 }
455 prev = house;
456 }
457 }
458
459 public void checkWrongRoadAssignmments(HousenumberRoad other) {
460 if (this.extNumbersHead == null || other.extNumbersHead == null)
461 return;
462
463 for (int loop = 0; loop < 10; loop++){
464 boolean changed = false;
465 ExtNumbers head1 = this.extNumbersHead;
466 for (ExtNumbers en1 = head1; en1 != null; en1 = en1.next){
467 if (changed)
468 break;
469 if (en1.hasNumbers() == false)
470 continue;
471 ExtNumbers head2 = other.extNumbersHead;
472 for (ExtNumbers en2 = head2; en2 != null; en2 = en2.next){
473 if (changed)
474 break;
475 if (en2.hasNumbers() == false)
476 continue;
477 int res = ExtNumbers.checkIntervals(streetName, en1, en2);
478 switch (res) {
479 case ExtNumbers.OK_NO_CHANGES:
480 case ExtNumbers.NOT_OK_KEEP:
481 break;
482 case ExtNumbers.OK_AFTER_CHANGES:
483 changed = true;
484 this.setChanged(true);
485 other.setChanged(true);
486 break;
487 case ExtNumbers.NOT_OK_TRY_SPLIT:
488 if (en1.needsSplit()){
489 ExtNumbers test = en1.tryChange(ExtNumbers.SR_FIX_ERROR);
490 if (test != en1){
491 changed = true;
492 if (test.prev == null){
493 this.extNumbersHead = test;
494 }
495 }
496 }
497 if (en2.needsSplit()){
498 ExtNumbers test = en2.tryChange(ExtNumbers.SR_FIX_ERROR);
499 if (test != en2){
500 changed = true;
501 if (test.prev == null){
502 other.extNumbersHead = test;
503 }
504 }
505 }
506 break;
507 case ExtNumbers.NOT_OK_STOP:
508 return;
509 default:
510 log.error("can't fix",en1,en2);
511 }
512 }
513 }
514 if (!changed)
515 break;
516 }
517 }
518
519 public void setNumbers() {
520 if (extNumbersHead == null)
521 return;
522 if (houseNumbers.isEmpty())
523 return;
524 // make sure that the name we used for the cluster is also attached to the road
525 if (streetName == null){
526 log.error("found no name for road with housenumbers, implement a move to the next named road ?",road);
527 return;
528 }
529 String[] labels = road.getLabels();
530 boolean found = false;
531 for (String label : labels){
532 if (label == null)
533 break;
534 if (streetName.equals(label))
535 found = true;
536 }
537 if (!found){
538 if (labels[0] == null){
539 // add empty label so that the address search name doesn't appear in the map
540 // when the original road did not have any label
541 labels[0] = "";
542 }
543 for (int i = 1; i < labels.length; i++){
544 if (labels[i] == null){
545 labels[i] = streetName;
546 log.info("added label",streetName,"for",road,"Labels are now:",Arrays.toString(labels));
547 found = true;
548 break;
549 }
550 }
551 }
552 if (!found){
553 int last = labels.length-1;
554 String droppedLabel = labels[last];
555 labels[last] = streetName;
556 if (droppedLabel != null){
557 if (log.isInfoEnabled())
558 log.info("dropped label",droppedLabel,"for",road,"in preference to correct address search. Labels are now:",Arrays.toString(labels));
559 }
560 }
561 if (furtherNames != null){
562 boolean changed = false;
563 for (String furtherName : furtherNames){
564 if (road.addLabel(furtherName))
565 changed = true;
566 }
567 if (changed){
568 log.info("added further labels for",road,"Labels are now:",Arrays.toString(labels));
569 }
570 }
571
572 if (road.getZip() == null && roadZipCode != null){
573 road.setZip(roadZipCode.getZipCode());
574 }
575 road.setNumbers(extNumbersHead.getNumberList());
576
577 }
578
579 public MapRoad getRoad(){
580 return road;
581 }
582
583 public CityInfo getRoadCityInfo() {
584 return roadCityInfo;
585 }
586
587 public ZipCodeInfo getRoadZipCode() {
588 return roadZipCode;
589 }
590
591
592 public boolean isChanged() {
593 return changed;
594 }
595
596 public void setChanged(boolean changed) {
597 this.changed = changed;
598 }
599
600 public boolean isRandom() {
601 return isRandom;
602 }
603
604 public void setRandom(boolean isRandom) {
605 if (this.isRandom == false)
606 if (log.isDebugEnabled())
607 log.debug("detected random case",this);
608 this.isRandom = isRandom;
609 }
610
611 public void setRemoveGaps(boolean b) {
612 removeGaps = true;
613 }
614 public boolean getRemoveGaps() {
615 return removeGaps;
616 }
617
618 /**
619 *
620 */
621 public void improveSearchResults() {
622 ExtNumbers curr = extNumbersHead;
623 while (curr != null) {
624 ExtNumbers en = curr.splitLargeGaps();
625 if (en != curr) {
626 if (en.hasNumbers() && en.next != null && en.next.hasNumbers())
627 setChanged(true);
628 else {
629 ExtNumbers test = en.hasNumbers() ? en : en.next;
630 if (test.getNumbers().isSimilar(curr.getNumbers()) == false)
631 setChanged(true);
632 }
633 if (curr.prev == null)
634 extNumbersHead = en;
635 curr = en;
636 continue;
637 }
638 curr = curr.next;
639 }
640 }
641
642 public String toString(){
643 return getRoad().toString() + " " + houseNumbers;
644 }
645
646
647 /**
648 * Check if street name is set, if not, try to find one.
649 * Identify those houses which are assigned to this road because it was the closest,
650 * but can't be correct because street name doesn't match.
651 *
652 * @param road2HousenumberRoadMap maps {@link MapRoad} instances to corresponding
653 * {@link HousenumberRoad} instances
654 * @param nodeId2RoadLists maps node ids to the {@link MapRoad} that use the corresponding nodes.
655 * @return
656 */
657 public List<HousenumberMatch> checkStreetName(Map<MapRoad, HousenumberRoad> road2HousenumberRoadMap, Int2ObjectOpenHashMap<HashSet<MapRoad>> nodeId2RoadLists) {
658 List<HousenumberMatch> noWrongHouses = Collections.emptyList();
659 List<HousenumberMatch> wrongHouses = Collections.emptyList();
660 double minDist = Double.MAX_VALUE;
661 double maxDist = 0;
662 if (houseNumbers.isEmpty() == false){
663 HashMap<String, Integer>possibleStreetNamesFromHouses = new HashMap<>();
664 HashMap<String, Integer>possiblePlaceNamesFromHouses = new HashMap<>();
665 for (HousenumberMatch house : houseNumbers){
666 if (house.getDistance() > maxDist)
667 maxDist = house.getDistance();
668 if (house.getDistance() < minDist)
669 minDist = house.getDistance();
670 String potentialName = house.getStreet();
671 if (potentialName != null){
672 Integer oldCount = possibleStreetNamesFromHouses.put(potentialName, 1);
673 if (oldCount != null)
674 possibleStreetNamesFromHouses.put(potentialName, oldCount + 1);
675 }
676 String placeName = house.getPlace();
677 if (placeName != null){
678 Integer oldCount = possiblePlaceNamesFromHouses.put(placeName, 1);
679 if (oldCount != null)
680 possiblePlaceNamesFromHouses.put(placeName, oldCount + 1);
681 }
682 }
683 HashSet<String> connectedRoadNames = new HashSet<>();
684 for (Coord co : road.getPoints()){
685 if (co.getId() == 0)
686 continue;
687 HashSet<MapRoad> connectedRoads = nodeId2RoadLists.get(co.getId());
688 for (MapRoad r : connectedRoads){
689 if (r.getStreet() != null)
690 connectedRoadNames.add(r.getStreet());
691 }
692 }
693 if (streetName != null){
694 if (possibleStreetNamesFromHouses.isEmpty()){
695 // ok, houses have no street name
696 return noWrongHouses;
697 }
698 if (possibleStreetNamesFromHouses.size() == 1){
699 if (possibleStreetNamesFromHouses.containsKey(streetName)){
700 // ok, houses have same name as street
701 return noWrongHouses;
702 }
703 }
704 }
705 if (possibleStreetNamesFromHouses.isEmpty()){
706 // neither road not houses tell us a street name
707 if (furtherNames != null && furtherNames.size() > 0){
708 Iterator<String> iter = furtherNames.iterator();
709 streetName = iter.next();
710 iter.remove();
711 if (furtherNames.isEmpty())
712 furtherNames = null;
713 }
714 return noWrongHouses;
715 }
716 if (streetName == null){
717 if (possibleStreetNamesFromHouses.size() == 1){
718 String potentialName = possibleStreetNamesFromHouses.keySet().iterator().next();
719 boolean nameOK = false;
720 if (connectedRoadNames.contains(potentialName))
721 nameOK = true;
722 else if (houseNumbers.size() > 1){
723 nameOK = true;
724 } else if (maxDist <= 10){
725 nameOK = true;
726 }
727 if (nameOK){
728 streetName = potentialName;
729 return noWrongHouses; // all good, return empty list
730
731 }
732 } else {
733 List<String> matchingNames = new ArrayList<>();
734 for (Entry<String, Integer> entry : possibleStreetNamesFromHouses.entrySet()){
735 String name = entry.getKey();
736 if (connectedRoadNames.contains(name)){
737 matchingNames.add(name);
738 }
739 }
740 if (matchingNames.size() == 1){
741 streetName = matchingNames.get(0);
742 }
743 }
744
745
746 }
747 // if we get here we have no usable street name
748 wrongHouses = new ArrayList<>();
749 Iterator<HousenumberMatch> iter = houseNumbers.iterator();
750 while (iter.hasNext()){
751 HousenumberMatch house = iter.next();
752 if (streetName != null){
753 if (house.getStreet() == null || streetName.equals(house.getStreet()))
754 continue;
755 } else if (house.getPlace() != null)
756 continue;
757 double bestDist = Double.MAX_VALUE;
758 HousenumberMatch best = null;
759 for (MapRoad altRoad : house.getAlternativeRoads()){
760 if (house.getStreet() != null){
761 if (house.getStreet().equals(altRoad.getStreet())){
762 HousenumberMatch test = new HousenumberMatch(house);
763 HousenumberGenerator.findClosestRoadSegment(test, altRoad);
764 if (test.getDistance() < bestDist){
765 best = test;
766 bestDist = test.getDistance();
767 }
768 }
769 }
770 }
771 iter.remove();
772 if (best != null){
773 best.calcRoadSide();
774 wrongHouses.add(best);
775 } else {
776 log.warn("found no plausible road for address",house.getStreet(),house,house.getElement().toBrowseURL());
777 }
778 }
779
780 }
781 return wrongHouses;
782 }
783
784 public void addHouse(HousenumberMatch house) {
785 if (extNumbersHead != null){
786 log.error("internal error: trying to add house to road that was already processed",this.getRoad(),house);
787 }
788 house.setHousenumberRoad(this);
789 houseNumbers.add(house);
790 }
791
792 public List<HousenumberMatch> getHouses() {
793 return houseNumbers;
794 }
795
796
797 public void setZipCodeInfo(ZipCodeInfo zipInfo) {
798 roadZipCode = zipInfo;
799 }
800 }
801
802
2121 /**
2222 * Superclass of the node, segment and way OSM elements.
2323 */
24 public abstract class Element {
25 private Tags tags;
26 private long id;
27 private long originalId;
28
29 public int getTagCount() {
30 return (tags == null ? 0 : tags.size());
24 public abstract class Element {
25 private Tags tags;
26 private long id;
27 private long originalId;
28
29 public int getTagCount() {
30 return (tags == null ? 0 : tags.size());
3131 }
3232
3333 /**
167167 }
168168
169169 public long getId() {
170 return id;
171 }
172
173 /**
174 * Returns the Id of the original OSM element on which this element was based.
175 * <p>
176 * The Id of the original element will be different from the Id of this
177 * element if this element uses a faked Id.
178 */
179 public long getOriginalId() {
180 return originalId;
181 }
182
183 protected void setId(long id) {
184 this.id = id;
185 originalId = id;
186 }
187
188 public void setFakeId() {
170 return id;
171 }
172
173 /**
174 * Returns the Id of the original OSM element on which this element was based.
175 * <p>
176 * The Id of the original element will be different from the Id of this
177 * element if this element uses a faked Id.
178 */
179 public long getOriginalId() {
180 return originalId;
181 }
182
183 protected void setId(long id) {
184 this.id = id;
185 originalId = id;
186 }
187
188 public void setFakeId() {
189189 id = FakeIdGenerator.makeFakeId();
190190 }
191191
8383 usedTags.add("cycleway:left");
8484 usedTags.add("cycleway:right");
8585 }
86
87 // add addr:street and addr:housenumber if housenumber search is enabled
88 if (props.getProperty("housenumbers", false)) {
89 usedTags.add("addr:street");
90 usedTags.add("addr:housenumber");
91 }
92
9386 return true;
9487 }
9588
0 /*
1 * Copyright (C) 2014.
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
13 package uk.me.parabola.mkgmap.reader.osm;
14
15 import java.util.ArrayList;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Set;
19
20 import uk.me.parabola.imgfmt.app.Coord;
21 import uk.me.parabola.log.Logger;
22 import uk.me.parabola.util.EnhancedProperties;
23
24 /**
25 * Collect data from ways with addr:interpolation tag.
26 * @author GerdP
27 *
28 */
29 public class HousenumberHooks extends OsmReadingHooksAdaptor {
30 private static final Logger log = Logger.getLogger(HousenumberHooks.class);
31
32 private ElementSaver saver;
33 private Node currentNodeInWay;
34 private final List<Node> nodes = new ArrayList<>();
35 private boolean clearNodes;
36
37 private static final short addrStreetTagKey = TagDict.getInstance().xlate("addr:street");
38 private static final short addrHousenumberTagKey = TagDict.getInstance().xlate("addr:housenumber");
39 private static final short addrInterpolationTagKey = TagDict.getInstance().xlate("addr:interpolation");
40
41 public static final short partOfInterpolationTagKey = TagDict.getInstance().xlate("mkgmap:part-of-interpolation");
42 public static final short mkgmapNodeIdsTagKey = TagDict.getInstance().xlate("mkgmap:node-ids");
43 @Override
44 public boolean init(ElementSaver saver, EnhancedProperties props) {
45 this.saver = saver;
46 if (props.getProperty("addr-interpolation", true) == false)
47 return false;
48 return (props.getProperty("housenumbers", false));
49 }
50
51 @Override
52 public Set<String> getUsedTags() {
53 HashSet<String> usedTags = new HashSet<>();
54 usedTags.add("addr:street");
55 usedTags.add("addr:housenumber");
56 usedTags.add("addr:interpolation");
57 usedTags.add("addr:place");
58 return usedTags;
59 }
60
61 @Override
62 public void onCoordAddedToWay(Way way, long id, Coord co) {
63 if (clearNodes){
64 nodes.clear();
65 clearNodes = false;
66 }
67 currentNodeInWay = saver.getNode(id);
68 if (currentNodeInWay == null)
69 return;
70 if (currentNodeInWay.getTag(addrHousenumberTagKey) == null)
71 return;
72 // this node might be part of a way that has the addr:interpolation tag
73 nodes.add(currentNodeInWay);
74 }
75
76 @Override
77 public void onAddWay(Way way) {
78 clearNodes = true; // make sure that the list is cleared with the next coord
79 String ai = way.getTag(addrInterpolationTagKey);
80 if (ai == null)
81 return;
82 if (nodes.size() < 2){
83 log.warn(way.toBrowseURL(),"tag addr:interpolation="+ai, "is ignored, found less than two valid nodes.");
84 return;
85 }
86 switch (ai) {
87 case "odd":
88 case "even":
89 case "all":
90 case "1":
91 case "2":
92 break;
93 default:
94 if (log.isInfoEnabled())
95 log.warn(way.toBrowseURL(),"tag addr:interpolation="+ai, "is ignored");
96 return;
97 }
98
99 StringBuilder sb = new StringBuilder();
100 int num = nodes.size();
101 for (int i = 0; i < num; i++) {
102 Node n = nodes.get(i);
103 String id = String.valueOf(n.getId());
104 sb.append(id);
105 if (i + 1 < num)
106 sb.append(",");
107 n.addTag(partOfInterpolationTagKey, "1");
108 }
109 way.addTag(mkgmapNodeIdsTagKey, sb.toString());
110 }
111
112 @Override
113 public void end() {
114 nodes.clear();
115 }
116 }
2020 import java.util.HashMap;
2121 import java.util.HashSet;
2222 import java.util.IdentityHashMap;
23 import java.util.LinkedHashMap;
24 import java.util.LinkedHashSet;
2325 import java.util.List;
2426 import java.util.Map;
2527 import java.util.Map.Entry;
4850 private IdentityHashMap<Coord, Set<Way>> adjacentWays = new IdentityHashMap<Coord, Set<Way>>();
4951 /** Contains all _link ways that have to be processed */
5052 private Map<Long, Way> destinationLinkWays = new HashMap<Long, Way>();
51
52 private HashSet<String> tagValues = new HashSet<String>(Arrays.asList(
53 "motorway_link", "trunk_link"));
53
54 private final static Set<String> highwayTypes = new LinkedHashSet<String>(Arrays.asList(
55 "motorway", "trunk", "primary", "motorway_link", "trunk_link", "primary_link"));
56 private HashSet<String> linkTypes = new HashSet<String>(Arrays.asList(
57 "motorway_link", "trunk_link", "primary_link"));
58
5459
5560 /** Map way ids to its restriction relations so that the relations can easily be updated when the way is split. */
5661 private MultiHashMap<Long, RestrictionRelation> restrictions = new MultiHashMap<>();
7277 }
7378
7479 /**
75 * Fills the internal lists with the
80 * Fills the internal lists
7681 */
7782 private void retrieveWays() {
7883 // collect all ways tagged with highway
8287 continue;
8388 }
8489 String highwayTag = w.getTag("highway");
85 if (highwayTag != null) {
90 if (highwayTag != null && highwayTypes.contains(highwayTag)) {
8691 // the points of the way are kept so that it is easy to get
8792 // the adjacent ways for a given _link way
8893 String directedDestination = null;
112117
113118 // if the way is a link way and has a destination tag
114119 // put it the list of ways that have to be processed
115 if (tagValues.contains(highwayTag)) {
120 if (linkTypes.contains(highwayTag)) {
116121 String destinationTag = w.getTag("destination");
117122
118123 if (destinationTag == null) {
439444 * mkgmap:exit_hint_ref, mkgmap:exit_hint_name and/or mkgmap:exit_hint_exit_to.
440445 */
441446 private void processWays() {
442 // collect all nodes of highway=motorway/trunk ways so that we can check if an exit node
443 // belongs to a motorway/trunk or is a "subexit" within a motorway/trunk junction
444 Set<Coord> motorwayCoords = new HashSet<Coord>();
445 Set<Coord> trunkCoords = new HashSet<Coord>();
446 if (processExits){
447 for (Way w : saver.getWays().values()) {
448 String motorwayTag = w.getTag("highway");
449 if (motorwayTag != null) {
450 if (motorwayTag.equals("motorway"))
451 motorwayCoords.addAll(w.getPoints());
452 else if (motorwayTag.equals("trunk"))
453 trunkCoords.addAll(w.getPoints());
454 }
455 }
456 }
457
458
459447 // remove the adjacent links from the destinationLinkWays list
460448 // to avoid duplicate dest_hints
461449 Queue<Way> linksWithDestination = new ArrayDeque<Way>();
464452 while (linksWithDestination.isEmpty()== false) {
465453 Way linkWay = linksWithDestination.poll();
466454 String destination = linkWay.getTag("destination");
467
468455
469456 if (log.isDebugEnabled())
470457 log.debug("Check way",linkWay.getId(),linkWay.toTagString());
499486 log.debug(destinationLinkWays.size(),"links with destination tag after cleanup");
500487
501488 if (processExits) {
489 // collect all nodes of highway=motorway/trunk ways so that we can check if an exit node
490 // belongs to a motorway/trunk or is a "subexit" within a motorway/trunk junction
491
492 Map<String, Set<Coord>> highwayCoords = new LinkedHashMap<>();
493 for (String type : highwayTypes){
494 highwayCoords.put(type, new HashSet<Coord>());
495 }
496 for (Way w : saver.getWays().values()) {
497 String highwayTag = w.getTag("highway");
498 if (highwayTag == null)
499 continue;
500 if (highwayTypes.contains(highwayTag)){
501 Set<Coord> set = highwayCoords.get(highwayTag);
502 set.addAll(w.getPoints());
503 }
504 }
505
502506 // get all nodes tagged with highway=motorway_junction
503507 for (Node exitNode : saver.getNodes().values()) {
504508 if (isTaggedAsExit(exitNode) && saver.getBoundingBox().contains(exitNode.getLocation())) {
505
506 boolean isMotorwayExit = motorwayCoords.contains(exitNode.getLocation());
507 boolean isTrunkExit = trunkCoords.contains(exitNode.getLocation());
508 boolean isHighwayExit = isMotorwayExit || isTrunkExit;
509 // use exits only if they are located on a motorway or trunk
510 if (isHighwayExit == false) {
509 String expectedHighwayTag = null;
510 for (Entry<String, Set<Coord>> entry : highwayCoords.entrySet()){
511 if (entry.getValue().contains(exitNode.getLocation())){
512 expectedHighwayTag = entry.getKey();
513 break;
514 }
515 }
516 if (expectedHighwayTag == null){
517 // use exits only if they are located on a motorway or trunk
511518 if (log.isDebugEnabled())
512519 log.debug("Skip non highway exit:", exitNode.toBrowseURL(), exitNode.toTagString());
513520 continue;
514521 }
515
516522 // retrieve all ways with this exit node
517523 Set<Way> exitWays = adjacentWays.get(exitNode.getLocation());
518524 if (exitWays==null) {
524530 // the inserted node has the correct orientation
525531 List<Entry<Coord, Way>> nextNodes = getNextNodes(exitNode.getLocation(), true);
526532 Coord nextHighwayNode = null;
527 String expectedHighwayTag = (isMotorwayExit ? "motorway" : "trunk");
533 int countMatches = 0;
528534 for (Entry<Coord, Way> nextNode : nextNodes) {
529535 if (expectedHighwayTag.equals(nextNode.getValue().getTag("highway"))) {
530536 nextHighwayNode = nextNode.getKey();
531 break;
537 countMatches++;
532538 }
539 }
540 if (countMatches > 1){
541 // may happen when the highway is a link which splits further into two or more links
542 // ignore the node
543 nextHighwayNode = null;
533544 }
534545
535546 // use link ways only
547558 String highwayLinkTag = w.getTag("highway");
548559 if (highwayLinkTag.endsWith("_link")) {
549560 log.debug("Try to cut",highwayLinkTag, w, "into three parts for giving hint to exit", exitNode);
550
551561 // calc the way length to decide how to cut the way
552562 double wayLength = getLength(w);
553563 if (wayLength < 10 && w.getPoints().size() < 3) {
586596 }
587597 if (exitNode.getTag("ref") != null)
588598 hintWay.addTag("mkgmap:exit_hint_ref", exitNode.getTag("ref"));
589 if (exitNode.getTag("exit_to") != null)
590 hintWay.addTag("mkgmap:exit_hint_exit_to", exitNode.getTag("exit_to"));
591 if (getName(exitNode) != null)
599 if (countMatches == 1){
600 if (exitNode.getTag("exit_to") != null){
601 hintWay.addTag("mkgmap:exit_hint_exit_to", exitNode.getTag("exit_to"));
602 }
603 }
604 if (getName(exitNode) != null){
592605 hintWay.addTag("mkgmap:exit_hint_name", getName(exitNode));
606 }
593607
594608 if (log.isInfoEnabled())
595609 log.info("Cut off exit hint way", hintWay, hintWay.toTagString());
705719 adjacentWays = null;
706720 wayNodes = null;
707721 destinationLinkWays = null;
708 tagValues = null;
722 linkTypes = null;
709723 saver = null;
710724 nameTags = null;
711725 }
1313 * Author: Steve Ratcliffe
1414 * Create date: 22-Sep-2007
1515 */
16 package uk.me.parabola.mkgmap.reader.osm;
17
18 import java.io.BufferedReader;
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.FileReader;
22 import java.io.IOException;
16 package uk.me.parabola.mkgmap.reader.osm;
17
18 import java.io.BufferedReader;
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.FileReader;
22 import java.io.IOException;
2323 import java.io.InputStream;
2424 import java.util.ArrayList;
2525 import java.util.HashMap;
2626 import java.util.HashSet;
2727 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30
31 import uk.me.parabola.imgfmt.ExitException;
32 import uk.me.parabola.imgfmt.FormatException;
33 import uk.me.parabola.imgfmt.Utils;
34 import uk.me.parabola.log.Logger;
28 import java.util.Map;
29 import java.util.Set;
30
31 import uk.me.parabola.imgfmt.ExitException;
32 import uk.me.parabola.imgfmt.FormatException;
33 import uk.me.parabola.imgfmt.Utils;
34 import uk.me.parabola.log.Logger;
3535 import uk.me.parabola.mkgmap.general.LevelInfo;
3636 import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
3737 import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
6161 new HighwayHooks(),
6262 new LocationHook(),
6363 new POIGeneratorHook(),
64 new HousenumberHooks(),
6465 };
6566 protected OsmConverter converter;
6667 private final Set<String> usedTags = new HashSet<String>();
133134 * include a fixed set of strings on the assumption that .osm files
134135 * are probably going to have the OSM copyright statements.
135136 *
136 * @return A list of copyright messages as a String array.
137 */
138 public String[] copyrightMessages() {
139 String copyrightFileName = getConfig().getProperty("copyright-file", null);
140 if (copyrightFileName != null)
141 {
142 File file = new File(copyrightFileName);
143 List<String> copyrightArray = new ArrayList<String>();
144 try {
145 BufferedReader reader = new BufferedReader(new FileReader(file));
146 String text;
147 while ((text = reader.readLine()) != null) {
148 copyrightArray.add(text);
149 }
150
151 reader.close();
152 } catch (FileNotFoundException e) {
153 throw new ExitException("Could not open copyright file " + copyrightFileName);
154 } catch (IOException e) {
155 throw new ExitException("Error reading copyright file " + copyrightFileName);
156 }
157 String[] copyright = new String[copyrightArray.size()];
158 copyrightArray.toArray(copyright);
159 return copyright;
160 }
161 String note = getConfig().getProperty("copyright-message",
162 "OpenStreetMap.org contributors. See: http://wiki.openstreetmap.org/index.php/Attribution");
163 return new String[] { note };
137 * @return A list of copyright messages as a String array.
138 */
139 public String[] copyrightMessages() {
140 String copyrightFileName = getConfig().getProperty("copyright-file", null);
141 if (copyrightFileName != null)
142 {
143 File file = new File(copyrightFileName);
144 List<String> copyrightArray = new ArrayList<String>();
145 try {
146 BufferedReader reader = new BufferedReader(new FileReader(file));
147 String text;
148 while ((text = reader.readLine()) != null) {
149 copyrightArray.add(text);
150 }
151
152 reader.close();
153 } catch (FileNotFoundException e) {
154 throw new ExitException("Could not open copyright file " + copyrightFileName);
155 } catch (IOException e) {
156 throw new ExitException("Error reading copyright file " + copyrightFileName);
157 }
158 String[] copyright = new String[copyrightArray.size()];
159 copyrightArray.toArray(copyright);
160 return copyright;
161 }
162 String note = getConfig().getProperty("copyright-message",
163 "OpenStreetMap.org contributors. See: http://wiki.openstreetmap.org/index.php/Attribution");
164 return new String[] { note };
164165 }
165166
166167 protected void setStyle(Style style) {
365365 if (r instanceof MultiPolygonRelation == false) {
366366 continue;
367367 }
368 // boundary relations may have a node with role admin_centre, if yes, use the
369 // location of it
370 Node existingPOI = null;
371 if ("boundary".equals(r.getTag("type")) && "administrative".equals(r.getTag("boundary"))){
368 Node admin_centre = null;
369 Node labelPOI = null;
370 String relName = getName(r);
371 if (relName != null){
372372 for (Entry<String, Element> pair : r.getElements()){
373373 String role = pair.getKey();
374 Element el = pair.getValue();
375 if ("admin_centre".equals(role)){
376 if (el instanceof Node){
377 String bName = getName(r);
378 String pName = getName(el);
379 if (bName != null && bName.equals(pName))
380 existingPOI = (Node) el;
374 Element el = pair.getValue();
375 if (el instanceof Node){
376 if ("admin_centre".equals(role)){
377 if ("boundary".equals(r.getTag("type")) && "administrative".equals(r.getTag("boundary"))){
378 // boundary relations may have a node with role admin_centre, if yes, use the
379 // location of it
380 String pName = getName(el);
381 if (relName.equals(pName)){
382 admin_centre = (Node) el;
383 if (log.isDebugEnabled())
384 log.debug("using admin_centre node as location for POI for rel",r.getId(),relName,"at",((Node) el).getLocation().toDegreeString());
385 }
386 }
387 } else if ("label".equals(role)){
388 String label = getName(el);
389 if (relName.equals(label)){
390 labelPOI = (Node) el;
391 log.debug("using label node as location for POI for rel",r.getId(),relName,"at",((Node) el).getLocation().toDegreeString());
392 break;
393 } else {
394 log.warn("rel",r.toBrowseURL(),",node with role label is ignored because it has a different name");
395 }
381396 }
382 break;
383397 }
384398 }
385399 }
386 Coord point;
387 if (existingPOI ==null)
400 Coord point = null;
401 if (admin_centre == null && labelPOI == null)
388402 point = ((MultiPolygonRelation)r).getCofG();
389 else
390 point = existingPOI.getLocation();
403 else {
404 if (labelPOI != null)
405 point = labelPOI.getLocation();
406 else
407 point = admin_centre.getLocation();
408 }
391409 if (point == null) {
392410 continue;
393411 }
5353 public void setFinalizeRule(Rule finalizeRule);
5454
5555 public void printStats(String header);
56
57 public Rule getFinalizeRule();
5658
5759 }
192192
193193 double lat = 0;
194194 double lon = 0;
195 for(Coord p : points) {
195 if (hasIdenticalEndPoints())
196 numPoints--;
197 for (int i = 0; i < numPoints; i++){
198 Coord p = points.get(i);
196199 lat += (double)p.getHighPrecLat()/numPoints;
197200 lon += (double)p.getHighPrecLon()/numPoints;
198201 }
6666
6767 int tagCount = binNode.getKeysCount();
6868 if (tagCount > 0) {
69 Node node = new Node(id, co);
70 for (int tid = 0; tid < tagCount; tid++) {
71 String key = getStringById(binNode.getKeys(tid));
72 String val = getStringById(binNode.getVals(tid)).trim();
73 key = keepTag(key, val);
74 if (key != null)
75 node.addTag(key, val.intern());
69 Node node = new Node(id, co);
70 for (int tid = 0; tid < tagCount; tid++) {
71 String key = getStringById(binNode.getKeys(tid));
72 String val = getStringById(binNode.getVals(tid)).trim();
73 key = keepTag(key, val);
74 if (key != null)
75 node.addTag(key, val.intern());
7676 }
7777
7878 saver.addNode(node);
101101 int ntags = 0;
102102 Node node = null;
103103 while (nodes.getKeysVals(kvid) != 0) {
104 int keyid = nodes.getKeysVals(kvid++);
105 int valid = nodes.getKeysVals(kvid++);
106 String key = getStringById(keyid);
107 String val = getStringById(valid).trim();
108 key = keepTag(key, val);
109 if (key != null) {
110 if (node == null)
104 int keyid = nodes.getKeysVals(kvid++);
105 int valid = nodes.getKeysVals(kvid++);
106 String key = getStringById(keyid);
107 String val = getStringById(valid).trim();
108 key = keepTag(key, val);
109 if (key != null) {
110 if (node == null)
111111 node = new Node(id, co);
112112 node.addTag(key, val.intern());
113113 ntags++;
128128 for (Osmformat.Way binWay : ways) {
129129 Way way = startWay(binWay.getId());
130130
131 for (int j = 0; j < binWay.getKeysCount(); j++) {
132
133 String key = getStringById(binWay.getKeys(j));
134 String val = getStringById(binWay.getVals(j)).trim();
135 key = keepTag(key, val);
136 if (key != null)
137 way.addTag(key, val.intern());
131 for (int j = 0; j < binWay.getKeysCount(); j++) {
132
133 String key = getStringById(binWay.getKeys(j));
134 String val = getStringById(binWay.getVals(j)).trim();
135 key = keepTag(key, val);
136 if (key != null)
137 way.addTag(key, val.intern());
138138 }
139139
140140 long nid = 0;
153153 long id = binRel.getId();
154154 GeneralRelation rel = new GeneralRelation(id);
155155
156 boolean tagsIncomplete = false;
157 for (int j = 0; j < binRel.getKeysCount(); j++) {
158 String key = getStringById(binRel.getKeys(j));
159 String val = getStringById(binRel.getVals(j)).trim();
160 // type is required for relations - all other tags are filtered
161 if ("type".equals(key))
162 // intern the string
156 boolean tagsIncomplete = false;
157 for (int j = 0; j < binRel.getKeysCount(); j++) {
158 String key = getStringById(binRel.getKeys(j));
159 String val = getStringById(binRel.getVals(j)).trim();
160 // type is required for relations - all other tags are filtered
161 if ("type".equals(key))
162 // intern the string
163163 key = "type";
164164 else
165165 key = keepTag(key, val);
310310
311311 private boolean readTags(Element elem) throws IOException{
312312 boolean tagsIncomplete = false;
313 while (bytesToRead > 0){
314 readStringPair();
315 String key = stringPair[0];
316 String val = stringPair[1].trim();
317 // the type tag is required for relations - all other tags are filtered
318 if (elem instanceof Relation && "type".equals(key))
319 // intern the string
313 while (bytesToRead > 0){
314 readStringPair();
315 String key = stringPair[0];
316 String val = stringPair[1].trim();
317 // the type tag is required for relations - all other tags are filtered
318 if (elem instanceof Relation && "type".equals(key))
319 // intern the string
320320 key = "type";
321321 else
322322 key = keepTag(key, val);
203203 * @param qName The new tag name.
204204 * @param attributes Its attributes.
205205 */
206 private void startInNode(String qName, Attributes attributes) {
207 if (qName.equals("tag")) {
208 String key = attributes.getValue("k");
209 String val = attributes.getValue("v").trim();
210
211 // We only want to create a full node for nodes that are POI's
212 // and not just one point of a way. Only create if it has tags that
206 private void startInNode(String qName, Attributes attributes) {
207 if (qName.equals("tag")) {
208 String key = attributes.getValue("k");
209 String val = attributes.getValue("v").trim();
210
211 // We only want to create a full node for nodes that are POI's
212 // and not just one point of a way. Only create if it has tags that
213213 // could be used in a POI.
214214 key = keepTag(key, val);
215215 if (key != null) {
231231 private void startInWay(String qName, Attributes attributes) {
232232 if (qName.equals("nd")) {
233233 long id = idVal(attributes.getValue("ref"));
234 addCoordToWay(currentWay, id);
235 } else if (qName.equals("tag")) {
236 String key = attributes.getValue("k");
237 String val = attributes.getValue("v").trim();
238 key = keepTag(key, val);
239 if (key != null)
240 currentWay.addTag(key, val.intern());
234 addCoordToWay(currentWay, id);
235 } else if (qName.equals("tag")) {
236 String key = attributes.getValue("k");
237 String val = attributes.getValue("v").trim();
238 key = keepTag(key, val);
239 if (key != null)
240 currentWay.addTag(key, val.intern());
241241 }
242242 }
243243
272272 } else
273273 el = null;
274274 if (el != null) // ignore non existing ways caused by splitting files
275 currentRelation.addElement(attributes.getValue("role"), el);
276 } else if (qName.equals("tag")) {
277 String key = attributes.getValue("k");
278 String val = attributes.getValue("v").trim();
279 // the type tag is required for relations - all other tags are filtered
280 if ("type".equals(key))
281 // intern the key
275 currentRelation.addElement(attributes.getValue("role"), el);
276 } else if (qName.equals("tag")) {
277 String key = attributes.getValue("k");
278 String val = attributes.getValue("v").trim();
279 // the type tag is required for relations - all other tags are filtered
280 if ("type".equals(key))
281 // intern the key
282282 key = "type";
283283 else
284284 key = keepTag(key, val);
1818 import java.util.ArrayList;
1919 import java.util.HashMap;
2020 import java.util.List;
21 import java.util.ListIterator;
2221 import java.util.Map;
2322
2423 import uk.me.parabola.imgfmt.app.Coord;
4342
4443 // routing node store, persistent over resets
4544 private final Map<Long, CoordNode> nodeCoords = new HashMap<>();
46
47 // Next node number to use for nodes constructed for house numbers. Persists over reset.
48 private long houseNumberNodeNumber = 16000000;
4945
5046 private int roadId;
5147 private final List<NodeIndex> nodes = new ArrayList<>();
126122 if (log.isDebugEnabled())
127123 log.debug("finishing road id " + roadId);
128124
129 MapRoad road = new MapRoad(roadId, l);
125 MapRoad road = new MapRoad(roadId, roadId, l);
130126
131127 // Set parameters.
132128 road.setRoadClass(roadClass);
138134 road.setAccess(mkgmapAccess);
139135
140136 if (numbers != null && !numbers.isEmpty()) {
141 convertNodesForHouseNumbers();
137 convertNodesForHouseNumbers(road);
142138 road.setNumbers(numbers);
143139 }
144140
145141 List<Coord> points = road.getPoints();
146 road.setNumNodes(nodes.size());
147
148 boolean starts = false;
149 boolean intern = false;
142
150143 for (NodeIndex ni : nodes) {
151144 int n = ni.index;
152 if (n == 0)
153 starts = true;
154 else if (n < points.size() - 1)
155 intern = true;
156145 if (log.isDebugEnabled())
157146 log.debug("road has " + points.size() +" points");
158147 Coord coord = points.get(n);
168157 log.warn("Inconsistant node ids");
169158 }
170159 }
171 road.setStartsWithNode(starts);
172 road.setInternalNodes(intern);
173160
174161 return road;
175162 }
176163
177164 /**
178 * Convert the node index into a routing node number.
179 *
180 * If necessary a new routing node is created, if there is not one already
181 * These constructed routing nodes are not connected to any other road and so
182 * should be marked as such in the NOD2 bit stream, but we don't appear to do that yet.
183 *
165 * Make sure that each node that is referenced by the house
166 * numbers is a number node. Some of them will later be changed
167 * to routing nodes.
184168 * Only called if numbers is non-null and not empty.
185169 */
186 private void convertNodesForHouseNumbers() {
170 private void convertNodesForHouseNumbers(MapRoad road) {
171 int rNodNumber = 0;
187172 for (Numbers n : numbers) {
188173 int node = n.getNodeNumber();
189
190 // This assumes that the nodes are sorted by index.
191 ListIterator<NodeIndex> iterator = nodes.listIterator();
192 while (iterator.hasNext()) {
193 NodeIndex ni = iterator.next();
194 if (ni.index == node) {
195 // It was already there (a common case)
196 n.setRnodNumber(iterator.previousIndex());
197 break;
198 } else if (ni.index > node) {
199 // there is no routing node for this node index, need to insert one.
200 break;
201 }
202 }
203
204 // If we don't have a routing node number then we have to construct one.
205 if (!n.hasRnodNumber()) {
206 NodeIndex hnNode = new NodeIndex(new String[] {
207 String.valueOf(node),
208 String.valueOf(houseNumberNodeNumber++),
209 "0"
210 });
211
212 iterator.previous();
213 iterator.add(hnNode);
214 n.setRnodNumber(iterator.previousIndex());
215 //System.out.printf("ADDING RN on %d, hn=%s, rn=%d\n", roadId, hnNode, n.getRnodNumber());
216 }
217 }
218
219 // Sanity checking. TODO remove
220 //int lastInd = -1;
221 //for (NodeIndex n : nodes) {
222 // assert n.index > lastInd;
223 // lastInd = n.index;
224 //
225 //}
226 //System.out.println("start");
227 //Numbers num = null;
228 //for (Numbers n1 : numbers) {
229 // int ncount = 0;
230 // for (NodeIndex n : nodes) {
231 // System.out.printf("n1.node=%d, ni=%s, ni.index=%d\n", n1.getNodeNumber(), n, n.index);
232 // if (n1.getNodeNumber() == n.index) {
233 // num = n1;
234 // break;
235 // }
236 // ncount++;
237 // }
238 // assert num != null && num.getRnodNumber() == ncount;
239 //}
174 n.setIndex(rNodNumber++);
175 road.getPoints().get(node).setNumberNode(true);
176 }
240177 }
241178
242179 public boolean isRoad() {
1717
1818 import java.util.ArrayList;
1919 import java.util.List;
20 import java.util.Properties;
2021
2122 import uk.me.parabola.imgfmt.app.Coord;
2223 import uk.me.parabola.mkgmap.general.MapCollector;
2324 import uk.me.parabola.mkgmap.general.MapLine;
2425 import uk.me.parabola.mkgmap.general.MapPoint;
2526 import uk.me.parabola.mkgmap.general.MapShape;
27 import uk.me.parabola.mkgmap.reader.osm.GType;
2628
2729
2830 /**
5052 private static final double ELEMENT_SPACING = 0.002;
5153 private static final double ELEMENT_SIZE = 0.001;
5254
53 // I don't know what the max types and subtypes actually are, adjust if
55 // I don't know what the max types and sub-types actually are, adjust if
5456 // there seems to be more beyond.
5557 private static final int MAX_POINT_TYPE = 0x7f;
56 private static final int MAX_POINT_SUB_TYPE = 0x30;
58 private static final int MAX_POINT_SUB_TYPE = 0x1f;
5759
5860 // we draw lines and polygons in a 16x16 square (or whatever is here).
59 private static final int MAX_LINE_TYPE_X = 8;
60 private static final int MAX_LINE_TYPE_Y = 8;
61 private static final int MAX_SHAPE_TYPE_X = 12;
62 private static final int MAX_SHAPE_TYPE_Y = 12;
61 private static final int MAX_LINE_TYPE_X = 4;
62 private static final int MAX_LINE_TYPE_Y = 16;
63 private static final int MAX_SHAPE_TYPE_X = 8;
64 private static final int MAX_SHAPE_TYPE_Y = 16;
65 private Properties configProps;
66
67 public AllElements(Properties configProps) {
68 this.configProps = configProps;
69 }
6370
6471 /**
6572 * Loading the map in this case means generating it.
6875 */
6976 public void load(MapCollector mapper) {
7077 double baseLat = 51.7;
78 double baseLong = 0.24;
7179
7280 String sBaseLat = System.getenv("BASE_LAT");
7381 String sBaseLong = System.getenv("BASE_LONG");
82 if (sBaseLat == null)
83 sBaseLat = configProps.getProperty("base-lat");
84 if (sBaseLong == null)
85 sBaseLong = configProps.getProperty("base-long");
7486
7587 if (sBaseLat != null)
7688 baseLat = Double.valueOf(sBaseLat);
7789
78 double baseLong = 0.24;
7990 if (sBaseLong != null)
8091 baseLong = Double.valueOf(sBaseLong);
81
92
8293 drawTestMap(mapper, baseLat, baseLong);
8394 }
8495
96107
97108 drawPoints(map, startLat, lng);
98109
99 lng += MAX_POINT_TYPE * ELEMENT_SPACING;
110 lng += (MAX_POINT_TYPE + 1) * ELEMENT_SPACING;
100111 drawLines(map, startLat, lng);
101112
102113 lng += MAX_LINE_TYPE_X * ELEMENT_SPACING;
107118
108119 double lat = slat + 0.004;
109120 double lon = slon + 0.002;
110
111 for (int type = 0; type < MAX_POINT_TYPE; type++) {
112 for (int subtype = 0; subtype < MAX_POINT_SUB_TYPE; subtype++) {
113
121
122 for (int maintype = 0; maintype <= MAX_POINT_TYPE; maintype++) {
123 for (int subtype = 0; subtype <= MAX_POINT_SUB_TYPE; subtype++) {
124 int type = (maintype << 8) + subtype;
114125 MapPoint point = new MapPoint();
115126
116127 double baseLat = lat + subtype * ELEMENT_SPACING;
117 double baseLong = lon + type * ELEMENT_SPACING;
128 double baseLong = lon + maintype * ELEMENT_SPACING;
118129
119130 point.setMinResolution(10);
120 point.setName("0x" + Integer.toHexString(type)
121 + ','
122 + "0x" + Integer.toHexString(subtype));
123
131 point.setName(GType.formatType(type));
124132 point.setLocation(new Coord(baseLat, baseLong));
125 point.setType((type << 8) + subtype);
133 point.setType(type);
126134
127135 mapper.addPoint(point);
136
137 if (configProps.containsKey("verbose"))
138 System.out.println("Generated POI " + GType.formatType(type) + " at " + point.getLocation().toDegreeString());
128139 mapper.addToBounds(point.getLocation()); // XXX shouldn't be needed.
129140 }
130141 }
131142 }
132143
133144 private void drawLines(MapCollector mapper, double slat, double slon) {
134
145
135146 double lat = slat + 0.004;
136147 double lon = slon + 0.002;
137
148 int type = 0;
138149 for (int x = 0; x < MAX_LINE_TYPE_X; x++) {
139150 for (int y = 0; y < MAX_LINE_TYPE_Y; y++) {
140 int type = x*MAX_LINE_TYPE_X + y;
141 if ((type & 0xc0) != 0)
151 type++;
152 if (type >= 0x40)
142153 break;
143154
144155 MapLine line = new MapLine();
145156 line.setMinResolution(10);
146 line.setName("0x" + Integer.toHexString(type));
157 line.setName(GType.formatType(type));
147158
148159 double baseLat = lat + y * ELEMENT_SPACING;
149160 double baseLong = lon + x * ELEMENT_SPACING;
150
151161 List<Coord> coords = new ArrayList<Coord>();
152162
153163 Coord co = new Coord(baseLat, baseLong);
154164 coords.add(co);
155165 mapper.addToBounds(co);
166 if (configProps.containsKey("verbose"))
167 System.out.println("Generated line " + GType.formatType(type) + " at " + co.toDegreeString());
156168
157169 co = new Coord(baseLat + ELEMENT_SIZE, baseLong + ELEMENT_SIZE);
158170 coords.add(co);
159171 mapper.addToBounds(co);
160172
161 co = new Coord(baseLat + ELEMENT_SIZE, baseLong + ELEMENT_SIZE + ELEMENT_SIZE/2);
173 co = new Coord(baseLat + ELEMENT_SIZE, baseLong + ELEMENT_SIZE + ELEMENT_SIZE/2);
162174 coords.add(co);
163175 mapper.addToBounds(co);
164176
175187
176188 double lat = slat + 0.004;
177189 double lon = slon + 0.002;
178
190 int type = 0;
179191 for (int x = 0; x < MAX_SHAPE_TYPE_X; x++) {
180192 for (int y = 0; y < MAX_SHAPE_TYPE_Y; y++) {
181 int type = x*16 + y;
182 if ((type & 0x80) != 0)
193 type++;
194 if (type >= 0x80)
183195 break;
184196
185197 //Polygon pg = div.createPolygon("0x" + Integer.toHexString(type));
186198
187199 MapShape shape = new MapShape();
188200 shape.setMinResolution(10);
189 shape.setName("0x" + Integer.toHexString(type));
201 shape.setName(GType.formatType(type));
190202
191203 double baseLat = lat + y * ELEMENT_SPACING;
192204 double baseLong = lon + x * ELEMENT_SPACING;
193
205
194206 List<Coord> coords = new ArrayList<Coord>();
195207
196208 Coord co = new Coord(baseLat, baseLong);
197209 //pg.addCoord(co);
198210 coords.add(co);
199
211 if (configProps.containsKey("verbose"))
212 System.out.println("Generated polygon " + GType.formatType(type) + " at " + co.toDegreeString());
213
200214 co = new Coord(baseLat + ELEMENT_SIZE, baseLong);
201215 coords.add(co);
202216 mapper.addToBounds(co);
209223 coords.add(co);
210224 mapper.addToBounds(co);
211225
212 co = new Coord(baseLat, baseLong);
213 coords.add(co);
214 mapper.addToBounds(co);
226 coords.add(coords.get(0));
215227
216228 shape.setType(type);
217229 shape.setPoints(coords);
4848 */
4949 public void load(String name) throws FileNotFoundException {
5050 if ("test-map:all-elements".equals(name)) {
51 AllElements all = new AllElements();
51 AllElements all = new AllElements(configProps);
5252 all.load(mapper);
5353 } else if ("test-map:test-points".equals(name)) {
5454 TestPoints test = new TestPoints();
0 /*
1 * Copyright (C) 2014 Gerd Petermann
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.util;
13
14
15 import java.util.LinkedHashSet;
16 import java.util.Set;
17 import uk.me.parabola.imgfmt.app.Coord;
18
19
20 /**
21 * A kd-tree (2D) implementation to solve the nearest neighbor problem.
22 * The tree is not explicitly balanced.
23 *
24 * @author Gerd Petermann
25 *
26 */
27 public class KdTree <T extends Locatable> {
28 private static final boolean ROOT_NODE_USES_LONGITUDE = false;
29
30 private class KdNode {
31 T point;
32 KdNode left;
33 KdNode right;
34
35 KdNode(T p) {
36 point = p;
37 }
38 }
39 // the tree root
40 private KdNode root;
41 // number of saved objects
42 private int size;
43
44 // helpers
45 private T nextPoint ;
46 private double minDist;
47 private double maxDist;
48 private Set<T> set;
49
50 /**
51 * create an empty tree
52 */
53 public KdTree() {
54 root = null;
55 }
56
57 public long size()
58 {
59 return size;
60 }
61
62
63 /**
64 * Start the add action with the root
65 * @param toAdd
66 */
67 public void add(T toAdd) {
68 size++;
69 root = add(toAdd, root, ROOT_NODE_USES_LONGITUDE);
70 }
71
72 /**
73 * Compares the given axis of both points.
74 * @param longitude <code>true</code>: compare longitude; <code>false</code> compare latitude
75 * @param c1 a point
76 * @param c2 another point
77 * @return <code>true</code> the axis value of c1 is smaller than c2;
78 * <code>false</code> the axis value of c1 is equal or larger than c2
79 */
80 private boolean isSmaller(boolean longitude, Coord c1, Coord c2) {
81 if (longitude) {
82 return c1.getLongitude() < c2.getLongitude();
83 } else {
84 return c1.getLatitude() < c2.getLatitude();
85 }
86 }
87
88 /**
89 * Recursive routine to find the right place for inserting
90 * into the tree.
91 * @param toAdd the point
92 * @param tree the subtree root node where to add (maybe <code>null</code>)
93 * @param useLongitude <code>true</code> the tree node uses longitude for comparison;
94 * <code>false</code> the tree node uses latitude for comparison
95 * @return the subtree root node after insertion
96 */
97 private KdNode add( T toAdd, KdNode tree, boolean useLongitude){
98 if( tree == null ) {
99 tree = new KdNode( toAdd );
100 } else {
101 if(isSmaller(useLongitude, toAdd.getLocation(), tree.point.getLocation())) {
102 tree.left = add(toAdd, tree.left, !useLongitude);
103 } else {
104 tree.right = add(toAdd, tree.right, !useLongitude);
105 }
106 }
107 return tree;
108 }
109
110 /**
111 * Searches for the point that has smallest distance to the given point.
112 * @param p the point to search for
113 * @return the point with shortest distance to <var>p</var>
114 */
115 public T findNextPoint(Locatable p) {
116 // reset
117 minDist = Double.MAX_VALUE;
118 maxDist = -1;
119 set = null;
120 nextPoint = null;
121
122 // false => first node is a latitude level
123 return findNextPoint(p, root, ROOT_NODE_USES_LONGITUDE);
124 }
125
126 /**
127 * Searches for the point that has smallest distance to the given point.
128 * @param p the point to search for
129 * @return the point with shortest distance to <var>p</var>
130 */
131 public Set<T> findNextPoint(Locatable p, double maxDist) {
132 // reset
133 minDist = Double.MAX_VALUE;
134 this.maxDist = Math.pow(maxDist * 360 / Coord.U, 2); // convert maxDist in meter to distanceInDegreesSquared
135 nextPoint = null;
136 this.set = new LinkedHashSet<>();
137 // false => first node is a latitude level
138 findNextPoint(p, root, ROOT_NODE_USES_LONGITUDE);
139 return set;
140 }
141
142 private T findNextPoint(Locatable p, KdNode tree, boolean useLongitude) {
143 boolean continueWithLeft = false;
144 if (tree == null)
145 return nextPoint;
146
147 if (tree.left == null && tree.right == null){
148 double dist = tree.point.getLocation().distanceInDegreesSquared(p.getLocation());
149 if (dist <= maxDist && set != null){
150 set.add(tree.point);
151 }
152 if (dist < minDist){
153 nextPoint = tree.point;
154 if (dist < maxDist)
155 minDist = maxDist;
156 else
157 minDist = dist;
158 }
159 return nextPoint;
160 }
161 else {
162 if (isSmaller(useLongitude, p.getLocation(), tree.point.getLocation())){
163 continueWithLeft = false;
164 nextPoint = findNextPoint(p, tree.left, !useLongitude);
165 }
166 else {
167 continueWithLeft = true;
168 nextPoint = findNextPoint(p, tree.right, !useLongitude);
169 }
170 }
171
172 double dist = tree.point.getLocation().distanceInDegreesSquared(p.getLocation());
173 if (dist <= maxDist && set != null)
174 set.add(tree.point);
175 if (dist < minDist){
176 nextPoint = tree.point;
177 minDist = dist;
178 if (dist < maxDist)
179 minDist = maxDist;
180 else
181 minDist = dist;
182 }
183 // do we have to search the other part of the tree?
184 Coord test;
185 if (useLongitude)
186 test = Coord.makeHighPrecCoord(p.getLocation().getHighPrecLat(), tree.point.getLocation().getHighPrecLon());
187 else
188 test = Coord.makeHighPrecCoord(tree.point.getLocation().getHighPrecLat(), p.getLocation().getHighPrecLon());
189 if (test.distanceInDegreesSquared(p.getLocation()) < minDist){
190 if (continueWithLeft)
191 nextPoint = findNextPoint(p, tree.left, !useLongitude);
192 else
193 nextPoint = findNextPoint(p, tree.right, !useLongitude);
194 }
195 return nextPoint;
196 }
197 }
0 /*
1 * Copyright (C) 2015 Gerd Petermann
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.util;
13
14 import uk.me.parabola.imgfmt.app.Coord;
15
16 /**
17 * For objects that have a location which can be represented as a Coord instance.
18 *
19 * @author Gerd Petermann
20 */
21 public interface Locatable {
22
23 /**
24 * get the location of the object
25 */
26 public Coord getLocation();
27 }
0 /*
1 * Copyright (C) 2015 Gerd Petermann
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.util;
13
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.LinkedHashSet;
17 import java.util.Set;
18
19
20 public class MultiHashSet<K,V> extends HashMap<K,Set<V>> {
21
22 /**
23 * the empty set to be returned when there is key without values.
24 */
25 private final Set<V> emptyList = Collections.emptySet();
26
27 /**
28 * Returns the list of values associated with the given key.
29 *
30 * @param key the key to get the values for.
31 * @return a list of values for the given keys or the empty list of no such
32 * value exist.
33 */
34 public Set<V> get(Object key) {
35 Set<V> result = super.get(key);
36 return result == null ? emptyList : result;
37 }
38
39
40 public boolean add(K key, V value ) {
41 Set<V> values = super.get(key);
42 if (values == null ) {
43 values = new LinkedHashSet<V>();
44 super.put( key, values );
45 }
46 return values.add(value);
47 }
48
49 public boolean removeMapping(K key, V value) {
50 Set<V> values = super.get(key);
51 if (values == null )
52 return false;
53
54 boolean existed = values.remove(value);
55
56 if (values.isEmpty())
57 super.remove(key);
58
59 return existed;
60 }
61 }
62
0 /*
1 * Copyright (C) 2014 Gerd Petermann
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 */
012 package uk.me.parabola.util;
113
214 import java.awt.Shape;
9999 // To do this properly we need to know the number of nodes I think, this is the
100100 // best we can do: if there are more than 8 bits left, there must be another command
101101 // left. We could leave a short command at the end.
102 while (br.getBitPosition() < br.getNumberOfBits() && numbers.size() < numberOfNodes) {
102 while (nodeCounter < numberOfNodes/* + 1*/) {
103 try {
103104 runCommand(numbers);
105 } catch (NumberException | ArrayIndexOutOfBoundsException e) {
106 System.out.printf("collected %d, wanted %d\n", numbers.size(), numberOfNodes+1);
107 return numbers;
108 }
104109 }
105110
106111 return numbers;
289294 adjustValues();
290295
291296 Numbers n = new Numbers();
292 n.setRnodNumber(nodeCounter);
293
294 n.setLeftNumberStyle(leftStyle);
295 n.setLeftStart(leftStart);
296 n.setLeftEnd(leftEnd);
297
298 n.setRightNumberStyle(rightStyle);
299 n.setRightStart(rightStart);
300 n.setRightEnd(rightEnd);
297 n.setIndex(nodeCounter);
298 n.setNumbers(Numbers.LEFT, leftStyle, leftStart, leftEnd);
299 n.setNumbers(Numbers.RIGHT, rightStyle, rightStart, rightEnd);
301300
302301 numbers.add(n);
303302 nodeCounter++;
330329 adjustValues();
331330
332331 Numbers n = new Numbers();
333 if (leftStyle == NONE) {
334 n.setRnodNumber(nodeCounter);
335 n.setRightNumberStyle(rightStyle);
336 n.setRightStart(rightStart);
337 n.setRightEnd(rightEnd);
338
339 n.setLeftNumberStyle(NONE);
340 n.setLeftStart(-1);
341 n.setLeftEnd(-1);
342 }
343 else {
344 n.setRnodNumber(nodeCounter);
345 n.setLeftNumberStyle(leftStyle);
346 n.setLeftStart(leftStart);
347 n.setLeftEnd(leftEnd);
348
349 n.setRightNumberStyle(NONE);
350 n.setRightStart(-1);
351 n.setRightEnd(-1);
352 }
332 n.setIndex(nodeCounter);
333 if (leftStyle == NONE)
334 n.setNumbers(Numbers.RIGHT, rightStyle, rightStart, rightEnd);
335 else
336 n.setNumbers(Numbers.LEFT, leftStyle, leftStart, leftEnd);
353337 numbers.add(n);
354338 nodeCounter++;
355339 }
7171 List<Numbers> numbers = new ArrayList<Numbers>();
7272 for (String s : strings) {
7373 Numbers n = new Numbers(s);
74 n.setRnodNumber(n.getNodeNumber());
74 n.setIndex(n.getNodeNumber());
7575 numbers.add(n);
7676 }
7777
8383 byte[] bytes = new byte[bitWriter.getLength()];
8484 System.arraycopy(bitWriter.getBytes(), 0, bytes, 0, bytes.length);
8585 NumberReader nr = new NumberReader(new BitReader(bytes));
86
87 nr.setNumberOfNodes(numbers.size());
86 nr.setNumberOfNodes(numbers.get(numbers.size()-1).getIndex() + 1);
8887 List<Numbers> list = nr.readNumbers(np.getSwapped());
8988
9089 // Have to fix up the node numbers
9190 for (Numbers n : list) {
92 n.setNodeNumber(n.getRnodNumber());
91 n.setNodeNumber(n.getIndex());
9392 }
9493
9594 // Test that they are the same.
264264
265265 BitReader br = new BitReader(bytes);
266266 NumberReader nr = new NumberReader(br);
267 nr.setNumberOfNodes(numbers.size());
267 nr.setNumberOfNodes(numbers.get(numbers.size()-1).getIndex() + 1);
268268 List<Numbers> list = nr.readNumbers(swapped);
269269 for (Numbers n : list)
270 n.setNodeNumber(n.getRnodNumber());
270 n.setNodeNumber(n.getIndex());
271271
272272 return list;
273273 }
276276 List<Numbers> numbers = new ArrayList<Numbers>();
277277 for (String s : specs) {
278278 Numbers n = new Numbers(s);
279 n.setRnodNumber(n.getNodeNumber());
279 n.setIndex(n.getNodeNumber());
280280 numbers.add(n);
281281 }
282282 return numbers;
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
13 package uk.me.parabola.imgfmt.app.net;
14
15 import org.junit.Test;
16
17 import static org.junit.Assert.*;
18
19 /**
20 * Tests to verify that the plausibility checks are working
21 */
22 public class NumbersTest {
23
24 @Test
25 public void testOKOddEvenOverlap() {
26 String spec = "0,O,1,7,E,2,12";
27 Numbers numbers = new Numbers(spec);
28 assertTrue(numbers.isPlausible());
29 }
30 @Test
31 public void testBothBothNoOverlap() {
32 String spec = "0,B,1,7,B,8,15";
33 Numbers numbers = new Numbers(spec);
34 assertTrue(numbers.isPlausible());
35 }
36 @Test
37 public void testBothEvenNoOverlap() {
38 String spec = "0,B,1,7,E,8,16";
39 Numbers numbers = new Numbers(spec);
40 assertTrue(numbers.isPlausible());
41 }
42 @Test
43 public void testBothEvenNoOverlapNotEven() {
44 String spec = "0,B,1,7,E,8,15";
45 Numbers numbers = new Numbers(spec);
46 assertFalse(numbers.isPlausible());
47 }
48 @Test
49 public void testOverlapAtStartEnd() {
50 String spec = "0,B,1,7,B,7,16";
51 Numbers numbers = new Numbers(spec);
52 assertFalse(numbers.isPlausible());
53 }
54 @Test
55 public void testBothEvenOverlap() {
56 String spec = "0,B,1,7,E,6,16";
57 Numbers numbers = new Numbers(spec);
58 assertFalse(numbers.isPlausible());
59 }
60 @Test
61 public void testRangeLargeNumbersOK() {
62 String spec = "0,B,10012,10024,N,0,0";
63 Numbers numbers = new Numbers(spec);
64 assertTrue(numbers.isPlausible());
65 }
66 @Test
67 public void testRangeLargeNumbersNotOK() {
68 String spec = "0,B,10012,1000240,N,0,0";
69 Numbers numbers = new Numbers(spec);
70 assertFalse(numbers.isPlausible());
71 }
72 @Test
73 public void testNotOK1() {
74 String spec = "0,B,10,23,O,15,15";
75 Numbers numbers = new Numbers(spec);
76 assertFalse(numbers.isPlausible());
77 }
78 @Test
79 public void testNotOK2() {
80 String spec = "0,O,15,15,B,10,23";
81 Numbers numbers = new Numbers(spec);
82 assertFalse(numbers.isPlausible());
83 }
84 @Test
85 public void testSingleNumBothSides() {
86 String spec = "0,O,15,15,O,15,15";
87 Numbers numbers = new Numbers(spec);
88 assertTrue(numbers.isPlausible());
89 }
90 @Test
91 public void testSingleNumOneSideEqualStartOrEndOtherSide1() {
92 String spec = "0,O,13,15,O,15,15";
93 Numbers numbers = new Numbers(spec);
94 assertFalse(numbers.isPlausible());
95 }
96 @Test
97 public void testSingleNumOneSideEqualStartOrEndOtherSide2() {
98 String spec = "0,O,15,15,O,13,15";
99 Numbers numbers = new Numbers(spec);
100 assertFalse(numbers.isPlausible());
101 }
102 @Test
103 public void testSingleNumOneSideEqualStartOrEndOtherSide3() {
104 String spec = "0,O,15,13,O,15,15";
105 Numbers numbers = new Numbers(spec);
106 assertFalse(numbers.isPlausible());
107 }
108 @Test
109 public void testSingleNumOneSideEqualStartOrEndOtherSide4() {
110 String spec = "0,O,15,15,O,15,13";
111 Numbers numbers = new Numbers(spec);
112 assertFalse(numbers.isPlausible());
113 }
114 @Test
115 public void testSingleDifferentNumEachSide() {
116 String spec = "0,O,15,15,O,13,13";
117 Numbers numbers = new Numbers(spec);
118 assertTrue(numbers.isPlausible());
119 }
120
121 @Test
122 public void testCountMatchesValid() {
123 String spec = "0,O,1,7,E,2,12";
124 Numbers numbers = new Numbers(spec);
125 assertEquals(1,numbers.countMatches(1));
126 assertEquals(0,numbers.countMatches(13));
127 }
128 @Test
129 public void testCountMatchesGap() {
130 String spec = "0,B,1,7,B,9,12";
131 Numbers numbers = new Numbers(spec);
132 assertEquals(1,numbers.countMatches(1));
133 assertEquals(1,numbers.countMatches(7));
134 assertEquals(0,numbers.countMatches(8));
135 assertEquals(0,numbers.countMatches(13));
136 }
137
138 }
+0
-59
test/uk/me/parabola/mkgmap/general/MapPointKdTreeTest.java less more
0 /*
1 * Copyright (C) 2012.
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
13 package uk.me.parabola.mkgmap.general;
14
15 import static org.junit.Assert.assertFalse;
16 import org.junit.Test;
17
18 import uk.me.parabola.imgfmt.app.Coord;
19
20 public class MapPointKdTreeTest {
21
22 @Test
23 public void TestFindNextPoint(){
24 MapPointKdTree t = new MapPointKdTree( );
25
26 int [][]test = {{70,20}, {50,40}, {90,60}, {20,30}, {40,70}, {80,10}, {-10,20}, {-30,-40} } ;
27 Coord []testCoords = new Coord[test.length];
28
29 for( int i = 0; i < test.length; i++ )
30 {
31 MapPoint p = new MapPoint();
32 testCoords[i] = new Coord(test[i][0],test[i][1]);
33 p.setLocation(testCoords[i]);
34 t.add(p);
35 }
36 // compare naive search result with kd--tree result
37 MapPoint toFind = new MapPoint();
38 for (int x = -100; x < 100; x++){
39 for (int y = -100; y < 100; y++){
40 Coord co = new Coord(x,y);
41 double minDist = Double.MAX_VALUE;
42
43 for (int i = 0; i<testCoords.length; i++){
44 Double dist = testCoords[i].distanceInDegreesSquared(co);
45 if (dist < minDist){
46 minDist = dist;
47 }
48 }
49 toFind.setLocation(co);
50 MapPoint next = t.findNextPoint(toFind);
51 double dist = next.getLocation().distanceInDegreesSquared(co);
52 double delta = Math.abs(dist - minDist);
53 // if this test fails because
54 assertFalse("delta should be 0.0: " + delta, delta != 0.0);
55 }
56 }
57 }
58 }
0 /*
1 * Copyright (C) 2014 Gerd Petermann
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.util;
13
14 import static org.junit.Assert.assertFalse;
15 import org.junit.Test;
16
17 import uk.me.parabola.imgfmt.app.Coord;
18 import uk.me.parabola.mkgmap.general.MapPoint;
19 import uk.me.parabola.util.KdTree;
20
21 public class KdTreeTest {
22
23 @Test
24 public void TestFindNextPoint(){
25 KdTree<MapPoint> t = new KdTree<>( );
26
27 int [][]test = {{70,20}, {50,40}, {90,60}, {20,30}, {40,70}, {80,10}, {-10,20}, {-30,-40} } ;
28 Coord []testCoords = new Coord[test.length];
29
30 for( int i = 0; i < test.length; i++ )
31 {
32 MapPoint p = new MapPoint();
33 testCoords[i] = new Coord(test[i][0],test[i][1]);
34 p.setLocation(testCoords[i]);
35 t.add(p);
36 }
37 // compare naive search result with kd--tree result
38 MapPoint toFind = new MapPoint();
39 for (int x = -100; x < 100; x++){
40 for (int y = -100; y < 100; y++){
41 Coord co = new Coord(x,y);
42 double minDist = Double.MAX_VALUE;
43
44 for (int i = 0; i<testCoords.length; i++){
45 Double dist = testCoords[i].distanceInDegreesSquared(co);
46 if (dist < minDist){
47 minDist = dist;
48 }
49 }
50 toFind.setLocation(co);
51 MapPoint next = (MapPoint) t.findNextPoint(toFind);
52 double dist = next.getLocation().distanceInDegreesSquared(co);
53 double delta = Math.abs(dist - minDist);
54 // if this test fails because
55 assertFalse("delta should be 0.0: " + delta, delta != 0.0);
56 }
57 }
58 }
59 }