New upstream version 0.0.0+svn4555
Bas Couwenberg
3 years ago
0 | ||
1 | settings: | |
2 | language: [java, python] | |
3 | ||
4 | build_envs: | |
5 | java: [jdk8, openjdk8, openjdk11] | |
6 | ||
7 | build: | |
8 | - shell: | |
9 | - java -version | |
10 | - ant -version | |
11 | # | |
12 | # Set the version and timestamp properties | |
13 | - | | |
14 | echo "svn.version: {{rev.revision}}" > resources/{{rev.project}}-version.properties | |
15 | echo "build.timestamp: $(date -Iseconds)" >> resources/{{rev.project}}-version.properties | |
16 | ||
17 | - when: rev.is_trunk | |
18 | set_var: | |
19 | dist_name: mkgmap-r{{rev.revision}} | |
20 | - when: not rev.is_trunk | |
21 | set_var: | |
22 | dist_name: mkgmap-{{rev.branch}}-r{{rev.revision}} | |
23 | ||
24 | # Create the source archives | |
25 | - archive: | |
26 | is_src: True | |
27 | type: tar.gz | |
28 | rename_dir: "{{dist_name}}" | |
29 | name: "{{dist_name}}-src.tar.gz" | |
30 | ||
31 | - archive: | |
32 | is_src: True | |
33 | type: zip | |
34 | rename_dir: "{{dist_name}}" | |
35 | name: "{{dist_name}}-src.zip" | |
36 | ||
37 | - shell: ant -Dhave.version=1 dist | |
38 | ||
39 | - shell: ant -Dhave.version=1 test | |
40 | when: rev.is_trunk | |
41 | ||
42 | - when: builder_java_version == 'jdk8' | |
43 | block: | |
44 | - shell: | |
45 | - pip install mwconv mkgmap-pygments pygments-xslfo-formatter | |
46 | - scripts/download/mkdoc {{rev.project}} | |
47 | ||
48 | - archive: | |
49 | dir: dist/mkgmap.jar | |
50 | name: "{{dist_name}}.jar" | |
51 | ||
52 | - archive: | |
53 | dir: dist | |
54 | type: [tar.gz, zip] | |
55 | rename_dir: "{{dist_name}}" | |
56 | ||
57 | - deploy: | |
58 | type: mkgmap_deploy | |
59 | build_ok: "{{builder|success}}" |
173 | 173 | <replaceregex pattern="^exported$" replace="" /> |
174 | 174 | </tokenfilter></outputfilterchain></redirector> |
175 | 175 | </exec> |
176 | <condition property="svn.version.build" value="${svn.version.tmp}" else="unknown"> | |
176 | <condition property="svn.version.build" value="${svn.version.tmp}" else="none"> | |
177 | 177 | <and> |
178 | 178 | <isset property="svn.version.tmp" /> |
179 | 179 | <equals arg1="${svnversion.result}" arg2="0" /> |
218 | 218 | |
219 | 219 | <target name="version-file" unless="have.version"> |
220 | 220 | <property name="project.version" value="${build.timestamp}" /> |
221 | <property name="svn.version.build" value="unknown"/> | |
221 | <property name="svn.version.build" value="none"/> | |
222 | 222 | |
223 | 223 | <propertyfile file="${build.classes}/mkgmap-version.properties"> |
224 | 224 | <entry key="svn.version" value="${svn.version.build}" /> |
233 | 233 | <javac srcdir="${src}" destdir="${build.classes}" encoding="utf-8" debug="true" includeantruntime="false"> |
234 | 234 | <include name="**/*.java" /> |
235 | 235 | <classpath refid="main"/> |
236 | <compilerarg value="-Xlint:all"/> | |
237 | <compilerarg value="-Xlint:-serial"/> | |
238 | <compilerarg value="-Xlint:-path"/> | |
236 | 239 | <exclude name="**/optional/*.java"/> |
237 | 240 | </javac> |
238 | 241 | </target> |
66 | 66 | |
67 | 67 | ;--gmapi |
68 | 68 | : Create a directory in the "gmapi" format required by Mac applications. |
69 | It can also be used for Windows programs; copy the complete directory tree into | |
70 | {user}\AppData\Roaming\Garmin\Maps or \ProgramData\Garmin\Maps | |
71 | and the map will be available in BaseCamp. | |
69 | It can also be used for Windows programs; copy the complete directory tree | |
70 | into {user}\AppData\Roaming\Garmin\Maps or \ProgramData\Garmin\Maps | |
71 | and the map will be available to Garmin PC programs. | |
72 | 72 | The directory name is --family-name with extension .gmap. |
73 | 73 | |
74 | 74 | ;-c filename |
111 | 111 | Placing the mkgmap --description option after -c template.args ensures that the |
112 | 112 | value is applied to gmapsupp.img. |
113 | 113 | <p> |
114 | Different GPS devices, BaseCamp and QLandkarte handle descriptions | |
114 | Different GPS devices and PC programs handle descriptions | |
115 | 115 | inconsistently. Some display the description when selecting maps or tiles, |
116 | 116 | others use the family name. |
117 | 117 | |
199 | 199 | You can search for road name Conc to find road names like 'Rue de la Concorde'. |
200 | 200 | However, a search for 'Rue' will not list 'Rue de la Concorde' or 'Rue du Moulin'. |
201 | 201 | It may list 'Rueben Brookins Road' if that is in the map. |
202 | Only MapSource shows a corresponding hint. | |
203 | 202 | |
204 | 203 | :: Another effect is that the index is smaller. |
205 | : See comments in the sample roadNameConfig.txt for further details. | |
204 | : See comments in the example roadNameConfig.txt for further details. | |
206 | 205 | |
207 | 206 | ;--mdr7-excl=name[,name...] |
208 | 207 | : Specify words which should be omitted from the road index. |
327 | 326 | ;--overview-levels=level:resolution[,level:resolution...] |
328 | 327 | : Like levels, specifies additional levels that are to be written to the |
329 | 328 | overview map. Counting of the levels should continue. Up to 8 additional |
330 | levels may be specified, but the lowest usable resolution with MapSource | |
331 | seems to be 11. The hard coded default is empty. | |
329 | levels may be specified. | |
330 | The hard coded default is empty. | |
332 | 331 | : See also option --overview-dem-dist. |
333 | 332 | |
334 | 333 | ;--remove-ovm-work-files |
361 | 360 | you may want to use a slightly different set of rules for a map of |
362 | 361 | a whole continent. The tags given will be prefixed with "mkgmap:option:". |
363 | 362 | If no value is provided the default "true" is used. |
364 | This option allows to use rules like | |
365 | mkgmap:option:light=true & landuse=farmland {remove landuse} | |
366 | Example: -- style-option=light;routing=car | |
367 | will add the tags mkgmap:option:light=true and mkgmap:option:routing=car | |
363 | : Example: --style-option=light;routing=car | |
364 | : will add the tags mkgmap:option:light=true and mkgmap:option:routing=car | |
368 | 365 | to each element before style processing happens. |
366 | This can then be used in rules like: | |
367 | : landuse=farmland & mkgmap:option:light=true {delete landuse} | |
369 | 368 | |
370 | 369 | ;--list-styles |
371 | 370 | : List the available styles. If this option is preceded by a style-file |
411 | 410 | : The version of the product. Default value is 100 which means version 1.00. |
412 | 411 | |
413 | 412 | ;--series-name=name |
414 | : This name will be displayed in BaseCamp in the map selection | |
413 | : This name will be displayed by Garmin PC programs in the map selection | |
415 | 414 | drop-down. The default is "OSM map". |
416 | 415 | |
417 | 416 | ;--area-name=name |
465 | 464 | |
466 | 465 | ;--polygon-size-limits=resolution:value[,resolution:value...] |
467 | 466 | : Allows you to specify different min-size-polygon values for each resolution. |
468 | Sample: | |
467 | Example: | |
469 | 468 | :: --polygon-size-limits="24:12, 18:10, 16:8, 14:4, 12:2, 11:0" |
470 | 469 | : If a resolution is not given, mkgmap uses the value for the next higher |
471 | one. For the given sample, resolutions 19 to 24 will use value 12, | |
470 | one. For the given example, resolutions 19 to 24 will use value 12, | |
472 | 471 | resolution 17 and 18 will use 10, and so on. |
473 | 472 | Value 0 means to not apply the size filter. |
474 | 473 | Note that in resolution 24 the filter is not used. |
678 | 677 | If no value or * is specified for value then presence of the key alone will |
679 | 678 | cause the dead end check to be skipped. The default is --dead-ends=fixme,FIXME. |
680 | 679 | |
681 | ;--add-pois-to-lines | |
682 | : Generate POIs for lines. For each line (must not be closed) POIs are | |
680 | ;--add-pois-to-lines[=all|start|end|mid|other] | |
681 | : Generate POIs for lines. The option expects a comma separated list that | |
682 | specifies the positions for which are POI should be generated. The default is | |
683 | to create all possible POI. For each line (must not be closed) POIs are | |
683 | 684 | created at several points of the line. Each POI is tagged with the |
684 | 685 | same tags like the line and additional tags added by mkgmap: |
685 | 686 | mkgmap:line2poi=true and tag mkgmap:line2poitype having |
920 | 921 | processed POI must be within for it to be considered to be nearby and |
921 | 922 | hence trigger the action part of the rule. |
922 | 923 | |
923 | : The third part of the rule is the action part and provides three options: | |
924 | : The third part of the rule is the action part and provides two options: | |
924 | 925 | |
925 | 926 | :: delete-poi - the POIS are considered to be duplicates and the |
926 | 927 | duplicate is deleted. This is the default. |
283 | 283 | |
284 | 284 | |is_in(*tag*,*value*,*method*) | x | x | | |
285 | 285 | +true+ if the element is in polygon(s) having the specified *tag*=*value* according to the *method*, +false+ otherwise. |
286 | *value* can be \'\*' which matches any polgon having *tag*. | |
286 | 287 | The methods available depend on the Style section: |
287 | 288 | |
288 | 289 | polygons: |
305 | 306 | The method to match an outside line (*none*) allows part to be on the edge, |
306 | 307 | likewise, the method to match an inside line (*all*) allows part to be on the edge. |
307 | 308 | Compared to *all*, the method *all_in_or_on* additionally matches lines which are only on the edge of the polygon. |
309 | ||
310 | |is_drive_on_left() | | x | | | |
311 | +true+ the element is in a drive_on_left country. +false+ the element is not a drive_on_left country or the information given | |
312 | in the tags mkgmap:admin_level2 and mkgmap:country could not be used to retrieve the information. | |
308 | 313 | |
309 | 314 | |==== |
310 | 315 |
3 | 3 | It contains everything you need to write a TYP file description that can be compiled, showing exactly how |
4 | 4 | you can get the different colour effects that are available. |
5 | 5 | |
6 | Although you can edit these files by hand, it is very much more convenient to use one of the graphical editors that are available. | |
6 | The file can be in written in almost any character-set that allows basic ascii characters and can have Strings for any language that can be encoded in that character-set. | |
7 | The mkgmap .txt TYP processor looks at the start of the file for a unicode BOM or a header line of the form: | |
8 | ; -*- coding: charset -*- | |
9 | ||
10 | to determine the encoding, and then will convert those Strings that can be represented in the target .IMG code-page and ignore the ones that can't. | |
11 | ||
12 | It is safest to edit multi-lingual TYP files (eg. resources/mapnik.txt) with a good text editor; | |
13 | if nessessary, using a graphical editor to manipulate the icons, then cutting the result back into the TYP .txt file. | |
14 | ||
15 | For your own TYP file it might be convenient to use one of the graphical editors that are available. | |
16 | However, beware that some of these editors don't preserve the full contents of the file | |
17 | and might change the character representations of the Strings to those in a different character | |
18 | set/encoding. | |
19 | Also they make assumptions about the chars used to represent colours in icons and might change them. | |
7 | 20 | |
8 | 21 | These produce file formats that differ from each other and have variations to the specification that is presented here. These variations will be supported as they are discovered as long as they do not conflict with each other, but are not listed here for clarity. In particular the files produced by [http://www.pinns.co.uk/osm/ostyp.html TYPWiz] and [http://opheliat.free.fr/michel40/ TYPViewer] are supported. |
9 | 22 | |
34 | 47 | :The level is a number starting at 1. |
35 | 48 | |
36 | 49 | Example: |
37 | <pre> | |
38 | [_drawOrder] | |
39 | Type=0x032,1 | |
40 | Type=0x10101,2 | |
41 | Type=0x002,3 | |
42 | Type=0x003,3 | |
43 | Type=0x007,3 | |
44 | Type=0x009,3 | |
45 | Type=0x00c,3 | |
46 | Type=0x00e,3 | |
47 | Type=0x010,3 | |
48 | Type=0x012,3 | |
49 | Type=0x015,3 | |
50 | Type=0x01b,3 | |
51 | ... | |
52 | [end] | |
53 | </pre> | |
50 | [_drawOrder] | |
51 | Type=0x032,1 | |
52 | Type=0x10101,2 | |
53 | Type=0x002,3 | |
54 | Type=0x003,3 | |
55 | Type=0x007,3 | |
56 | Type=0x009,3 | |
57 | Type=0x00c,3 | |
58 | Type=0x00e,3 | |
59 | Type=0x010,3 | |
60 | Type=0x012,3 | |
61 | Type=0x015,3 | |
62 | Type=0x01b,3 | |
63 | ... | |
64 | [end] | |
65 | ||
54 | 66 | If a polygon type is not listed in this section, then it will not be displayed at all. If it is then it will be styled according to a definition in a [_polygon] section later in this file, or in the default Garmin style if these is no such definition. |
55 | 67 | |
56 | 68 | == Element sections == |
89 | 101 | :The object type number - the kind of element that it is. If the number is greater than 0xff then it will be treated as a type and subtype combination. Eg 0x2305 is type 0x23 with subtype 0x5. |
90 | 102 | |
91 | 103 | ;String=#,xxx |
92 | :Defines a label that will be attached to the element. The first part is a language number, the second the actual string. You can use String1, String2 for compatibility, but just String will do for the mkgmap compiler. | |
104 | ;String=xxx | |
105 | :Defines a label that will be attached to the element. | |
106 | The (optional) first part is a language number, the next is the actual string. | |
107 | You can use String1, String2 for compatibility, but just String will do for the mkgmap compiler. | |
108 | String=Parking Lot | |
93 | 109 | String=0x04,Parking |
94 | 110 | String=0x03,Parkeerplaats |
111 | :If the language number isn't specified it defaults to 0x00 which is the generic language. | |
112 | This string is used if there isn't a definition for the currently selected language; | |
113 | If there isn't a default the device shows the first string in the list. | |
114 | The language numbers are defined [https://wiki.openstreetmap.org/wiki/Mkgmap/help/typ_compile#Appendix here]. | |
95 | 115 | |
96 | 116 | ;FontStyle=xxx |
97 | 117 | : xxx can be one of NoLabel, SmallFont, NormalFont, LargeFont. |
285 | 305 | <p> |
286 | 306 | An alternate notation is supported where the transparency value is appended to the line in the form "alpha=13", this is a transparency value that goes from 0 to 15, with 15 being completely transparent. As such it works the opposite way to the alpha value of the normal RGBa values in the previous example. The required conversions are made by mkgmap which ever one you use. |
287 | 307 | |
288 | * Image with up to 16 million different colours | |
308 | * Image with up to 16 million different colours. | |
289 | 309 | By setting the number of colours in the XPM to zero you can specify a |
290 | 310 | different kind of image, where the colour for each pixel is specified |
291 | 311 | separately. |
42 | 42 | @SuppressWarnings({"UnusedDeclaration"}) |
43 | 43 | public class MKGMapTask extends Task { |
44 | 44 | |
45 | private final ArrayList<Path> paths = new ArrayList<Path>(); | |
45 | private final ArrayList<Path> paths = new ArrayList<>(); | |
46 | 46 | private String configFile; |
47 | 47 | |
48 | 48 | public void addPath(Path path) { |
54 | 54 | } |
55 | 55 | |
56 | 56 | public void execute() { |
57 | List<String> args = new ArrayList<String>(); | |
57 | List<String> args = new ArrayList<>(); | |
58 | 58 | |
59 | 59 | try { |
60 | 60 | CommandArgsReader argsReader = new CommandArgsReader(new Main()); |
63 | 63 | RuleBasedCollator col = (RuleBasedCollator) Collator.getInstance(); |
64 | 64 | |
65 | 65 | charset = Charset.forName(charsetName); |
66 | if (charsetName.equalsIgnoreCase("utf-8")) | |
66 | if ("utf-8".equalsIgnoreCase(charsetName)) | |
67 | 67 | isUnicode = true; |
68 | 68 | decoder = charset.newDecoder(); |
69 | 69 |
1008 | 1008 | <variant>SJ</variant> |
1009 | 1009 | <variant>SJM</variant> |
1010 | 1010 | </country> |
1011 | <country name="Swaziland" abr="SWZ" driveOnLeft="true"> | |
1011 | <country name="Kingdom of Eswatini" abr="SWZ" driveOnLeft="true"> | |
1012 | 1012 | <variant>SZ</variant> |
1013 | 1013 | <variant>SWZ</variant> |
1014 | 1014 | <variant>Swatini</variant> |
1015 | <variant>Eswatini</variant> | |
1016 | <variant>Swaziland</variant> | |
1015 | 1017 | </country> |
1016 | 1018 | <country name="Sverige" abr="SWE" streetBeforeHousenumber="true" postalcodeBeforeCity="true"> |
1017 | 1019 | <variant>Sweden</variant> |
67 | 67 | Create a directory in the "gmapi" format required by Mac applications. It |
68 | 68 | can also be used for Windows programs; copy the complete directory tree |
69 | 69 | into {user}\AppData\Roaming\Garmin\Maps or \ProgramData\Garmin\Maps and the |
70 | map will be available in BaseCamp. The directory name is --family-name with | |
71 | extension .gmap. | |
70 | map will be available to Garmin PC programs. The directory name is | |
71 | --family-name with extension .gmap. | |
72 | 72 | |
73 | 73 | -c filename |
74 | 74 | --read-config=filename |
110 | 110 | from the mkgmap command line. Placing the mkgmap --description option after |
111 | 111 | -c template.args ensures that the value is applied to gmapsupp.img. |
112 | 112 | |
113 | Different GPS devices, BaseCamp and QLandkarte handle descriptions | |
114 | inconsistently. Some display the description when selecting maps or tiles, | |
115 | others use the family name. | |
113 | Different GPS devices and PC programs handle descriptions inconsistently. | |
114 | Some display the description when selecting maps or tiles, others use the | |
115 | family name. | |
116 | 116 | |
117 | 117 | --country-name=name |
118 | 118 | Set the map's country name. The default is "COUNTRY". |
196 | 196 | name. You can search for road name Conc to find road names like 'Rue de |
197 | 197 | la Concorde'. However, a search for 'Rue' will not list 'Rue de la |
198 | 198 | Concorde' or 'Rue du Moulin'. It may list 'Rueben Brookins Road' if |
199 | that is in the map. Only MapSource shows a corresponding hint. | |
199 | that is in the map. | |
200 | 200 | |
201 | 201 | Another effect is that the index is smaller. |
202 | See comments in the sample roadNameConfig.txt for further details. | |
202 | See comments in the example roadNameConfig.txt for further details. | |
203 | 203 | |
204 | 204 | --mdr7-excl=name[,name...] |
205 | 205 | Specify words which should be omitted from the road index. It was added |
321 | 321 | --overview-levels=level:resolution[,level:resolution...] |
322 | 322 | Like levels, specifies additional levels that are to be written to the |
323 | 323 | overview map. Counting of the levels should continue. Up to 8 additional |
324 | levels may be specified, but the lowest usable resolution with MapSource | |
325 | seems to be 11. The hard coded default is empty. | |
324 | levels may be specified. The hard coded default is empty. | |
326 | 325 | See also option --overview-dem-dist. |
327 | 326 | |
328 | 327 | --remove-ovm-work-files |
354 | 353 | The intended use is to make a single style more flexible, e.g. you may want |
355 | 354 | to use a slightly different set of rules for a map of a whole continent. |
356 | 355 | The tags given will be prefixed with "mkgmap:option:". If no value is |
357 | provided the default "true" is used. This option allows to use rules like | |
358 | mkgmap:option:light=true & landuse=farmland {remove landuse} Example: -- | |
359 | style-option=light;routing=car will add the tags mkgmap:option:light=true | |
360 | and mkgmap:option:routing=car to each element before style processing | |
361 | happens. | |
356 | provided the default "true" is used. | |
357 | Example: --style-option=light;routing=car | |
358 | will add the tags mkgmap:option:light=true and mkgmap:option:routing=car to | |
359 | each element before style processing happens. This can then be used in | |
360 | rules like: | |
361 | landuse=farmland & mkgmap:option:light=true {delete landuse} | |
362 | 362 | |
363 | 363 | --list-styles |
364 | 364 | List the available styles. If this option is preceded by a style-file |
401 | 401 | The version of the product. Default value is 100 which means version 1.00. |
402 | 402 | |
403 | 403 | --series-name=name |
404 | This name will be displayed in BaseCamp in the map selection drop-down. The | |
405 | default is "OSM map". | |
404 | This name will be displayed by Garmin PC programs in the map selection | |
405 | drop-down. The default is "OSM map". | |
406 | 406 | |
407 | 407 | --area-name=name |
408 | 408 | Area name is displayed on Garmin units (or at least on eTrex) as the second |
453 | 453 | |
454 | 454 | --polygon-size-limits=resolution:value[,resolution:value...] |
455 | 455 | Allows you to specify different min-size-polygon values for each |
456 | resolution. Sample: | |
456 | resolution. Example: | |
457 | 457 | --polygon-size-limits="24:12, 18:10, 16:8, 14:4, 12:2, 11:0" |
458 | 458 | If a resolution is not given, mkgmap uses the value for the next higher |
459 | one. For the given sample, resolutions 19 to 24 will use value 12, | |
459 | one. For the given example, resolutions 19 to 24 will use value 12, | |
460 | 460 | resolution 17 and 18 will use 10, and so on. Value 0 means to not apply the |
461 | 461 | size filter. Note that in resolution 24 the filter is not used. The |
462 | 462 | following options are equivalent: |
661 | 661 | cause the dead end check to be skipped. The default is |
662 | 662 | --dead-ends=fixme,FIXME. |
663 | 663 | |
664 | --add-pois-to-lines | |
665 | Generate POIs for lines. For each line (must not be closed) POIs are | |
664 | --add-pois-to-lines[=all|start|end|mid|other] | |
665 | Generate POIs for lines. The option expects a comma separated list that | |
666 | specifies the positions for which are POI should be generated. The default | |
667 | is to create all possible POI. For each line (must not be closed) POIs are | |
666 | 668 | created at several points of the line. Each POI is tagged with the same |
667 | 669 | tags like the line and additional tags added by mkgmap: |
668 | 670 | mkgmap:line2poi=true and tag mkgmap:line2poitype having the following |
898 | 900 | processed POI must be within for it to be considered to be nearby and hence |
899 | 901 | trigger the action part of the rule. |
900 | 902 | |
901 | The third part of the rule is the action part and provides three options: | |
903 | The third part of the rule is the action part and provides two options: | |
902 | 904 | |
903 | 905 | delete-poi - the POIS are considered to be duplicates and the duplicate |
904 | 906 | is deleted. This is the default. |
0 | svn.version: 4506 | |
1 | build.timestamp: 2020-05-28T10:18:05+0100 | |
0 | svn.version: 4555 | |
1 | build.timestamp: 2020-06-22T17:00:46+0100 |
36 | 36 | #make-opposite-cycleways |
37 | 37 | #order-by-decreasing-area |
38 | 38 | #name-tag-list=name:en,int_name,name,place_name,loc_name |
39 | #nearby-poi-rules=0x4a00:30:delete-poi,0x6605:30:delete-poi | |
40 | # above deletes multiple POI that the default style might generate from picnic_table and bench |
118 | 118 | aeroway=helipad [0x5904 resolution 23] |
119 | 119 | aeroway=terminal [0x2f04 resolution 24] |
120 | 120 | |
121 | amenity=atm [0x2f06 resolution 24] | |
121 | amenity=atm [0x2f06 resolution 24 continue] | |
122 | 122 | amenity=arts_centre [0x2c04 resolution 24] |
123 | 123 | amenity=bank [0x2f06 resolution 24] |
124 | amenity=bar [0x2d02 resolution 24] | |
124 | amenity=bar [0x2d02 resolution 24 continue] | |
125 | amenity=bench [0x6605 resolution 24 continue] | |
125 | 126 | amenity=biergarten [0x2d02 resolution 24] |
126 | 127 | amenity=border_control | barrier=border_control [0x3006 resolution 20] |
127 | 128 | amenity=bus_station [0x2f08 resolution 23] |
128 | amenity=cafe [0x2a0e resolution 24] | |
129 | amenity=cafe {delete cuisine} [0x2a0e resolution 24 continue with_actions] | |
129 | 130 | amenity=car_club [0x2f0d resolution 24] |
130 | 131 | amenity=car_rental [0x2f02 resolution 24] |
131 | 132 | amenity=car_sharing [0x2f02 resolution 24] |
156 | 157 | amenity=library [0x2c03 resolution 24] |
157 | 158 | amenity=nightclub [0x2d02 resolution 24] |
158 | 159 | amenity=nursing_home [0x2f14 resolution 24] |
159 | amenity=parking [0x2f0b resolution 24 default_name 'Parking'] | |
160 | (amenity=parking | amenity=parking_entrance) & access!=private & access!=no {add name='${access} parking'| 'Parking'} [0x2f0b resolution 24] | |
160 | 161 | amenity=pharmacy [0x2e05 resolution 24] |
161 | 162 | amenity=place_of_worship [0x2c0b resolution 24] |
162 | 163 | amenity=police [0x3001 resolution 24] |
171 | 172 | # amenity=shelter is ambiguous; when possible, consider using other tags: |
172 | 173 | # tourism=lean_to or tourism=picnic_site |
173 | 174 | # shelter=yes on highway=bus_stop or highway=tram_stop or railway=halt |
174 | amenity=shelter & shelter_type!=public_transport [0x2b06 resolution 24 default_name 'Shelter'] | |
175 | amenity=shelter & shelter_type=basic_hut [0x2b06 resolution 24 default_name 'Shelter'] | |
175 | 176 | # amenity=supermarket is superceded by shop=supermarket |
176 | 177 | amenity=supermarket [0x2e02 resolution 24] |
177 | 178 | amenity=taxi [0x2f17 resolution 24] |
189 | 190 | healthcare=hospital | amenity=hospital | amenity=clinic [0x3002 resolution 22] |
190 | 191 | healthcare=* | amenity=dentist | amenity=doctors [0x3002 resolution 24] |
191 | 192 | |
192 | highway=motorway_junction [0x2100 resolution 24] | |
193 | ||
194 | highway=services & mkgmap:area2poi!=true [0x210f resolution 24 default_name 'Services'] | |
193 | highway=motorway_junction & exit:road_ref=* {add exit:to='${exit_to}' | '${destination}'} [0x2000 resolution 24] | |
194 | ||
195 | highway=services {add exit:facility="0x02,I,0x47,Features"} [0x230f resolution 20 default_name 'Services'] | |
195 | 196 | |
196 | 197 | historic=museum [0x2c02 resolution 24] |
197 | 198 | historic=archaeological_site | historic=ruins [0x2c02 resolution 24] |
201 | 202 | leisure=garden & name=* [0x2c06 resolution 24] |
202 | 203 | leisure=golf_course [0x2d05 resolution 24] |
203 | 204 | leisure=ice_rink [0x2d08 resolution 24] |
204 | leisure=marina [0x4300 resolution 24] | |
205 | leisure=nature_reserve & name=* [0x6612 resolution 24] | |
205 | leisure=marina [0x2f09 resolution 24] | |
206 | leisure=nature_reserve [0x6612 resolution 24] | |
206 | 207 | leisure=park [0x2c06 resolution 24] |
207 | leisure=pitch {name '${name} (${sport})' | '${sport}'} [0x2c08 resolution 24] | |
208 | leisure=pitch & (name=* | sport=*) {name '${name} (${sport})' | '${sport}'} [0x2c08 resolution 24] | |
208 | 209 | leisure=playground [0x2c06 resolution 24 default_name 'Playground'] |
209 | 210 | leisure=recreation_ground [0x2c08 resolution 24 default_name 'Rec.'] |
210 | 211 | leisure=sports_center | leisure=sports_centre {name '${name} (${sport})' | '${sport}'} [0x2d0a resolution 24] |
211 | 212 | leisure=stadium {name '${name} (${sport})' | '${sport}'} [0x2c08 resolution 24] |
212 | leisure=swimming_pool [0x2d09 resolution 24] | |
213 | (leisure=swimming_pool | amenity=swimming_pool) & access!=private & access!=no [0x2d09 resolution 24] | |
213 | 214 | leisure=track {name '${name} (${sport})' | '${sport}'} [0x2c08 resolution 24] |
214 | 215 | leisure=water_park [0x2d09 resolution 24] |
215 | 216 | |
218 | 219 | # Edge 705 displays 0x650a,0x6511,0x6512,0x6513,0x6603,0x6614 as hollow white circles, no menu |
219 | 220 | natural=cave_entrance [0x6601 resolution 24] |
220 | 221 | natural=cape [0x6606 resolution 24] |
221 | natural=cliff [0x6607 resolution 24] | |
222 | natural=cliff & name=* [0x6607 resolution 24] | |
222 | 223 | natural=peak {name '${name|def:}${ele|height:m=>ft|def:}'} [0x6616 resolution 24] |
223 | 224 | natural=rock [0x6614 resolution 24] |
224 | 225 | natural=volcano [0x2c0c resolution 24] |
226 | natural=bay & name=* [0x6503 resolution 20] | |
225 | 227 | |
226 | 228 | railway=station [0x2f08 resolution 22] |
227 | (public_transport=platform & rail=yes) | railway=halt [0x2f08 resolution 23] | |
228 | public_transport=platform | highway=bus_stop | railway=tram_stop [0x2f17 resolution 24] | |
229 | railway=halt [0x2f08 resolution 23] | |
230 | # if option --add-pois-to-lines in effect, add just 1 POI | |
231 | (railway=platform | (public_transport=platform & railway=*)) & (mkgmap:line2poi!=true | mkgmap:line2poitype=mid) [0x2f08 resolution 23] | |
232 | public_transport=platform & (mkgmap:line2poi!=true | mkgmap:line2poitype=mid) [0x2f17 resolution 24] | |
233 | highway=bus_stop | railway=tram_stop [0x2f17 resolution 24] | |
234 | ||
235 | shop=* & name!=* & shop!=yes & shop!=no & shop!=none & shop!=vacant {set name='${shop|subst:"_=> "}'} | |
236 | # Uncomment the following lines to enable these extra POI that some devices support | |
237 | #shop=convenience [0x2e0e resolution 24] | |
238 | #shop=florist [0x2e0f resolution 24] | |
239 | #shop=gift | shop=art | shop=antiques [0x2e10 resolution 24] | |
240 | #shop=music [0x2e11 resolution 24] | |
241 | #shop=sports [0x2e12 resolution 24] | |
242 | #shop=wine | shop=alcohol [0x2e13 resolution 24] | |
243 | #shop=books [0x2e14 resolution 24] | |
244 | # to here | |
229 | 245 | |
230 | 246 | shop=bakers [0x2e02 resolution 24] |
231 | 247 | shop=bakery [0x2e02 resolution 24] |
254 | 270 | shop=hairdresser [0x2f10 resolution 24] |
255 | 271 | shop=mall [0x2e04 resolution 24] |
256 | 272 | shop=organic [0x2e0a resolution 24] |
273 | shop=outdoor [0x2e08 resolution 24] | |
257 | 274 | shop=shoes [0x2e07 resolution 24] |
258 | 275 | shop=supermarket [0x2e02 resolution 24] |
259 | 276 | shop=tires [0x2f07 resolution 24] |
260 | 277 | shop=tyres [0x2f07 resolution 24] |
261 | shop=* & shop!=no & shop!=none {add name='${shop|subst:"_=> "}'} [0x2e0c resolution 24] | |
278 | shop=* & name=* [0x2e0c resolution 24] | |
262 | 279 | |
263 | 280 | sport=9pin [0x2d07 resolution 24] |
264 | 281 | sport=10pin | leisure=bowling_alley [0x2d07 resolution 24] |
274 | 291 | tourism=artwork [0x2c04 resolution 24] |
275 | 292 | tourism=aquarium [0x2c07 resolution 24] |
276 | 293 | tourism=bed_and_breakfast [0x2b02 resolution 24] |
277 | tourism=camp_site [0x2b05 resolution 24] | |
278 | tourism=caravan_site [0x2b05 resolution 24] | |
294 | # NB: different devices use different codes for camp/caravan_site, have both! | |
295 | tourism=camp_site [0x2b03 resolution 24] [0x2b05 resolution 24] | |
296 | tourism=caravan_site [0x2b03 resolution 24] [0x2b05 resolution 24] | |
279 | 297 | tourism=chalet [0x2b02 resolution 24] |
280 | 298 | tourism=guest_house [0x2b02 resolution 24] |
281 | 299 | tourism=hostel [0x2b02 resolution 24] |
282 | tourism=hotel | tourism=motel [0x2b01 resolution 24] | |
300 | # Have both hotel &| restaurant POIs | |
301 | tourism=hotel | tourism=motel {set tmp:stopMopUp=yes} [0x2b01 resolution 24 continue with_actions] | |
283 | 302 | tourism=information [0x4c00 resolution 24] |
284 | 303 | # tourism=lean_to replaces some uses of amenity=shelter |
285 | 304 | tourism=lean_to [0x2b05 resolution 24 default_name 'lean-to'] |
291 | 310 | tourism=viewpoint {name '${name} - ${description}' | '${name}'} [0x2c04 resolution 24] |
292 | 311 | tourism=wine_cellar [0x2c0a resolution 24] |
293 | 312 | tourism=zoo [0x2c07 resolution 24] |
294 | tourism=* & tourism!=yes & tourism!=no {add name='${tourism|subst:"_=> "}'} | |
295 | tourism=* & tourism!=no [0x2c0d resolution 24] | |
313 | tourism=* & name!=* & tourism!=yes & tourism!=no {set name='${tourism|subst:"_=> "}'} | |
314 | tourism=* & tourism!=no & tmp:stopMopUp!=yes [0x2c0d resolution 24] | |
296 | 315 | |
297 | 316 | # amenity=restaurant/fast_food/cafe/pub and shop=* can use cuisine, so have cuisine section later than where the others |
298 | 317 | # should take precedence |
354 | 373 | barrier=stile | barrier=kissing_gate | barrier=lift_gate | barrier=swing_gate |
355 | 374 | {add name='${barrier|subst:"_=> "}'} [0x3200 resolution 24] |
356 | 375 | |
357 | landuse=basin | landuse=reservoir [0x650f resolution 24] | |
376 | landuse=basin [0x6603 resolution 24] | |
377 | landuse=reservoir | water=reservoir [0x650f resolution 22] | |
358 | 378 | |
359 | 379 | natural=beach [0x6604 resolution 24] |
360 | 380 | natural=glacier [0x650a resolution 24] |
361 | 381 | natural=spring [0x6511 resolution 24] |
362 | 382 | natural=stream [0x6512 resolution 24] |
363 | 383 | natural=water & (water=canal | water=lock) & name=* [0x6505 resolution 24] |
384 | (water=lake | water=pond) & name=* [0x650d resolution 24] | |
364 | 385 | natural=water & name=* [0x6603 resolution 24] |
365 | 386 | natural=waterfall | waterway=waterfall [0x6508 resolution 24] |
366 | 387 | natural=wetland & name=* [0x6513 resolution 24] |
14 | 14 | |
15 | 15 | for f in *.txt |
16 | 16 | do |
17 | mwtext -t text $f > ../dist/doc/$f | |
17 | mwconv -t text $f > ../dist/doc/$f | |
18 | 18 | done |
19 | 19 | |
20 | 20 | # Use the actual options help file. |
386 | 386 | int latHp = co.getHighPrecLat(); |
387 | 387 | int lonHp = co.getHighPrecLon(); |
388 | 388 | |
389 | return (long)(latHp & 0xffffffffL) << 32 | (lonHp & 0xffffffffL); | |
389 | return (latHp & 0xffffffffL) << 32 | (lonHp & 0xffffffffL); | |
390 | 390 | } |
391 | 391 | |
392 | 392 | public static int numberToPointerSize(int n) { |
28 | 28 | */ |
29 | 29 | public class Exit { |
30 | 30 | |
31 | public final static String TAG_ROAD_REF = "exit:road_ref"; | |
32 | public final static String TAG_TO = "exit:to"; | |
33 | public final static String TAG_FACILITY = "exit:facility"; | |
31 | public static final String TAG_ROAD_REF = "exit:road_ref"; | |
32 | public static final String TAG_TO = "exit:to"; | |
33 | public static final String TAG_FACILITY = "exit:facility"; | |
34 | 34 | |
35 | 35 | private final Highway highway; |
36 | 36 | private Label description; |
37 | private final List<ExitFacility> facilities = new ArrayList<ExitFacility>(); | |
37 | private final List<ExitFacility> facilities = new ArrayList<>(); | |
38 | 38 | |
39 | 39 | public Exit(Highway highway) { |
40 | 40 | this.highway = highway; |
60 | 60 | * the bounding box of the tile |
61 | 61 | * @param demPolygonMapUnits |
62 | 62 | * a bounding polygon which might be smaller than the area |
63 | * @param pathToHGT | |
64 | * comma separated list of directories or zip files | |
63 | * @param pathsToHGT | |
64 | * string with comma separated list of directories or zip files | |
65 | 65 | * @param pointDistances |
66 | 66 | * list of distances which determine the resolution |
67 | 67 | * @param outsidePolygonHeight |
69 | 69 | * bounding polygon |
70 | 70 | * @return a new bounding box that should be used for the TRE file |
71 | 71 | */ |
72 | public Area calc(Area area, java.awt.geom.Area demPolygonMapUnits, String pathToHGT, List<Integer> pointDistances, | |
72 | public Area calc(Area area, java.awt.geom.Area demPolygonMapUnits, String pathsToHGT, List<Integer> pointDistances, | |
73 | 73 | short outsidePolygonHeight, InterpolationMethod interpolationMethod) { |
74 | 74 | // HGT area is extended by EXTRA degrees in each direction |
75 | HGTConverter hgtConverter = new HGTConverter(pathToHGT, area, demPolygonMapUnits, EXTRA); | |
75 | HGTConverter hgtConverter = new HGTConverter(pathsToHGT, area, demPolygonMapUnits, EXTRA); | |
76 | 76 | hgtConverter.setInterpolationMethod(interpolationMethod); |
77 | 77 | hgtConverter.setOutsidePolygonHeight(outsidePolygonHeight); |
78 | 78 |
156 | 156 | // wasn't in the right form to guess |
157 | 157 | throw new ExitException("Invalid character set: " + cs); |
158 | 158 | } |
159 | } else if (cs.equals("latin1")) { | |
159 | } else if ("latin1".equals(cs)) { | |
160 | 160 | return 1252; |
161 | 161 | } |
162 | 162 | return 0; |
14 | 14 | |
15 | 15 | import java.util.Locale; |
16 | 16 | |
17 | import uk.me.parabola.log.Logger; | |
18 | ||
19 | 17 | /** |
20 | 18 | * A sparse character-based transliterator that leaves most characters unchanged. |
21 | 19 | * |
22 | 20 | */ |
23 | 21 | public class SparseTransliterator implements Transliterator { |
24 | private static final Logger log = Logger.getLogger(SparseTransliterator.class); | |
25 | 22 | |
26 | 23 | private final boolean useNoMacron; |
27 | 24 | private boolean forceUppercase; |
28 | 25 | |
29 | 26 | public SparseTransliterator(String targetCharset) { |
30 | useNoMacron = (targetCharset.equals("nomacron")) ? true : false; | |
27 | useNoMacron = "nomacron".equals(targetCharset); | |
31 | 28 | } |
32 | 29 | |
33 | 30 | /** |
47 | 44 | // Only macrons are modified, all other chars (including non-ascii) are left unchanged |
48 | 45 | if (c == 0x101) // Unicode Character 'LATIN SMALL LETTER A WITH MACRON' (U+0101) |
49 | 46 | c = 'a'; |
50 | if (c == 0x113) // Unicode Character 'LATIN SMALL LETTER E WITH MACRON' (U+0113) | |
47 | else if (c == 0x113) // Unicode Character 'LATIN SMALL LETTER E WITH MACRON' (U+0113) | |
51 | 48 | c = 'e'; |
52 | if (c == 0x12b) // Unicode Character 'LATIN SMALL LETTER I WITH MACRON' (U+012B) | |
49 | else if (c == 0x12b) // Unicode Character 'LATIN SMALL LETTER I WITH MACRON' (U+012B) | |
53 | 50 | c = 'i'; |
54 | if (c == 0x14d) // Unicode Character 'LATIN SMALL LETTER O WITH MACRON' (U+014D) | |
51 | else if (c == 0x14d) // Unicode Character 'LATIN SMALL LETTER O WITH MACRON' (U+014D) | |
55 | 52 | c = 'o'; |
56 | if (c == 0x16b) // Unicode Character 'LATIN SMALL LETTER U WITH MACRON' (U+016B) | |
53 | else if (c == 0x16b) // Unicode Character 'LATIN SMALL LETTER U WITH MACRON' (U+016B) | |
57 | 54 | c = 'u'; |
58 | 55 | } |
59 | 56 | sb.append(c); |
48 | 48 | |
49 | 49 | private final Region region; |
50 | 50 | |
51 | private final List<ExitPoint> exits = new ArrayList<ExitPoint>(); | |
51 | private final List<ExitPoint> exits = new ArrayList<>(); | |
52 | 52 | |
53 | 53 | private Label label; |
54 | 54 |
17 | 17 | |
18 | 18 | import java.util.List; |
19 | 19 | |
20 | import uk.me.parabola.imgfmt.Utils; | |
21 | 20 | import uk.me.parabola.imgfmt.app.Exit; |
22 | 21 | import uk.me.parabola.imgfmt.app.ImgFileWriter; |
23 | 22 | import uk.me.parabola.imgfmt.app.Label; |
105 | 104 | } |
106 | 105 | |
107 | 106 | void write(ImgFileWriter writer, int POIGlobalFlags, int realofs, |
108 | long numCities, long numZips, long numHighways, long numExitFacilities) { | |
107 | int cityPtrSize, int zipPtrSize, int highwayPtrSize, int exitFacilityPtrSize) { | |
109 | 108 | assert offset == realofs : "offset = " + offset + " realofs = " + realofs; |
110 | 109 | int ptr = poiName.getOffset(); |
111 | 110 | if (POIGlobalFlags != getPOIFlags()) |
129 | 128 | writer.put3u(streetName.getOffset()); |
130 | 129 | |
131 | 130 | if (city != null) |
132 | writer.putNu(Utils.numberToPointerSize((int)numCities), city.getIndex()); | |
131 | writer.putNu(cityPtrSize, city.getIndex()); | |
133 | 132 | |
134 | 133 | if (zip != null) |
135 | writer.putNu(Utils.numberToPointerSize((int)numZips), zip.getIndex()); | |
134 | writer.putNu(zipPtrSize, zip.getIndex()); | |
136 | 135 | |
137 | 136 | if (complexPhoneNumber != null) |
138 | 137 | { |
162 | 161 | writer.put3u(val); |
163 | 162 | |
164 | 163 | int highwayIndex = exit.getHighway().getIndex(); |
165 | writer.putNu(Utils.numberToPointerSize((int)numHighways), highwayIndex); | |
164 | writer.putNu(highwayPtrSize, highwayIndex); | |
166 | 165 | |
167 | 166 | if(ef != null) { |
168 | 167 | int exitFacilityIndex = ef.getIndex(); |
169 | writer.putNu(Utils.numberToPointerSize((int)numExitFacilities), exitFacilityIndex); | |
168 | writer.putNu(exitFacilityPtrSize, exitFacilityIndex); | |
170 | 169 | } |
171 | 170 | } |
172 | 171 | } |
178 | 177 | if (simpleStreetNumber.isUsed() || streetNumberName != null) |
179 | 178 | b |= HAS_STREET_NUM; |
180 | 179 | if (city != null) |
181 | b |= HAS_CITY; | |
180 | b |= HAS_CITY; | |
182 | 181 | if (zip != null) |
183 | b |= HAS_ZIP; | |
182 | b |= HAS_ZIP; | |
184 | 183 | if (simplePhoneNumber.isUsed() || complexPhoneNumber != null) |
185 | 184 | b |= HAS_PHONE; |
186 | 185 | if (exit != null) |
213 | 212 | } |
214 | 213 | |
215 | 214 | flag |= 0x80; // gpsmapedit asserts for this bit set |
216 | ||
215 | ||
217 | 216 | return flag; |
218 | 217 | } |
219 | 218 | |
222 | 221 | * |
223 | 222 | * @return Number of bytes needed by this entry |
224 | 223 | */ |
225 | int calcOffset(int ofs, int POIGlobalFlags, long numCities, long numZips, long numHighways, long numExitFacilities) { | |
224 | int calcOffset(int ofs, int POIGlobalFlags, int cityPtrSize, int zipPtrSize, int highwayPtrSize, int exitFacilityPtrSize) { | |
226 | 225 | offset = ofs; |
227 | 226 | int size = 3; |
228 | 227 | if (exit != null) { |
229 | 228 | size += 3; |
230 | size += Utils.numberToPointerSize((int)numHighways); | |
229 | size += highwayPtrSize; | |
231 | 230 | if(!exit.getFacilities().isEmpty()) |
232 | size += Utils.numberToPointerSize((int)numExitFacilities); | |
231 | size += exitFacilityPtrSize; | |
233 | 232 | } |
234 | 233 | if (POIGlobalFlags != getPOIFlags()) |
235 | 234 | size += 1; |
243 | 242 | size += 3; |
244 | 243 | if (streetName != null) |
245 | 244 | size += 3; |
246 | if (city != null) | |
247 | { | |
248 | /* | |
249 | depending on how many cities are in the LBL block we have | |
250 | to write one to three bytes | |
251 | */ | |
252 | size += Utils.numberToPointerSize((int)numCities); | |
253 | } | |
254 | if (zip != null) { | |
255 | // depending on how many zips are in the LBL block we have to write one to three bytes | |
256 | size += Utils.numberToPointerSize((int)numZips); | |
257 | } | |
245 | if (city != null) | |
246 | size += cityPtrSize; | |
247 | if (zip != null) | |
248 | size += zipPtrSize; | |
258 | 249 | return size; |
259 | 250 | } |
260 | 251 | |
301 | 292 | while (i < number.length()) { |
302 | 293 | |
303 | 294 | int c1 = decodeChar(number.charAt(i++)); |
304 | ||
305 | int c2; | |
306 | if (i < number.length()) { | |
307 | c2 = decodeChar(number.charAt(i++)); | |
308 | } else | |
309 | c2 = 10; | |
295 | int c2 = (i < number.length()) ? decodeChar(number.charAt(i++)) : 10; | |
310 | 296 | |
311 | 297 | // Only 0-9 and - allowed |
312 | 298 | if (c1 < 0 || c1 > 10 || c2 < 0 || c2 > 10) |
23 | 23 | import java.util.Random; |
24 | 24 | import java.util.TreeMap; |
25 | 25 | |
26 | import uk.me.parabola.imgfmt.Utils; | |
26 | 27 | import uk.me.parabola.imgfmt.app.Exit; |
27 | 28 | import uk.me.parabola.imgfmt.app.ImgFileWriter; |
28 | 29 | import uk.me.parabola.imgfmt.app.Label; |
103 | 104 | |
104 | 105 | int poistart = writer.position(); |
105 | 106 | int poiglobalflags = placeHeader.getPOIGlobalFlags(); |
107 | final int cityPtrSize = Utils.numberToPointerSize(cityList.size()); | |
108 | final int zipPtrSize = Utils.numberToPointerSize(postalCodes.size()); | |
109 | final int highwayPtrSize = Utils.numberToPointerSize(highways.size()); | |
110 | final int exitFacilityPtrSize = Utils.numberToPointerSize(exitFacilities.size()); | |
106 | 111 | for (POIRecord p : pois) |
107 | 112 | p.write(writer, poiglobalflags, |
108 | writer.position() - poistart, cityList.size(), postalCodes.size(), highways.size(), exitFacilities.size()); | |
113 | writer.position() - poistart, cityPtrSize, zipPtrSize, highwayPtrSize, exitFacilityPtrSize); | |
109 | 114 | placeHeader.endPOI(writer.position()); |
110 | 115 | |
111 | 116 | int numPoiIndexEntries = 0; |
286 | 291 | placeHeader.setPOIGlobalFlags(poiFlags); |
287 | 292 | |
288 | 293 | int ofs = 0; |
294 | final int cityPtrSize = Utils.numberToPointerSize(cityList.size()); | |
295 | final int zipPtrSize = Utils.numberToPointerSize(postalCodes.size()); | |
296 | final int highwayPtrSize = Utils.numberToPointerSize(highways.size()); | |
297 | final int exitFacilityPtrSize = Utils.numberToPointerSize(exitFacilities.size()); | |
289 | 298 | for (POIRecord p : pois) |
290 | ofs += p.calcOffset(ofs, poiFlags, cityList.size(), postalCodes.size(), highways.size(), exitFacilities.size()); | |
299 | ofs += p.calcOffset(ofs, poiFlags, cityPtrSize, zipPtrSize, highwayPtrSize, exitFacilityPtrSize); | |
291 | 300 | } |
292 | 301 | |
293 | 302 | /** |
56 | 56 | public static final boolean WITH_EXT_TYPE_DATA = true; |
57 | 57 | public static final boolean WITHOUT_EXT_TYPE_DATA = false; |
58 | 58 | |
59 | private final Deque<Closeable> toClose = new ArrayDeque<Closeable>(); | |
59 | private final Deque<Closeable> toClose = new ArrayDeque<>(); | |
60 | 60 | |
61 | 61 | public MapReader(String filename) throws FileNotFoundException { |
62 | 62 | FileSystem fs = ImgFS.openFs(filename); |
111 | 111 | * @param level The level, lower numbers are the most detailed. |
112 | 112 | */ |
113 | 113 | public List<Point> pointsForLevel(int level, boolean withExtType) { |
114 | List<Point> points = new ArrayList<Point>(); | |
114 | List<Point> points = new ArrayList<>(); | |
115 | 115 | |
116 | 116 | Subdivision[] subdivisions = treFile.subdivForLevel(level); |
117 | 117 | for (Subdivision sd : subdivisions) { |
134 | 134 | * @param level The level, lower numbers are the most detailed. |
135 | 135 | */ |
136 | 136 | public List<Polyline> linesForLevel(int level) { |
137 | ArrayList<Polyline> lines = new ArrayList<Polyline>(); | |
137 | ArrayList<Polyline> lines = new ArrayList<>(); | |
138 | 138 | |
139 | 139 | Subdivision[] subdivisions = treFile.subdivForLevel(level); |
140 | 140 | for (Subdivision div : subdivisions) { |
147 | 147 | |
148 | 148 | |
149 | 149 | public List<Polygon> shapesForLevel(int level, boolean witExtTypeData) { |
150 | ArrayList<Polygon> shapes = new ArrayList<Polygon>(); | |
150 | ArrayList<Polygon> shapes = new ArrayList<>(); | |
151 | 151 | |
152 | 152 | Subdivision[] subdivisions = treFile.subdivForLevel(level); |
153 | 153 | for (Subdivision div : subdivisions) { |
29 | 29 | * @author Steve Ratcliffe |
30 | 30 | */ |
31 | 31 | public class Mdr10 extends MdrMapSection { |
32 | // The maximum group number. Note that this is 1 based, not 0 based. | |
32 | /** The maximum group number. Note that this is 1 based, not 0 based. */ | |
33 | 33 | private static final int MAX_GROUP_NUMBER = MdrUtils.MAX_GROUP; |
34 | 34 | |
35 | @SuppressWarnings({"unchecked"}) | |
36 | private List<Mdr10Record>[] poiTypes = new ArrayList[MAX_GROUP_NUMBER+1]; | |
35 | private List<List<Mdr10Record>> poiTypes = new ArrayList<>(); | |
37 | 36 | |
38 | 37 | private int numberOfPois; |
39 | 38 | |
40 | 39 | public Mdr10(MdrConfig config) { |
41 | 40 | setConfig(config); |
42 | ||
43 | for (int i = 1; i <= MAX_GROUP_NUMBER; i++) { | |
44 | poiTypes[i] = new ArrayList<>(); | |
45 | } | |
41 | while (poiTypes.size() <= MAX_GROUP_NUMBER) | |
42 | poiTypes.add(new ArrayList<>()); | |
46 | 43 | } |
47 | 44 | |
48 | 45 | public void addPoiType(Mdr11Record poi) { |
59 | 56 | t.setSubtype(MdrUtils.getSubtypeFromFullType(fullType)); |
60 | 57 | } |
61 | 58 | t.setMdr11ref(poi); |
62 | poiTypes[group].add(t); | |
59 | poiTypes.get(group).add(t); | |
63 | 60 | } |
64 | 61 | |
65 | 62 | public void writeSectData(ImgFileWriter writer) { |
102 | 99 | public Map<Integer, Integer> getGroupSizes() { |
103 | 100 | Map<Integer, Integer> m = new LinkedHashMap<>(); |
104 | 101 | |
105 | for (int i = 1; i <= MAX_GROUP_NUMBER; i++) { | |
106 | List<Mdr10Record> poiGroup = poiTypes[i]; | |
107 | if (!poiGroup.isEmpty()) | |
108 | m.put(i, poiGroup.size()); | |
102 | for (int group = 1; group <= MAX_GROUP_NUMBER; group++) { | |
103 | int size = poiTypes.get(group).size(); | |
104 | if (size > 0) | |
105 | m.put(group, size); | |
109 | 106 | } |
110 | 107 | return m; |
111 | 108 | } |
362 | 362 | |
363 | 363 | |
364 | 364 | public List<Mdr7Record> getStreets() { |
365 | return Collections.unmodifiableList((ArrayList<Mdr7Record>) allStreets); | |
365 | return Collections.unmodifiableList(allStreets); | |
366 | 366 | } |
367 | 367 | |
368 | 368 | public List<Mdr7Record> getSortedStreets() { |
127 | 127 | return Collections.unmodifiableSet(mdr7Excl); |
128 | 128 | } |
129 | 129 | |
130 | public void setMdr7Excl(String exclList) { | |
131 | mdr7Excl = stringToSet(exclList); | |
132 | } | |
133 | ||
134 | 130 | public Set<String> getMdr7Del() { |
135 | 131 | return Collections.unmodifiableSet(mdr7Del); |
136 | } | |
137 | ||
138 | public void setMdr7Del(String delList) { | |
139 | mdr7Del = stringToSet(delList); | |
140 | } | |
141 | ||
142 | private static Set<String> stringToSet (String opt) { | |
143 | Set<String> set; | |
144 | ||
145 | if (opt == null) | |
146 | set = Collections.emptySet(); | |
147 | else { | |
148 | if (opt.startsWith("'") || opt.startsWith("\"")) | |
149 | opt = opt.substring(1); | |
150 | if (opt.endsWith("'") || opt.endsWith("\"")) | |
151 | opt = opt.substring(0, opt.length() - 1); | |
152 | List<String> list = Arrays.asList(opt.split(",")); | |
153 | set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); | |
154 | for (String s : list) { | |
155 | set.add(s.trim()); | |
156 | } | |
157 | } | |
158 | return set; | |
159 | 132 | } |
160 | 133 | |
161 | 134 | /** |
163 | 136 | * @param opt the option string given. Expected is a comma separated list,eg |
164 | 137 | * "0x2800" or "0x2800, 0x6400-0x661f" |
165 | 138 | */ |
166 | public void setPoiExcl (String opt) { | |
167 | if (opt == null) | |
168 | return; | |
169 | poiExclTypes = new TreeSet<>(); | |
170 | ||
171 | String[] opts = opt.split(","); | |
172 | for (String range : opts) { | |
173 | if (range.contains("-")) { | |
174 | String[] ranges = range.split("-"); | |
175 | if (ranges.length != 2) | |
176 | throw new IllegalArgumentException("invalid range in option " + range); | |
177 | genTypesForRange(poiExclTypes, ranges[0], ranges[1]); | |
178 | } else { | |
179 | genTypesForRange(poiExclTypes, range,range); | |
139 | public void setPoiExcl (List<String> opts) { | |
140 | if (opts.isEmpty()) | |
141 | poiExclTypes = Collections.emptyNavigableSet(); | |
142 | else { | |
143 | poiExclTypes = new TreeSet<>(); | |
144 | for (String range : opts) { | |
145 | if (range.contains("-")) { | |
146 | String[] ranges = range.split("-"); | |
147 | if (ranges.length != 2) | |
148 | throw new IllegalArgumentException("invalid range in option " + range); | |
149 | genTypesForRange(poiExclTypes, ranges[0], ranges[1]); | |
150 | } else { | |
151 | genTypesForRange(poiExclTypes, range,range); | |
152 | } | |
180 | 153 | } |
181 | 154 | } |
182 | 155 | } |
221 | 194 | |
222 | 195 | public void setIndexOptions(CommandArgs args) { |
223 | 196 | setSplitName(args.get("split-name-index", false)); |
224 | setMdr7Excl(args.get("mdr7-excl", null)); | |
225 | setMdr7Del(args.get("mdr7-del", null)); | |
226 | setPoiExcl(args.get("poi-excl-index", null)); | |
197 | mdr7Excl = args.argToSet("mdr7-excl", null); | |
198 | mdr7Del = args.argToSet("mdr7-del", null); | |
199 | setPoiExcl(args.argToList("poi-excl-index", null)); | |
227 | 200 | } |
228 | 201 | } |
97 | 97 | private static final short TKM_UNPAVED = TagDict.getInstance().xlate("mkgmap:unpaved"); |
98 | 98 | private static final short TKM_FERRY = TagDict.getInstance().xlate("mkgmap:ferry"); |
99 | 99 | private static final short TKM_THROUGHROUTE = TagDict.getInstance().xlate("mkgmap:throughroute"); |
100 | private static final short TKM_JUNCTION = TagDict.getInstance().xlate("junction"); | |
101 | private static final short TKM_ONEWAY = TagDict.getInstance().xlate("oneway"); | |
100 | private static final short TK_JUNCTION = TagDict.getInstance().xlate("junction"); | |
101 | private static final short TK_ONEWAY = TagDict.getInstance().xlate("oneway"); | |
102 | 102 | |
103 | 103 | public static byte evalRouteTags(Element el) { |
104 | 104 | byte routeFlags = 0; |
120 | 120 | routeFlags |= R_THROUGHROUTE; |
121 | 121 | |
122 | 122 | // tags without the mkgmap: prefix |
123 | if ("roundabout".equals(el.getTag(TKM_JUNCTION))) | |
123 | if ("roundabout".equals(el.getTag(TK_JUNCTION))) | |
124 | 124 | routeFlags |= R_ROUNDABOUT; |
125 | if (el.tagIsLikeYes(TKM_ONEWAY)) | |
125 | if (el.tagIsLikeYes(TK_ONEWAY)) | |
126 | 126 | routeFlags |= R_ONEWAY; |
127 | 127 | |
128 | 128 | return routeFlags; |
526 | 526 | if (arc.getLengthInMeter() <= 0.0001) |
527 | 527 | ignoreAngle = true; |
528 | 528 | Integer angle = Math.round(getAngle(fromArc, arc)); |
529 | List<RouteArc> list = angleMap.get(angle); | |
530 | if (list == null){ | |
531 | list = new ArrayList<>(); | |
532 | angleMap.put(angle, list); | |
533 | } | |
534 | list.add(arc); | |
529 | angleMap.computeIfAbsent(angle, k-> new ArrayList<>()).add(arc); | |
535 | 530 | } |
536 | 531 | |
537 | 532 | // find the group that fits best |
316 | 316 | |
317 | 317 | // We write this big endian |
318 | 318 | if(log.isDebugEnabled()) |
319 | log.debug("val is", Integer.toHexString((int)val)); | |
319 | log.debug("val is", Integer.toHexString(val)); | |
320 | 320 | writer.put1u(val >> 8); |
321 | 321 | writer.put1u(val & 0xff); |
322 | 322 | } |
185 | 185 | |
186 | 186 | private Byte morseLetter; |
187 | 187 | |
188 | private final int DISTANCE_FLAG_METRIC_INDEX = 0; | |
189 | private final int DISTANCE_FLAG_TENTHS_INDEX = 1; | |
188 | private static final int DISTANCE_FLAG_METRIC_INDEX = 0; | |
189 | private static final int DISTANCE_FLAG_TENTHS_INDEX = 1; | |
190 | 190 | |
191 | 191 | private static final byte FLAGS0_RACON_BIT = (1); |
192 | 192 | private static final byte FLAGS0_NOTE_BIT = (1 << 1); |
220 | 220 | protected byte[] getExtTypeExtraBytes(MapObject mapObject) { |
221 | 221 | try { |
222 | 222 | return encodeExtraBytes(mapObject); |
223 | } | |
224 | catch (Exception e) { | |
223 | } catch (Exception e) { | |
225 | 224 | log.error(objectName + " (" + e + ")"); |
226 | 225 | return null; |
227 | 226 | } |
276 | 275 | lt = "interrupted quick"; |
277 | 276 | else if(parts[0].startsWith("IVQ ")) |
278 | 277 | lt = "interrupted very quick"; |
279 | else if(parts[0].startsWith("UQ")) | |
278 | else if(parts[0].startsWith("IUQ")) | |
280 | 279 | lt = "interrupted ultra quick"; |
281 | 280 | else if(parts[0].startsWith("AI")) |
282 | 281 | lt = "alternating"; |
283 | 282 | else if(parts[0].startsWith("Mo")) { |
284 | if(parts[0].indexOf("(") == 2) | |
283 | if(parts[0].indexOf('(') == 2) | |
285 | 284 | lt = parts[0].substring(3, 4); |
286 | 285 | else if(parts.length > 1 && parts[i].startsWith("(")) { |
287 | 286 | lt = parts[i].substring(1, 2); |
290 | 289 | } |
291 | 290 | |
292 | 291 | String group2 = null; |
293 | if(!parts[0].startsWith("Mo") && parts[0].indexOf("(") > 0) { | |
294 | group2 = parts[0].substring(parts[0].indexOf("(") + 1, parts[0].length() - 1); | |
292 | if(!parts[0].startsWith("Mo") && parts[0].indexOf('(') > 0) { | |
293 | group2 = parts[0].substring(parts[0].indexOf('(') + 1, parts[0].length() - 1); | |
295 | 294 | } |
296 | 295 | else if(i < parts.length && parts[i].startsWith("(")) { |
297 | 296 | // should be (group) |
334 | 333 | } |
335 | 334 | |
336 | 335 | String period = null; |
337 | if((i + 1) < parts.length && parts[i+1].equals("s")) { | |
336 | if((i + 1) < parts.length && "s".equals(parts[i+1])) { | |
338 | 337 | // period |
339 | 338 | period = parts[i]; |
340 | 339 | i += 2; |
341 | 340 | } |
342 | 341 | |
343 | 342 | String height = null; |
344 | if((i + 1) < parts.length && parts[i+1].equals("m")) { | |
343 | if((i + 1) < parts.length && "m".equals(parts[i+1])) { | |
345 | 344 | // height |
346 | 345 | height = parts[i]; |
347 | 346 | i += 2; |
348 | 347 | } |
349 | 348 | |
350 | 349 | String range = null; |
351 | if((i + 1) < parts.length && parts[i+1].equals("M")) { | |
350 | if((i + 1) < parts.length && "M".equals(parts[i+1])) { | |
352 | 351 | // range |
353 | 352 | range = parts[i]; |
354 | 353 | i += 2; |
381 | 380 | String[] parts = desc.split(":"); |
382 | 381 | if(parts.length == 4) { |
383 | 382 | colour = parts[0]; |
384 | if (parts[1].equalsIgnoreCase("shore") || parts[2].equalsIgnoreCase("shore")) { | |
383 | if ("shore".equalsIgnoreCase(parts[1]) || "shore".equalsIgnoreCase(parts[2])) { | |
385 | 384 | log.error(objectName + ": shore is no valid sector bound, please annotate a numeric value"); |
386 | 385 | } else { |
387 | 386 | sectorStart = Double.valueOf(parts[1]).intValue(); |
399 | 398 | else return sectorStart > other.sectorStart? 1: -1; |
400 | 399 | } |
401 | 400 | } |
402 | List<SeamarkLight> lights = new ArrayList<SeamarkLight>(); | |
401 | List<SeamarkLight> lights = new ArrayList<>(); | |
403 | 402 | // create a SeamarkLight for each light |
404 | 403 | for(int n = 1; n <= 100; ++n) { |
405 | 404 | String desc = attributes.get("seamark:light:" + n); |
412 | 411 | lights.sort(null); |
413 | 412 | // generate the descriptor string - each light is |
414 | 413 | // specified as color,range,sectorStartAngle |
415 | String light = null; | |
414 | StringBuilder light = null; | |
416 | 415 | for(int i = 0; i < lights.size(); ++i) { |
417 | 416 | SeamarkLight sml = lights.get(i); |
418 | 417 | if(light == null) |
419 | light = ""; | |
418 | light = new StringBuilder(); | |
420 | 419 | else |
421 | light += "/"; | |
422 | light += sml.colour + "," + sml.range + "," + sml.sectorStart; | |
423 | //light += sml.colour + "," + sml.range/10 + "." + sml.range%10 + "," + sml.sectorStart; | |
420 | light.append('/'); | |
421 | light.append(sml.colour).append(',').append(sml.range).append(',').append(sml.sectorStart); | |
424 | 422 | if((i + 1) < lights.size()) { |
425 | 423 | if(sml.sectorEnd != lights.get(i + 1).sectorStart) { |
426 | 424 | // gap between lit sectors |
427 | light += "/unlit,0," + sml.sectorEnd; | |
425 | light.append("/unlit,0,").append(sml.sectorEnd); | |
428 | 426 | } |
429 | 427 | } |
430 | 428 | else if(sml.sectorEnd != lights.get(0).sectorStart) { |
431 | 429 | // gap to end |
432 | light += "/unlit,0," + sml.sectorEnd; | |
430 | light.append("/unlit,0,").append(sml.sectorEnd); | |
433 | 431 | } |
434 | 432 | } |
435 | 433 | if(light != null) { |
436 | //System.err.println(light); | |
437 | attributes.put("light", light); | |
434 | attributes.put("light", light.toString()); | |
438 | 435 | if(attributes.get("seamark:light:character") == null) |
439 | 436 | attributes.put("type", "fixed"); |
440 | 437 | } |
443 | 440 | String sequence = attributes.get("seamark:light:sequence"); |
444 | 441 | |
445 | 442 | if(sequence != null) { |
446 | StringBuffer periods = new StringBuffer(); | |
447 | StringBuffer eclipse = new StringBuffer(); | |
443 | StringBuilder periods = new StringBuilder(); | |
444 | StringBuilder eclipse = new StringBuilder(); | |
448 | 445 | for(String p : sequence.split("[+,]")) { |
449 | 446 | if (p.startsWith("(") && p.endsWith(")")) { |
450 | 447 | // phases of eclipse are enclosed in (), remove them |
867 | 864 | return null; |
868 | 865 | } |
869 | 866 | |
870 | private boolean meansYes(String s) { | |
867 | private static boolean meansYes(String s) { | |
871 | 868 | if(s == null) |
872 | 869 | return false; |
873 | 870 | s = s.toLowerCase(); |
874 | 871 | return ("yes".startsWith(s) || "true".startsWith(s) || "1".equals(s)); |
875 | 872 | } |
876 | 873 | |
877 | private Integer parseDistance(String ds, boolean[] flags) { | |
874 | private static Integer parseDistance(String ds, boolean[] flags) { | |
878 | 875 | ParsePosition pp = new ParsePosition(0); |
879 | 876 | Number dn = new DecimalFormat().parse(ds, pp); |
880 | 877 | if(dn != null) { |
882 | 879 | int di = dn.intValue(); |
883 | 880 | flags[DISTANCE_FLAG_METRIC_INDEX] = true; |
884 | 881 | flags[DISTANCE_FLAG_TENTHS_INDEX] = false; |
885 | if("ft".equals(ds.substring(pp.getIndex()).trim().toLowerCase())) | |
882 | if("ft".equalsIgnoreCase(ds.substring(pp.getIndex()).trim())) | |
886 | 883 | flags[DISTANCE_FLAG_METRIC_INDEX] = false; |
887 | 884 | if((double)di != dd) { |
888 | 885 | // number has fractional part |
1037 | 1034 | return 0; |
1038 | 1035 | } |
1039 | 1036 | |
1040 | private int[] parsePeriods(String ps) { | |
1037 | private static int[] parsePeriods(String ps) { | |
1041 | 1038 | if(ps == null) |
1042 | 1039 | return ZERO_INT_ARRAY; |
1043 | 1040 | String [] psa = ps.split(","); |
1054 | 1051 | String[] defs = new String[0]; |
1055 | 1052 | if(ls.startsWith("(")) { |
1056 | 1053 | // handle polish syntax "(c,r,a),(c,r,a)..." |
1057 | List<String> out = new ArrayList<String>(); | |
1054 | List<String> out = new ArrayList<>(); | |
1058 | 1055 | int start = 0; |
1059 | 1056 | // start should be on the '(' at the start of the loop |
1060 | 1057 | while(start < ls.length()) { |
28 | 28 | * @author Steve Ratcliffe |
29 | 29 | */ |
30 | 30 | public interface InternalFiles { |
31 | public RGNFile getRgnFile(); | |
31 | RGNFile getRgnFile(); | |
32 | 32 | |
33 | public LBLFile getLblFile(); | |
33 | LBLFile getLblFile(); | |
34 | 34 | |
35 | public TREFile getTreFile(); | |
35 | TREFile getTreFile(); | |
36 | 36 | |
37 | public NETFile getNetFile(); | |
37 | NETFile getNetFile(); | |
38 | 38 | |
39 | public NODFile getNodFile(); | |
39 | NODFile getNodFile(); | |
40 | 40 | } |
74 | 74 | BitWriter bsBest = bsSimple; |
75 | 75 | int xBestBase = xBase; |
76 | 76 | int yBestBase = yBase; |
77 | if (xBase > 0 || yBase > 0){ | |
78 | if (log.isDebugEnabled()) | |
79 | log.debug("start opt:", xBase, yBase, xSameSign, xSignNegative, ySameSign, ySignNegative); | |
77 | if (xBase > 0 || yBase > 0 && log.isDebugEnabled()) { | |
78 | log.debug("start opt:", xBase, yBase, xSameSign, xSignNegative, ySameSign, ySignNegative); | |
80 | 79 | } |
81 | 80 | if (xBase > 0){ |
82 | 81 | int notBetter = 0; |
84 | 83 | xSameSign = false; |
85 | 84 | for (int xTestBase = xBase-1; xTestBase >= 0; xTestBase--){ |
86 | 85 | BitWriter bstest = makeBitStream(minPointsRequired, xTestBase, yBase); |
87 | // System.out.println(xBase + " " + xTestBase + " -> " + bsBest.getBitPosition() + " " + bstest.getBitPosition()); | |
88 | 86 | if (bstest.getBitPosition() >= bsBest.getBitPosition() ){ |
89 | 87 | if (++notBetter >= 2) |
90 | 88 | break; // give up |
102 | 100 | ySameSign = false; |
103 | 101 | for (int yTestBase = yBase-1; yTestBase >= 0; yTestBase--){ |
104 | 102 | BitWriter bstest = makeBitStream(minPointsRequired, xBestBase, yTestBase); |
105 | // System.out.println(yBase + " " + yTestBase + " -> " + bsBest.getBitPosition() + " " + bstest.getBitPosition()); | |
106 | 103 | if (bstest.getBitPosition() >= bsBest.getBitPosition()){ |
107 | 104 | if (++notBetter >= 2) |
108 | 105 | break; // give up |
114 | 111 | } |
115 | 112 | ySameSign = ySameSignBak; |
116 | 113 | } |
117 | if (xBase != xBestBase || yBestBase != yBase){ | |
118 | if (log.isInfoEnabled()){ | |
119 | if (bsSimple.getLength() > bsBest.getLength()) | |
120 | log.info("optimizer reduced bit stream byte length from",bsSimple.getLength(),"->",bsBest.getLength(),"(" + (bsSimple.getLength()-bsBest.getLength()), " byte(s)) for",polyline.getClass().getSimpleName(),"with",polyline.getPoints().size(),"points"); | |
121 | else | |
122 | log.info("optimizer only reduced bit stream bit length from",bsSimple.getBitPosition(),"->",bsBest.getBitPosition(),"bits for",polyline.getClass().getSimpleName(),"with",polyline.getPoints().size(),"points, using original bit stream"); | |
123 | } | |
114 | if (xBase != xBestBase || yBestBase != yBase && log.isInfoEnabled()) { | |
115 | if (bsSimple.getLength() > bsBest.getLength()) | |
116 | log.info("optimizer reduced bit stream byte length from",bsSimple.getLength(),"->",bsBest.getLength(),"(" + (bsSimple.getLength()-bsBest.getLength()), " byte(s)) for",polyline.getClass().getSimpleName(),"with",polyline.getPoints().size(),"points"); | |
117 | else | |
118 | log.info("optimizer only reduced bit stream bit length from",bsSimple.getBitPosition(),"->",bsBest.getBitPosition(),"bits for",polyline.getClass().getSimpleName(),"with",polyline.getPoints().size(),"points, using original bit stream"); | |
124 | 119 | } |
125 | 120 | if (bsSimple.getLength() == bsBest.getLength()){ |
126 | 121 | // if the (byte) length was not improved, |
184 | 179 | for (int i = 0; i < deltas.length; i+=2) { |
185 | 180 | int dx = deltas[i]; |
186 | 181 | int dy = deltas[i + 1]; |
187 | if (dx == 0 && dy == 0){ | |
188 | if (extraBit && nodes[i/2+1] == false && i+2 != deltas.length) // don't skip CoordNode | |
182 | if (dx == 0 && dy == 0) { | |
183 | if (extraBit && !nodes[i/2+1] && i+2 != deltas.length) // don't skip CoordNode | |
189 | 184 | continue; |
190 | 185 | } |
191 | 186 | ++numPointsEncoded; |
244 | 239 | |
245 | 240 | // Space to hold the deltas |
246 | 241 | int numPointsToUse = points.size(); |
247 | if (polyline instanceof Polygon){ | |
248 | if (points.get(0).equals(points.get(points.size()-1))) | |
249 | --numPointsToUse; // no need to write the closing point | |
242 | if (polyline instanceof Polygon && points.get(0).equals(points.get(points.size()-1))) { | |
243 | --numPointsToUse; // no need to write the closing point | |
250 | 244 | } |
251 | 245 | deltas = new int[2 * (numPointsToUse - 1)]; |
252 | 246 |
123 | 123 | |
124 | 124 | public Coord getLocation() { |
125 | 125 | int shift = getSubdiv().getShift(); |
126 | Coord co = new Coord(getSubdiv().getLatitude() + (getDeltaLat() << shift), | |
127 | getSubdiv().getLongitude() + (getDeltaLong() << shift) ); | |
128 | return co; | |
126 | return new Coord(getSubdiv().getLatitude() + (getDeltaLat() << shift), | |
127 | getSubdiv().getLongitude() + (getDeltaLong() << shift)); | |
129 | 128 | } |
130 | 129 | } |
61 | 61 | private List<Label> refLabels; |
62 | 62 | |
63 | 63 | // The actual points that make up the line. |
64 | private final List<Coord> points = new ArrayList<Coord>(); | |
64 | private final List<Coord> points = new ArrayList<>(); | |
65 | 65 | |
66 | 66 | public Polyline(Subdivision div) { |
67 | 67 | setSubdiv(div); |
81 | 81 | try { |
82 | 82 | // Prepare the information that we need. |
83 | 83 | w = new LinePreparer(this); |
84 | } | |
85 | catch (AssertionError ae) { | |
84 | } catch (AssertionError ae) { | |
86 | 85 | log.error("Problem writing line (" + getClass() + ") of type 0x" + Integer.toHexString(getType()) + " containing " + points.size() + " points and starting at " + points.get(0).toOSMURL()); |
87 | 86 | log.error(" Subdivision shift is " + getSubdiv().getShift() + |
88 | 87 | " and its centre is at " + getSubdiv().getCenter().toOSMURL()); |
94 | 93 | |
95 | 94 | int minPointsRequired = (this instanceof Polygon)? 3 : 2; |
96 | 95 | BitWriter bw = w.makeShortestBitStream(minPointsRequired); |
97 | if(bw == null) { | |
96 | if (bw == null) { | |
98 | 97 | log.error("Level " + getSubdiv().getZoom().getLevel() + " " + ((this instanceof Polygon)? "polygon" : "polyline") + " has less than " + minPointsRequired + " points, discarding"); |
99 | 98 | return; |
100 | 99 | } |
125 | 124 | FLAG_NETINFO | (loff & FLAG_EXTRABIT)); |
126 | 125 | // also add ref label(s) if present |
127 | 126 | if(refLabels != null) |
128 | for(Label rl : refLabels) | |
129 | roaddef.addLabel(rl); | |
127 | refLabels.forEach(roaddef::addLabel); | |
130 | 128 | } |
131 | 129 | |
132 | 130 | file.put3u(loff); |
311 | 309 | } |
312 | 310 | |
313 | 311 | public void addRefLabel(Label refLabel) { |
314 | if(refLabels == null) | |
315 | refLabels = new ArrayList<Label>(); | |
312 | if (refLabels == null) | |
313 | refLabels = new ArrayList<>(); | |
316 | 314 | refLabels.add(refLabel); |
317 | 315 | } |
318 | 316 |
185 | 185 | } |
186 | 186 | } |
187 | 187 | |
188 | @Override | |
188 | 189 | public ImgFileWriter getWriter() { |
189 | 190 | return super.getWriter(); |
190 | 191 | } |
71 | 71 | * @return A list of all points for the subdiv. |
72 | 72 | */ |
73 | 73 | public List<Point> pointsForSubdiv(Subdivision sd, boolean withExtType) { |
74 | ArrayList<Point> list = new ArrayList<Point>(); | |
74 | ArrayList<Point> list = new ArrayList<>(); | |
75 | 75 | if (sd.hasIndPoints() || sd.hasPoints()){ |
76 | 76 | |
77 | 77 | RgnOffsets rgnOffsets = getOffsets(sd); |
111 | 111 | if (record != null) { |
112 | 112 | l = record.getNameLabel(); |
113 | 113 | p.setPOIRecord(record); |
114 | } else | |
114 | } else { | |
115 | 115 | l = lblFile.fetchLabel(0); |
116 | } | |
116 | 117 | } else { |
117 | 118 | l = lblFile.fetchLabel(labelOffset); |
118 | 119 | } |
160 | 161 | if (record != null) { |
161 | 162 | l = record.getNameLabel(); |
162 | 163 | p.setPOIRecord(record); |
163 | } else | |
164 | } else { | |
164 | 165 | l = lblFile.fetchLabel(0); |
166 | } | |
165 | 167 | } else { |
166 | 168 | l = lblFile.fetchLabel(labelOffset); |
167 | 169 | } |
182 | 184 | * @return A list of lines. |
183 | 185 | */ |
184 | 186 | public List<Polyline> linesForSubdiv(Subdivision div) { |
185 | ArrayList<Polyline> list = new ArrayList<Polyline>(); | |
187 | ArrayList<Polyline> list = new ArrayList<>(); | |
186 | 188 | |
187 | 189 | if (div.hasPolylines()){ |
188 | 190 | RgnOffsets rgnOffsets = getOffsets(div); |
215 | 217 | * @param witExtTypeData |
216 | 218 | */ |
217 | 219 | public List<Polygon> shapesForSubdiv(Subdivision div, boolean witExtTypeData) { |
218 | ArrayList<Polygon> list = new ArrayList<Polygon>(); | |
220 | ArrayList<Polygon> list = new ArrayList<>(); | |
219 | 221 | if (div.hasPolygons()){ |
220 | 222 | |
221 | 223 | RgnOffsets rgnOffsets = getOffsets(div); |
232 | 234 | line.setNumber(list.size()); |
233 | 235 | } |
234 | 236 | } |
235 | if (witExtTypeData) { | |
236 | if (div.getExtTypeAreasSize() > 0){ | |
237 | int start = rgnHeader.getExtTypeAreasOffset() + div.getExtTypeAreasOffset(); | |
238 | int end = start + div.getExtTypeAreasSize(); | |
239 | position(start); | |
240 | while (position() < end) { | |
241 | Polygon line = new Polygon(div); | |
242 | readLineCommonExtType(getReader(), div, line); | |
243 | list.add(line); | |
244 | } | |
237 | if (witExtTypeData && div.getExtTypeAreasSize() > 0) { | |
238 | int start = rgnHeader.getExtTypeAreasOffset() + div.getExtTypeAreasOffset(); | |
239 | int end = start + div.getExtTypeAreasSize(); | |
240 | position(start); | |
241 | while (position() < end) { | |
242 | Polygon line = new Polygon(div); | |
243 | readLineCommonExtType(getReader(), div, line); | |
244 | list.add(line); | |
245 | 245 | } |
246 | 246 | } |
247 | 247 | return list; |
358 | 358 | void extractExtraBytes(ImgFileReader reader, MapObject o){ |
359 | 359 | long pos = reader.position(); |
360 | 360 | StringBuilder sb = new StringBuilder(); |
361 | ArrayList<Byte> bytes = new ArrayList<Byte>(); | |
361 | ArrayList<Byte> bytes = new ArrayList<>(); | |
362 | 362 | byte b1 = reader.get(); |
363 | 363 | bytes.add(b1); |
364 | 364 | if ((b1 & 0xe0) != 0){ |
422 | 422 | boolean xsame = br.get1(); |
423 | 423 | if (xsame) { |
424 | 424 | xneg = br.get1(); |
425 | } else | |
425 | } else { | |
426 | 426 | xbase++; |
427 | } | |
427 | 428 | |
428 | 429 | boolean ysame = br.get1(); |
429 | 430 | boolean yneg = false; |
430 | 431 | if (ysame) { |
431 | 432 | yneg = br.get1(); |
432 | } else | |
433 | } else { | |
433 | 434 | ybase++; |
435 | } | |
434 | 436 | |
435 | 437 | if(line.hasExtendedType()) { |
436 | 438 | br.get1(); |
45 | 45 | private static final Logger log = Logger.getLogger(TREFile.class); |
46 | 46 | |
47 | 47 | // Zoom levels for map |
48 | // private List<Zoom> mapLevels = new ArrayList<Zoom>(); | |
49 | 48 | private final Zoom[] mapLevels = new Zoom[16]; |
50 | 49 | |
51 | 50 | private final List<Label> copyrights = new ArrayList<>(); |
52 | 51 | |
53 | 52 | // Information about polylines. eg roads etc. |
54 | private final List<PolylineOverview> polylineOverviews = new ArrayList<PolylineOverview>(); | |
55 | ||
56 | private final List<PolygonOverview> polygonOverviews = new ArrayList<PolygonOverview>(); | |
57 | ||
58 | private final List<PointOverview> pointOverviews = new ArrayList<PointOverview>(); | |
53 | private final List<PolylineOverview> polylineOverviews = new ArrayList<>(); | |
54 | ||
55 | private final List<PolygonOverview> polygonOverviews = new ArrayList<>(); | |
56 | ||
57 | private final List<PointOverview> pointOverviews = new ArrayList<>(); | |
59 | 58 | |
60 | 59 | private int lastRgnPos; |
61 | 60 |
170 | 170 | int levelsSize = header.getMapLevelsSize(); |
171 | 171 | reader.position(levelsPos); |
172 | 172 | |
173 | List<Subdivision[]> levelDivs = new ArrayList<Subdivision[]>(); | |
174 | List<Zoom> mapLevels = new ArrayList<Zoom>(); | |
173 | List<Subdivision[]> levelDivsList = new ArrayList<>(); | |
174 | List<Zoom> mapLevelsList = new ArrayList<>(); | |
175 | 175 | int end = levelsPos + levelsSize; |
176 | 176 | while (reader.position() < end) { |
177 | 177 | int level = reader.get1u(); |
179 | 179 | int ndivs = reader.get2u(); |
180 | 180 | |
181 | 181 | Subdivision[] divs = new Subdivision[ndivs]; |
182 | levelDivs.add(divs); | |
182 | levelDivsList.add(divs); | |
183 | 183 | level &= 0x7f; |
184 | 184 | |
185 | 185 | Zoom z = new Zoom(level, nbits); |
186 | mapLevels.add(z); | |
187 | } | |
188 | ||
189 | this.levelDivs = levelDivs.toArray(new Subdivision[levelDivs.size()][]); | |
190 | this.mapLevels = mapLevels.toArray(new Zoom[mapLevels.size()]); | |
186 | mapLevelsList.add(z); | |
187 | } | |
188 | ||
189 | this.levelDivs = levelDivsList.toArray(new Subdivision[levelDivsList.size()][]); | |
190 | this.mapLevels = mapLevelsList.toArray(new Zoom[mapLevelsList.size()]); | |
191 | 191 | } |
192 | 192 | |
193 | 193 | public void config(EnhancedProperties props) { |
39 | 39 | |
40 | 40 | private final int resolution; |
41 | 41 | |
42 | private final List<Subdivision> subdivs = new ArrayList<Subdivision>(); | |
42 | private final List<Subdivision> subdivs = new ArrayList<>(); | |
43 | 43 | |
44 | 44 | /** |
45 | 45 | * Create a new zoom level. |
38 | 38 | |
39 | 39 | private boolean hasBitmap; |
40 | 40 | private boolean hasBorder; |
41 | private final List<RgbWithTag> colours = new ArrayList<RgbWithTag>(); | |
42 | private final Map<String, Integer> indexMap = new HashMap<String, Integer>(); | |
41 | private final List<RgbWithTag> colours = new ArrayList<>(); | |
42 | private final Map<String, Integer> indexMap = new HashMap<>(); | |
43 | 43 | |
44 | 44 | private char width; |
45 | 45 | private char height; |
29 | 29 | * @author Steve Ratcliffe |
30 | 30 | */ |
31 | 31 | public class ShapeStacking { |
32 | private final SortedMap<Integer, DrawOrder> bar = new TreeMap<Integer, DrawOrder>(); | |
32 | private final SortedMap<Integer, DrawOrder> bar = new TreeMap<>(); | |
33 | 33 | |
34 | 34 | public void addPolygon(int level, int type, int subtype) { |
35 | 35 | int levelType = (level << 16) + type; |
36 | DrawOrder order = bar.get(levelType); | |
37 | if (order == null) { | |
38 | order = new DrawOrder(type); | |
39 | bar.put(levelType, order); | |
40 | } | |
41 | ||
42 | order.addSubtype(subtype); | |
36 | bar.computeIfAbsent(levelType, k -> new DrawOrder(type)).addSubtype(subtype); | |
43 | 37 | } |
44 | 38 | |
45 | 39 | public void write(ImgFileWriter writer) { |
49 | 49 | |
50 | 50 | private TypData data; |
51 | 51 | |
52 | private final Map<Integer, Integer> strToType = new TreeMap<Integer, Integer>(); | |
53 | private final Map<Integer, Integer> typeToStr = new TreeMap<Integer, Integer>(); | |
52 | private final Map<Integer, Integer> strToType = new TreeMap<>(); | |
53 | private final Map<Integer, Integer> typeToStr = new TreeMap<>(); | |
54 | 54 | |
55 | 55 | public TYPFile(ImgChannel chan) { |
56 | 56 | setHeader(header); |
90 | 90 | |
91 | 91 | SectionWriter writer = header.getLabels().makeSectionWriter(in); |
92 | 92 | |
93 | List<SortKey<TypIconSet>> keys = new ArrayList<SortKey<TypIconSet>>(); | |
93 | List<SortKey<TypIconSet>> keys = new ArrayList<>(); | |
94 | 94 | Sort sort = data.getSort(); |
95 | 95 | for (TypIconSet icon : data.getIcons()) { |
96 | 96 | String label = icon.getLabel(); |
185 | 185 | zapZero(dataSection, indexSection); |
186 | 186 | } |
187 | 187 | |
188 | private void zapZero(Section... sect) { | |
188 | private static void zapZero(Section... sect) { | |
189 | 189 | for (Section s : sect) { |
190 | 190 | if (s.getSize() == 0) { |
191 | 191 | s.setPosition(0); |
28 | 28 | |
29 | 29 | private final ShapeStacking stacking = new ShapeStacking(); |
30 | 30 | private final TypParam param = new TypParam(); |
31 | private final List<TypPolygon> polygons = new ArrayList<TypPolygon>(); | |
32 | private final List<TypLine> lines = new ArrayList<TypLine>(); | |
33 | private final List<TypPoint> points = new ArrayList<TypPoint>(); | |
34 | private final List<TypIconSet> icons = new ArrayList<TypIconSet>(); | |
31 | private final List<TypPolygon> polygons = new ArrayList<>(); | |
32 | private final List<TypLine> lines = new ArrayList<>(); | |
33 | private final List<TypPoint> points = new ArrayList<>(); | |
34 | private final List<TypIconSet> icons = new ArrayList<>(); | |
35 | 35 | |
36 | 36 | private Sort sort; |
37 | 37 | private CharsetEncoder encoder; |
35 | 35 | private int type; |
36 | 36 | private int subType; |
37 | 37 | |
38 | protected final List<TypLabel> labels = new ArrayList<TypLabel>(); | |
38 | protected final List<TypLabel> labels = new ArrayList<>(); | |
39 | 39 | |
40 | 40 | protected Xpm xpm; |
41 | 41 |
24 | 24 | * @author Steve Ratcliffe |
25 | 25 | */ |
26 | 26 | public class TypIconSet extends TypElement { |
27 | private final List<Xpm> icons = new ArrayList<Xpm>(); | |
27 | private final List<Xpm> icons = new ArrayList<>(); | |
28 | 28 | |
29 | 29 | public void write(ImgFileWriter writer, CharsetEncoder encoder) { |
30 | 30 | offset = writer.position(); |
43 | 43 | } |
44 | 44 | } |
45 | 45 | |
46 | private int calcBits(ColourInfo colourInfo) { | |
46 | private static int calcBits(ColourInfo colourInfo) { | |
47 | 47 | int bits = 0; |
48 | 48 | int bpp = colourInfo.getBitsPerPixel(); |
49 | 49 | |
68 | 68 | /** |
69 | 69 | * Icon sets can have full colour pixmaps. |
70 | 70 | */ |
71 | @Override | |
71 | 72 | public boolean simpleBitmap() { |
72 | 73 | return false; |
73 | 74 | } |
28 | 28 | private final ImgFileReader reader; |
29 | 29 | private int numberOfMaps; |
30 | 30 | |
31 | private final List<MapInfo> maps = new ArrayList<MapInfo>(); | |
31 | private final List<MapInfo> maps = new ArrayList<>(); | |
32 | 32 | |
33 | 33 | public MdxFileReader(ImgChannel chan) { |
34 | 34 | this.reader = new BufferedImgFileReader(chan); |
50 | 50 | private char[] currTable; |
51 | 51 | |
52 | 52 | BlockTable() { |
53 | blocks = new ArrayList<char[]>(200); | |
53 | blocks = new ArrayList<>(200); | |
54 | 54 | } |
55 | 55 | |
56 | 56 | /** |
67 | 67 | } |
68 | 68 | |
69 | 69 | private byte[] writeToCommon(int codePage) throws IOException { |
70 | StructuredOutputStream output = getStructuredOutput(codePage); | |
71 | writeBody(output); | |
70 | writeBody(getStructuredOutput(codePage)); | |
72 | 71 | |
73 | 72 | byte[] b = this.output.toByteArray(); |
74 | 73 |
35 | 35 | public class Logger { |
36 | 36 | private final java.util.logging.Logger log; |
37 | 37 | |
38 | private static final ThreadLocal<String> threadTags = new ThreadLocal<String>(); | |
38 | private static final ThreadLocal<String> threadTags = new ThreadLocal<>(); | |
39 | 39 | |
40 | 40 | static { |
41 | 41 | initLogging(); |
241 | 241 | log.log(type, tagMessage(msg)); |
242 | 242 | } |
243 | 243 | |
244 | private String tagMessage(String message) { | |
244 | private static String tagMessage(String message) { | |
245 | 245 | String threadTag = threadTags.get(); |
246 | 246 | return (threadTag != null) ? threadTag + ": " + message : message; |
247 | 247 | } |
0 | 0 | package uk.me.parabola.mkgmap; |
1 | 1 | |
2 | 2 | import java.io.File; |
3 | import java.util.Arrays; | |
4 | import java.util.Collections; | |
5 | import java.util.List; | |
6 | import java.util.Properties; | |
7 | import java.util.Set; | |
8 | import java.util.TreeSet; | |
9 | import java.util.regex.Pattern; | |
3 | 10 | |
11 | import uk.me.parabola.imgfmt.ExitException; | |
4 | 12 | import uk.me.parabola.imgfmt.app.srt.Sort; |
5 | 13 | import uk.me.parabola.util.EnhancedProperties; |
6 | 14 | |
13 | 21 | |
14 | 22 | public CommandArgs(EnhancedProperties args) { |
15 | 23 | currentOptions = new EnhancedProperties(args); |
24 | // verify options which expect a comma-separated list | |
25 | for (String listOpt : Arrays.asList("mdr7-excl", "mdr7-del", "poi-excl-index", "location-autofill", | |
26 | "overview-levels", "levels", "name-tag-list", "polygon-size-limits", "dem", "dem-dists", "drive-on", | |
27 | "dead-ends", "add-pois-to-lines", "coastlinefile", "generate-sea", "nearby-poi-rules")) { | |
28 | stringToList(get(listOpt, null), listOpt); | |
29 | } | |
16 | 30 | } |
17 | 31 | |
18 | 32 | public EnhancedProperties getProperties() { |
76 | 90 | } |
77 | 91 | |
78 | 92 | public String getOutputDir() { |
79 | String DEFAULT_DIR = "."; | |
93 | final String DEFAULT_DIR = "."; | |
80 | 94 | String fileOutputDir = currentOptions.getProperty("output-dir", DEFAULT_DIR); |
81 | 95 | |
82 | 96 | // Test if directory exists |
115 | 129 | public boolean exists(String name) { |
116 | 130 | return currentOptions.containsKey(name); |
117 | 131 | } |
132 | ||
133 | public static List<String> getNameTags(Properties props) { | |
134 | String s = props.getProperty("name-tag-list", "name"); | |
135 | return stringToList(s, "name-tag-list"); | |
136 | } | |
137 | ||
138 | public Set<String> argToSet(String name, String def) { | |
139 | String optVal = currentOptions.getProperty(name, def); | |
140 | return stringToSet(optVal, name); | |
141 | } | |
142 | ||
143 | public List<String> argToList(String name, String def) { | |
144 | String optVal = currentOptions.getProperty(name, def); | |
145 | return stringToList(optVal, name); | |
146 | } | |
147 | ||
148 | public static Set<String> stringToSet(String opt, String optName) { | |
149 | List<String> list = stringToList(opt, optName); | |
150 | if (list.isEmpty()) | |
151 | return Collections.emptySet(); | |
152 | TreeSet<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); | |
153 | set.addAll(list); | |
154 | return set; | |
155 | } | |
156 | ||
157 | private static final Pattern COMMA_OR_SPACE_PATTERN = Pattern.compile("[,\\s]+"); | |
158 | ||
159 | public static List<String> stringToList(String opt, String optName) { | |
160 | if (opt == null) | |
161 | return Collections.emptyList(); | |
162 | if (opt.startsWith("'") || opt.startsWith("\"")) | |
163 | opt = opt.substring(1); | |
164 | if (opt.endsWith("'") || opt.endsWith("\"")) | |
165 | opt = opt.substring(0, opt.length() - 1); | |
166 | if (opt.endsWith(",")) { | |
167 | throw new ExitException("Option " + optName + " ends in a comma and hence has a missing value"); | |
168 | } | |
169 | return Arrays.asList(COMMA_OR_SPACE_PATTERN.split(opt)); | |
170 | } | |
171 | ||
118 | 172 | } |
90 | 90 | // This is a long style 'property' format option. |
91 | 91 | addOption(arg.substring(2)); |
92 | 92 | |
93 | } else if (arg.equals("-c")) { | |
93 | } else if ("-c".equals(arg)) { | |
94 | 94 | // Config file |
95 | 95 | readConfigFile(args[i++]); |
96 | 96 | |
97 | } else if (arg.equals("-n")) { | |
97 | } else if ("-n".equals(arg)) { | |
98 | 98 | // Map name (should be an 8 digit number). |
99 | 99 | addOption("mapname", args[i++]); |
100 | 100 | |
101 | } else if (arg.equals("-v")) { | |
101 | } else if ("-v".equals(arg)) { | |
102 | 102 | // make commands more verbose |
103 | 103 | addOption("verbose"); |
104 | 104 | |
255 | 255 | * filenames. The options take effect where they appear. |
256 | 256 | */ |
257 | 257 | interface ArgType { |
258 | public abstract void processArg(); | |
258 | void processArg(); | |
259 | 259 | } |
260 | 260 | |
261 | 261 | /** |
315 | 315 | |
316 | 316 | } |
317 | 317 | |
318 | private String extractMapName(String path) { | |
318 | private static String extractMapName(String path) { | |
319 | 319 | File file = new File(path); |
320 | 320 | String fname = file.getName(); |
321 | 321 | Pattern pat = Pattern.compile("([0-9]{8})"); |
45 | 45 | private final OptionProcessor proc; |
46 | 46 | |
47 | 47 | // Used to prevent the same file being read more than once. |
48 | private final Collection<String> readFiles = new HashSet<String>(); | |
48 | private final Collection<String> readFiles = new HashSet<>(); | |
49 | 49 | |
50 | 50 | public Options(OptionProcessor proc) { |
51 | 51 | this.proc = proc; |
85 | 85 | public void readOptionFile(Reader r, String filename) { |
86 | 86 | BufferedReader br; |
87 | 87 | if (r instanceof BufferedReader) |
88 | br = (BufferedReader)r; | |
88 | br = (BufferedReader) r; | |
89 | 89 | else |
90 | 90 | br = new BufferedReader(r); |
91 | 91 | TokenScanner ts = new TokenScanner(filename, br); |
112 | 112 | |
113 | 113 | String punc = ts.nextValue(); |
114 | 114 | String val; |
115 | if (punc.equals(":") || punc.equals("=")) { | |
115 | if (":".equals(punc) || "=".equals(punc)) { | |
116 | 116 | val = ts.readLine(); |
117 | } else if (punc.equals("{")) { | |
117 | } else if ("{".equals(punc)) { | |
118 | 118 | ts.skipSpace(); |
119 | 119 | val = ts.readUntil(TokType.SYMBOL, "}"); |
120 | 120 | ts.nextToken(); // discard the closing brace |
125 | 125 | |
126 | 126 | // Relative file names in the file are taken relative to the |
127 | 127 | // location of the argument file. |
128 | if (key.equals("input-file") && !new File(val).isAbsolute()) | |
128 | if ("input-file".equals(key) && !new File(val).isAbsolute()) | |
129 | 129 | val = new File(parent, val).getPath(); |
130 | 130 | |
131 | 131 | proc.processOption(new Option(key, val)); |
34 | 34 | private static final Logger log = Logger.getLogger(LayerFilterChain.class); |
35 | 35 | |
36 | 36 | // The filters that will be applied to the element. |
37 | private List<MapFilter> filters = new ArrayList<MapFilter>(); | |
37 | private List<MapFilter> filters = new ArrayList<>(); | |
38 | 38 | |
39 | 39 | // The position in the filter list. |
40 | 40 | private int position; |
31 | 31 | private static final Logger log = Logger.getLogger(Locator.class); |
32 | 32 | |
33 | 33 | /** hash map to collect equally named MapPoints*/ |
34 | private final MultiHashMap<String, MapPoint> cityMap = new MultiHashMap<String, MapPoint>(); | |
34 | private final MultiHashMap<String, MapPoint> cityMap = new MultiHashMap<>(); | |
35 | 35 | |
36 | 36 | private final KdTree<MapPoint> cityFinder = new KdTree<>(); |
37 | private final List<MapPoint> placesMap = new ArrayList<MapPoint>(); | |
37 | private final List<MapPoint> placesMap = new ArrayList<>(); | |
38 | 38 | |
39 | 39 | private final NameFinder nameFinder; |
40 | 40 | |
50 | 50 | |
51 | 51 | public Locator(EnhancedProperties props) { |
52 | 52 | this.nameFinder = new NameFinder(props); |
53 | this.locationAutofill = new HashSet<String>(LocatorUtil.parseAutofillOption(props)); | |
53 | this.locationAutofill = new HashSet<>(LocatorUtil.parseAutofillOption(props)); | |
54 | 54 | } |
55 | 55 | |
56 | 56 | public void addCityOrPlace(MapPoint p) |
57 | 57 | { |
58 | if (p.isCity() == false) | |
59 | { | |
60 | log.warn("MapPoint has no city type id: 0x"+Integer.toHexString(p.getType())); | |
58 | if (!p.isCity()) { | |
59 | log.warn("MapPoint has no city type id: 0x" + Integer.toHexString(p.getType())); | |
61 | 60 | return; |
62 | 61 | } |
63 | 62 | |
132 | 131 | } |
133 | 132 | } |
134 | 133 | |
135 | public final static ShortArrayList PREFERRED_NAME_TAG_KEYS = TagDict.compileTags("name","name:en","int_name"); | |
134 | public static final ShortArrayList PREFERRED_NAME_TAG_KEYS = TagDict.compileTags("name","name:en","int_name"); | |
136 | 135 | |
137 | 136 | public String getCountryISOCode(Tags tags) { |
138 | 137 | for (short nameTagKey : PREFERRED_NAME_TAG_KEYS) { |
173 | 172 | */ |
174 | 173 | private void resolveIsInInfo(MapPoint p) |
175 | 174 | { |
176 | if (locationAutofill.contains("is_in") == false) { | |
175 | if (!locationAutofill.contains("is_in")) { | |
177 | 176 | return; |
178 | 177 | } |
179 | 178 | |
281 | 280 | |
282 | 281 | private MapPoint findCityByIsIn(MapPoint place) { |
283 | 282 | |
284 | if (locationAutofill.contains("is_in") == false) { | |
283 | if (!locationAutofill.contains("is_in")) { | |
285 | 284 | return null; |
286 | 285 | } |
287 | 286 | |
302 | 301 | cityCandidate = cityCandidate.trim(); |
303 | 302 | |
304 | 303 | Collection<MapPoint> candidateCityList = cityMap.get(cityCandidate); |
305 | if (candidateCityList.isEmpty() == false) { | |
304 | if (!candidateCityList.isEmpty()) { | |
306 | 305 | if (nextCityList == null) { |
307 | nextCityList = new ArrayList<MapPoint>(candidateCityList.size()); | |
306 | nextCityList = new ArrayList<>(candidateCityList.size()); | |
308 | 307 | } |
309 | 308 | nextCityList.addAll(candidateCityList); |
310 | 309 | } |
338 | 337 | } |
339 | 338 | |
340 | 339 | public void autofillCities() { |
341 | if (locationAutofill.contains("nearest") == false && locationAutofill.contains("is_in") == false) { | |
340 | if (!locationAutofill.contains("nearest") && !locationAutofill.contains("is_in")) { | |
342 | 341 | return; |
343 | 342 | } |
344 | 343 |
45 | 45 | private final Set<String> continents = new HashSet<>(); |
46 | 46 | |
47 | 47 | /** maps ISO => default country name */ |
48 | private final Map<String, String> defaultCountryNames = new HashMap<String, String>(); | |
48 | private final Map<String, String> defaultCountryNames = new HashMap<>(); | |
49 | 49 | |
50 | 50 | /** Maps 3 letter ISO code to all tags of a country */ |
51 | private final Map<String, Tags> countryTagMap = new HashMap<String, Tags>(); | |
52 | ||
53 | private final static LocatorConfig instance = new LocatorConfig(); | |
51 | private final Map<String, Tags> countryTagMap = new HashMap<>(); | |
52 | ||
53 | private static final LocatorConfig instance = new LocatorConfig(); | |
54 | 54 | |
55 | 55 | public static LocatorConfig get() { |
56 | 56 | return instance; |
85 | 85 | |
86 | 86 | Node rootNode = document.getDocumentElement(); |
87 | 87 | |
88 | if(rootNode.getNodeName().equals("locator")) | |
88 | if("locator".equals(rootNode.getNodeName())) | |
89 | 89 | { |
90 | 90 | Node cNode = rootNode.getFirstChild(); |
91 | 91 | |
92 | 92 | while(cNode != null) |
93 | 93 | { |
94 | if(cNode.getNodeName().equals("continent")) | |
94 | if("continent".equals(cNode.getNodeName())) | |
95 | 95 | { |
96 | 96 | NamedNodeMap attr = cNode.getAttributes(); |
97 | 97 | |
104 | 104 | |
105 | 105 | } |
106 | 106 | |
107 | if (cNode.getNodeName().equals("country")) { | |
107 | if ("country".equals(cNode.getNodeName())) { | |
108 | 108 | NamedNodeMap attr = cNode.getAttributes(); |
109 | 109 | String iso = null; |
110 | 110 | if (attr != null) { |
162 | 162 | Node cEntryNode = cNode.getFirstChild(); |
163 | 163 | while(cEntryNode != null) |
164 | 164 | { |
165 | if(cEntryNode.getNodeName().equals("variant")) | |
165 | if("variant".equals(cEntryNode.getNodeName())) | |
166 | 166 | { |
167 | 167 | Node nodeText = cEntryNode.getFirstChild(); |
168 | 168 |
13 | 13 | |
14 | 14 | import java.util.Arrays; |
15 | 15 | import java.util.Collections; |
16 | import java.util.HashSet; | |
17 | 16 | import java.util.List; |
18 | 17 | import java.util.Set; |
19 | import java.util.regex.Pattern; | |
20 | 18 | |
19 | import uk.me.parabola.mkgmap.CommandArgs; | |
21 | 20 | import uk.me.parabola.util.EnhancedProperties; |
22 | 21 | |
23 | 22 | public class LocatorUtil { |
24 | ||
25 | private static final Pattern COMMA_OR_SPACE_PATTERN = Pattern.compile("[,\\s]+"); | |
23 | private LocatorUtil () { | |
24 | // private constructor to hide the implicit one | |
25 | } | |
26 | 26 | |
27 | 27 | /** |
28 | 28 | * Parses the parameters of the location-autofill option. Establishes also downwards |
31 | 31 | * @return the options |
32 | 32 | */ |
33 | 33 | public static Set<String> parseAutofillOption(EnhancedProperties props) { |
34 | String optionStr = props.getProperty("location-autofill", null); | |
34 | final String optName = "location-autofill"; | |
35 | final String IS_IN = "is_in"; | |
36 | final String NEAREST = "nearest"; | |
37 | String optionStr = props.getProperty(optName, null); | |
35 | 38 | if (optionStr == null) { |
36 | 39 | return Collections.emptySet(); |
37 | 40 | } |
38 | ||
39 | Set<String> autofillOptions = new HashSet<String>(Arrays.asList(COMMA_OR_SPACE_PATTERN | |
40 | .split(optionStr))); | |
41 | Set<String> autofillOptions = CommandArgs.stringToSet(optionStr, optName); | |
41 | 42 | |
42 | 43 | // convert the old autofill options to the new parameters |
43 | 44 | if (autofillOptions.contains("0")) { |
44 | autofillOptions.add("is_in"); | |
45 | autofillOptions.add(IS_IN); | |
45 | 46 | autofillOptions.remove("0"); |
46 | 47 | } |
47 | 48 | if (autofillOptions.contains("1")) { |
48 | autofillOptions.add("is_in"); | |
49 | // PENDING: fuzzy search | |
49 | autofillOptions.add(IS_IN); | |
50 | 50 | autofillOptions.remove("1"); |
51 | 51 | } |
52 | 52 | if (autofillOptions.contains("2")) { |
53 | autofillOptions.add("is_in"); | |
53 | autofillOptions.add(IS_IN); | |
54 | 54 | // PENDING: fuzzy search |
55 | autofillOptions.add("nearest"); | |
55 | autofillOptions.add(NEAREST); | |
56 | 56 | autofillOptions.remove("2"); |
57 | 57 | } |
58 | 58 | if (autofillOptions.contains("3")) { |
59 | autofillOptions.add("is_in"); | |
59 | autofillOptions.add(IS_IN); | |
60 | 60 | // PENDING: fuzzy search |
61 | autofillOptions.add("nearest"); | |
61 | autofillOptions.add(NEAREST); | |
62 | 62 | autofillOptions.remove("3"); |
63 | 63 | } |
64 | final List<String> knownOptions = Arrays.asList("bounds","is_in","nearest"); | |
64 | final List<String> knownOptions = Arrays.asList("bounds", IS_IN, NEAREST); | |
65 | 65 | for (String s : autofillOptions){ |
66 | if (knownOptions.contains(s) == false){ | |
66 | if (!knownOptions.contains(s)) { | |
67 | 67 | throw new IllegalArgumentException(s + " is not a known sub option for option location-autofill: " + optionStr); |
68 | 68 | } |
69 | 69 | } |
30 | 30 | import java.util.Set; |
31 | 31 | import java.util.TreeMap; |
32 | 32 | import java.util.function.UnaryOperator; |
33 | import java.util.stream.Collectors; | |
33 | 34 | |
34 | 35 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; |
35 | 36 | import uk.me.parabola.imgfmt.ExitException; |
70 | 71 | import uk.me.parabola.imgfmt.app.trergn.TREHeader; |
71 | 72 | import uk.me.parabola.imgfmt.app.trergn.Zoom; |
72 | 73 | import uk.me.parabola.log.Logger; |
74 | import uk.me.parabola.mkgmap.CommandArgs; | |
73 | 75 | import uk.me.parabola.mkgmap.Version; |
74 | 76 | import uk.me.parabola.mkgmap.combiners.OverviewBuilder; |
75 | 77 | import uk.me.parabola.mkgmap.filters.BaseFilter; |
165 | 167 | private String licenseFileName; |
166 | 168 | |
167 | 169 | private boolean orderByDecreasingArea; |
168 | private String pathToHGT; | |
170 | private String pathsToHGT; | |
169 | 171 | private List<Integer> demDists; |
170 | 172 | private short demOutsidePolygonHeight; |
171 | 173 | private java.awt.geom.Area demPolygon; |
214 | 216 | if ("right".equals(driveOn)) |
215 | 217 | driveOnLeft = false; |
216 | 218 | orderByDecreasingArea = props.getProperty("order-by-decreasing-area", false); |
217 | pathToHGT = props.getProperty("dem", null); | |
219 | pathsToHGT = props.getProperty("dem", null); | |
218 | 220 | demDists = parseDemDists(props.getProperty("dem-dists", "-1")); |
219 | 221 | demOutsidePolygonHeight = (short) props.getProperty("dem-outside-polygon", HGTReader.UNDEF); |
220 | 222 | String demPolygonFile = props.getProperty("dem-poly", null); |
239 | 241 | } |
240 | 242 | |
241 | 243 | private static List<Integer> parseDemDists(String demDists) { |
242 | List<Integer> dists = new ArrayList<>(); | |
243 | if (demDists == null) | |
244 | dists.add(-1); | |
245 | else { | |
246 | String[] vals = demDists.split(","); | |
247 | for( String val : vals) { | |
248 | int dist = Integer.parseInt(val.trim()); | |
249 | dists.add(dist); | |
250 | } | |
251 | } | |
244 | List<Integer> dists = CommandArgs.stringToList(demDists, "dem-dists") | |
245 | .stream().map(Integer::parseInt).collect(Collectors.toList()); | |
246 | if (dists.isEmpty()) | |
247 | return Arrays.asList(-1); | |
252 | 248 | return dists; |
253 | 249 | } |
254 | 250 | |
348 | 344 | demArea = new java.awt.geom.Area(demPoly); |
349 | 345 | } |
350 | 346 | } |
351 | Area treArea = demFile.calc(src.getBounds(), demArea, pathToHGT, demDists, demOutsidePolygonHeight, demInterpolationMethod); | |
347 | Area treArea = demFile.calc(src.getBounds(), demArea, pathsToHGT, demDists, demOutsidePolygonHeight, demInterpolationMethod); | |
352 | 348 | map.setBounds(treArea); |
353 | 349 | long t2 = System.currentTimeMillis(); |
354 | 350 | log.info("DEM file calculation for", map.getFilename(), "took", (t2 - t1), "ms"); |
664 | 660 | |
665 | 661 | private void processExit(Map map, MapExitPoint mep) { |
666 | 662 | LBLFile lbl = map.getLblFile(); |
663 | String exitName = mep.getName(); | |
667 | 664 | String ref = mep.getMotorwayRef(); |
668 | 665 | String osmId = mep.getOSMId(); |
669 | if(ref != null) { | |
666 | if (ref == null) | |
667 | log.warn("Can't create exit", exitName, "(OSM id", osmId, ") doesn't have exit:road_ref tag"); | |
668 | else { | |
670 | 669 | Highway hw = highways.get(ref); |
671 | if(hw == null) | |
672 | hw = makeHighway(map, ref); | |
673 | if(hw == null) { | |
674 | log.warn("Can't create exit", mep.getName(), "(OSM id", osmId, ") on unknown highway", ref); | |
675 | return; | |
676 | } | |
677 | String exitName = mep.getName(); | |
670 | if (hw == null) { | |
671 | String countryStr = mep.getCountry(); | |
672 | Country thisCountry = countryStr != null ? lbl.createCountry(locator.normalizeCountry(countryStr), locator.getCountryISOCode(countryStr)) : getDefaultCountry(); | |
673 | String regionStr = regionName != null ? regionName : mep.getRegion(); // use --region-name if set because highway will likely span regions | |
674 | Region thisRegion = regionStr != null ? lbl.createRegion(thisCountry, regionStr, null) : getDefaultRegion(thisCountry); | |
675 | hw = lbl.createHighway(thisRegion, ref); | |
676 | log.info("creating highway", ref, "region:", regionStr, "country:", countryStr, "for exit:", exitName); | |
677 | highways.put(ref, hw); | |
678 | } | |
678 | 679 | String exitTo = mep.getTo(); |
679 | 680 | Exit exit = new Exit(hw); |
680 | 681 | String facilityDescription = mep.getFacilityDescription(); |
1286 | 1287 | } |
1287 | 1288 | } |
1288 | 1289 | |
1289 | Highway makeHighway(Map map, String ref) { | |
1290 | if (getDefaultRegion(null) == null) { | |
1291 | log.warn("Highway " + ref + " has no region (define a default region to zap this warning)"); | |
1292 | } | |
1293 | return highways.computeIfAbsent(ref, k-> { | |
1294 | log.info("creating highway", ref); | |
1295 | return map.getLblFile().createHighway(getDefaultRegion(null), ref); | |
1296 | }); | |
1297 | } | |
1298 | ||
1299 | 1290 | /** |
1300 | 1291 | * It is not possible to represent large maps at the 24 bit resolution. This |
1301 | 1292 | * gets the largest resolution that can still cover the whole area of the |
32 | 32 | * |
33 | 33 | * @param args The command line arguments. |
34 | 34 | */ |
35 | public void init(CommandArgs args); | |
35 | void init(CommandArgs args); | |
36 | 36 | |
37 | 37 | /** |
38 | 38 | * This is called when an individual map is complete. |
39 | 39 | * |
40 | 40 | * @param info An interface to read the map. |
41 | 41 | */ |
42 | public void onMapEnd(FileInfo info); | |
42 | void onMapEnd(FileInfo info); | |
43 | 43 | |
44 | 44 | /** |
45 | 45 | * The complete map set has been processed. Finish off anything that needs |
46 | 46 | * doing. |
47 | 47 | */ |
48 | public void onFinish(); | |
48 | void onFinish(); | |
49 | 49 | |
50 | public default String getFilename() { | |
50 | default String getFilename() { | |
51 | 51 | return null; |
52 | 52 | } |
53 | 53 | } |
44 | 44 | * each .img file. |
45 | 45 | */ |
46 | 46 | public class GmapiBuilder implements Combiner { |
47 | private final static String NS = "http://www.garmin.com/xmlschemas/MapProduct/v1"; | |
47 | private static final String NS = "http://www.garmin.com/xmlschemas/MapProduct/v1"; | |
48 | 48 | |
49 | 49 | private final Map<String, Combiner> combinerMap; |
50 | 50 |
244 | 244 | return mb; |
245 | 245 | } |
246 | 246 | |
247 | private ProductBlock makeProductBlock(FileInfo info) { | |
247 | private static ProductBlock makeProductBlock(FileInfo info) { | |
248 | 248 | ProductBlock pb = new ProductBlock(info.getCodePage()); |
249 | 249 | pb.setFamilyId(info.getFamilyId()); |
250 | 250 | pb.setProductId(info.getProductId()); |
274 | 274 | } |
275 | 275 | } |
276 | 276 | |
277 | private void addImg(FileSystem outfs, FileInfo info) { | |
277 | private static void addImg(FileSystem outfs, FileInfo info) { | |
278 | 278 | FileCopier fc = new FileCopier(info.getFilename()); |
279 | 279 | List<SubFileInfo> subFiles = info.subFiles(); |
280 | 280 | |
290 | 290 | } |
291 | 291 | } |
292 | 292 | |
293 | private void addFile(FileSystem outfs, FileInfo info) { | |
293 | private static void addFile(FileSystem outfs, FileInfo info) { | |
294 | 294 | String filename = info.getFilename(); |
295 | 295 | FileCopier fc = new FileCopier(filename); |
296 | 296 | |
333 | 333 | mpsFile.addProduct(makeProductBlock(info)); |
334 | 334 | } |
335 | 335 | |
336 | private MpsFile createMpsFile(FileSystem outfs) throws FileNotWritableException { | |
336 | private static MpsFile createMpsFile(FileSystem outfs) throws FileNotWritableException { | |
337 | 337 | try { |
338 | 338 | ImgChannel channel = outfs.create("MAKEGMAP.MPS"); |
339 | 339 | return new MpsFile(channel); |
357 | 357 | * @return The filename part, will be restricted to 8+3 characters and all |
358 | 358 | * in upper case. |
359 | 359 | */ |
360 | private String createImgFilename(String pathname) { | |
360 | private static String createImgFilename(String pathname) { | |
361 | 361 | File f = new File(pathname); |
362 | 362 | String name = f.getName().toUpperCase(Locale.ENGLISH); |
363 | 363 | int dot = name.lastIndexOf('.'); |
457 | 457 | } |
458 | 458 | } |
459 | 459 | |
460 | private void copyFile(ImgChannel fin, ImgChannel fout) throws IOException { | |
460 | private static void copyFile(ImgChannel fin, ImgChannel fout) throws IOException { | |
461 | 461 | ByteBuffer buf = ByteBuffer.allocate(1024); |
462 | 462 | while (fin.read(buf) > 0) { |
463 | 463 | buf.flip(); |
14 | 14 | import java.io.File; |
15 | 15 | import java.io.FileNotFoundException; |
16 | 16 | import java.io.IOException; |
17 | import java.nio.file.Files; | |
18 | import java.nio.file.Paths; | |
19 | import java.nio.file.StandardCopyOption; | |
17 | 20 | import java.util.ArrayList; |
18 | 21 | import java.util.HashMap; |
19 | 22 | import java.util.List; |
179 | 182 | for (Country c : countries) { |
180 | 183 | if (c != null) { |
181 | 184 | Mdr14Record record = mdrFile.addCountry(c); |
182 | countryMap.put((int) c.getIndex(), record); | |
185 | countryMap.put(c.getIndex(), record); | |
183 | 186 | } |
184 | 187 | } |
185 | 188 | return countryMap; |
191 | 194 | List<Region> regions = mr.getRegions(); |
192 | 195 | for (Region region : regions) { |
193 | 196 | if (region != null) { |
194 | Mdr14Record mdr14 = maps.countries.get((int) region.getCountry().getIndex()); | |
197 | Mdr14Record mdr14 = maps.countries.get(region.getCountry().getIndex()); | |
195 | 198 | Mdr13Record record = mdrFile.addRegion(region, mdr14); |
196 | regionMap.put((int) region.getIndex(), record); | |
199 | regionMap.put(region.getIndex(), record); | |
197 | 200 | } |
198 | 201 | } |
199 | 202 | return regionMap; |
334 | 337 | |
335 | 338 | // Rename from the temporary file to the proper name. On windows the target file must |
336 | 339 | // not exist for rename to work, so we are forced to remove it first. |
337 | File outputName = new File(this.outputName); | |
338 | outputName.delete(); | |
339 | boolean ok = tmpName.renameTo(outputName); | |
340 | if (!ok) | |
340 | try { | |
341 | Files.move(tmpName.toPath(), Paths.get(outputName), StandardCopyOption.REPLACE_EXISTING); | |
342 | } catch (IOException e) { | |
341 | 343 | throw new MapFailedException("Could not create mdr.img file"); |
344 | } | |
342 | 345 | } |
343 | 346 | |
344 | 347 | /** |
352 | 355 | mdrFile.write(); |
353 | 356 | } |
354 | 357 | |
358 | @Override | |
355 | 359 | public String getFilename() { |
356 | 360 | return outputName; |
357 | 361 | } |
55 | 55 | } |
56 | 56 | } |
57 | 57 | |
58 | @Override | |
58 | 59 | public String getFilename() { |
59 | 60 | return Utils.joinPath(outputDir, mdxFilename); |
60 | 61 | } |
124 | 124 | } |
125 | 125 | |
126 | 126 | private void writeDefines(PrintWriter pw) { |
127 | pw.format(Locale.ROOT, "!define DEFAULT_DIR \"C:\\Garmin\\Maps\\%s\"\n", familyName); | |
128 | pw.format(Locale.ROOT, "!define INSTALLER_DESCRIPTION \"%s\"\n", familyName); | |
129 | pw.format(Locale.ROOT, "!define INSTALLER_NAME \"%s\"\n", familyName); | |
130 | pw.format(Locale.ROOT, "!define MAPNAME \"%s\"\n", baseFilename); | |
131 | pw.format(Locale.ROOT, "!define PRODUCT_ID \"%s\"\n", productId); | |
132 | pw.format(Locale.ROOT, "!define REG_KEY \"%s\"\n", familyName); | |
133 | if (hasIndex) | |
134 | pw.format(Locale.ROOT, "!define INDEX\n"); | |
135 | if (hasTyp) | |
136 | pw.format(Locale.ROOT, "!define TYPNAME \"%s\"\n", typName); | |
127 | pw.format(Locale.ROOT, "!define DEFAULT_DIR \"C:\\Garmin\\Maps\\%s\"\n", familyName); | |
128 | pw.format(Locale.ROOT, "!define INSTALLER_DESCRIPTION \"%s\"\n", familyName); | |
129 | pw.format(Locale.ROOT, "!define INSTALLER_NAME \"%s\"\n", familyName); | |
130 | pw.format(Locale.ROOT, "!define MAPNAME \"%s\"\n", baseFilename); | |
131 | pw.format(Locale.ROOT, "!define PRODUCT_ID \"%s\"\n", productId); | |
132 | pw.format(Locale.ROOT, "!define REG_KEY \"%s\"\n", familyName); | |
133 | if (hasIndex) | |
134 | pw.append("!define INDEX\n"); | |
135 | if (hasTyp) | |
136 | pw.format(Locale.ROOT, "!define TYPNAME \"%s\"\n", typName); | |
137 | 137 | } |
138 | 138 | |
139 | 139 | private void writeRegBin(PrintWriter pw) { |
142 | 142 | } |
143 | 143 | |
144 | 144 | private void writeAddedFiles(PrintWriter pw) { |
145 | pw.format(Locale.ROOT, " File \"${MAPNAME}.img\"\n"); | |
146 | if (hasIndex) { | |
147 | pw.format(Locale.ROOT, " File \"${MAPNAME}_mdr.img\"\n"); | |
148 | pw.format(Locale.ROOT, " File \"${MAPNAME}.mdx\"\n"); | |
149 | } | |
150 | if (hasTyp) | |
151 | pw.format(Locale.ROOT, " File \"%s\"\n", typName); | |
152 | ||
153 | pw.format(Locale.ROOT, " File \"${MAPNAME}.tdb\"\n"); | |
154 | for (String file : mapList) | |
155 | pw.format(Locale.ROOT, " File \"%s.img\"\n", file); | |
156 | } | |
157 | ||
158 | ||
145 | pw.append(" File \"${MAPNAME}.img\"\n"); | |
146 | if (hasIndex) { | |
147 | pw.append(" File \"${MAPNAME}_mdr.img\"\n"); | |
148 | pw.append(" File \"${MAPNAME}.mdx\"\n"); | |
149 | } | |
150 | if (hasTyp) | |
151 | pw.format(Locale.ROOT, " File \"%s\"\n", typName); | |
152 | ||
153 | pw.append(" File \"${MAPNAME}.tdb\"\n"); | |
154 | for (String file : mapList) | |
155 | pw.format(Locale.ROOT, " File \"%s.img\"\n", file); | |
156 | } | |
159 | 157 | |
160 | 158 | private void writeRemovedFiles(PrintWriter pw) { |
161 | pw.format(Locale.ROOT, " Delete \"$INSTDIR\\${MAPNAME}.img\"\n"); | |
162 | if (hasIndex) { | |
163 | pw.format(Locale.ROOT, " Delete \"$INSTDIR\\${MAPNAME}_mdr.img\"\n"); | |
164 | pw.format(Locale.ROOT, " Delete \"$INSTDIR\\${MAPNAME}.mdx\"\n"); | |
165 | } | |
166 | if (hasTyp) | |
167 | pw.format(Locale.ROOT, " Delete \"$INSTDIR\\%s\"\n", typName); | |
168 | pw.format(Locale.ROOT, " Delete \"$INSTDIR\\${MAPNAME}.tdb\"\n"); | |
169 | for (String file : mapList) { | |
170 | pw.format(Locale.ROOT, " Delete \"$INSTDIR\\%s.img\"\n", file); | |
171 | } | |
172 | pw.format(Locale.ROOT, " Delete \"$INSTDIR\\Uninstall.exe\"\n"); | |
159 | pw.append(" Delete \"$INSTDIR\\${MAPNAME}.img\"\n"); | |
160 | if (hasIndex) { | |
161 | pw.append(" Delete \"$INSTDIR\\${MAPNAME}_mdr.img\"\n"); | |
162 | pw.append(" Delete \"$INSTDIR\\${MAPNAME}.mdx\"\n"); | |
163 | } | |
164 | if (hasTyp) | |
165 | pw.format(Locale.ROOT, " Delete \"$INSTDIR\\%s\"\n", typName); | |
166 | pw.append(" Delete \"$INSTDIR\\${MAPNAME}.tdb\"\n"); | |
167 | for (String file : mapList) { | |
168 | pw.format(Locale.ROOT, " Delete \"$INSTDIR\\%s.img\"\n", file); | |
169 | } | |
170 | pw.append(" Delete \"$INSTDIR\\Uninstall.exe\"\n"); | |
173 | 171 | } |
174 | 172 | |
175 | 173 |
59 | 59 | private String outputDir; |
60 | 60 | private Integer codepage; |
61 | 61 | private Integer encodingType; |
62 | private List<String[]> copyrightMsgs = new ArrayList<String[]>(); | |
63 | private List<String[]> licenseInfos = new ArrayList<String[]>(); | |
62 | private List<String[]> copyrightMsgs = new ArrayList<>(); | |
63 | private List<String[]> licenseInfos = new ArrayList<>(); | |
64 | 64 | private LevelInfo[] wantedLevels; |
65 | 65 | private Area bounds; |
66 | 66 | private boolean hasBackground; |
78 | 78 | outputDir = args.getOutputDir(); |
79 | 79 | String demDist = args.getProperties().getProperty("overview-dem-dist"); |
80 | 80 | String hgtPath = args.getProperties().getProperty("dem"); |
81 | if (hgtPath != null && demDist != null && "0".equals(demDist.trim()) == false) { | |
81 | if (hgtPath != null && demDist != null && !"0".equals(demDist.trim())) { | |
82 | 82 | demProps = new EnhancedProperties(args.getProperties()); |
83 | 83 | demProps.setProperty("dem-dists", demDist); |
84 | 84 | } |
111 | 111 | } |
112 | 112 | |
113 | 113 | private void calcLevels() { |
114 | List<MapShape> shapes = overviewSource.getShapes(); | |
115 | 114 | int maxRes = 16; // we can write a 0x4a polygon for planet in res 16 |
116 | 115 | if (wantedLevels != null) |
117 | 116 | maxRes = wantedLevels[wantedLevels.length-1].getBits(); |
118 | 117 | int maxSize = 0xffff << (24 - maxRes); |
119 | for (MapShape s : shapes){ | |
118 | for (MapShape s : overviewSource.getShapes()){ | |
120 | 119 | if (s.getType() != 0x4a) |
121 | 120 | continue; |
122 | 121 | int maxDimPoly = s.getBounds().getMaxDimension(); |
126 | 125 | maxRes--; |
127 | 126 | maxSize = 0xffff << (24 - maxRes); |
128 | 127 | } |
129 | String[] name = s.getName().split("\u001d"); | |
130 | String msg = "Tile selection (0x4a) polygon for "; | |
131 | if (name != null && name.length == 2) | |
132 | msg += "tile " + name[1].trim(); | |
128 | final String[] name = s.getName().split("\u001d"); | |
129 | final String msg = "Tile selection (0x4a) polygon for"; | |
130 | final String msg2; | |
131 | if (name.length == 2) | |
132 | msg2 = "tile " + name[1].trim(); | |
133 | 133 | else |
134 | msg += s.getBounds(); | |
135 | log.error(msg,"cannot be written in level 0 resolution",oldMaxRes + ", using",maxRes,"instead"); | |
134 | msg2 = s.getBounds().toString(); | |
135 | log.error(msg, msg2, "cannot be written in level 0 resolution", oldMaxRes + ", using", maxRes, "instead"); | |
136 | 136 | |
137 | 137 | } |
138 | 138 | } |
152 | 152 | } |
153 | 153 | wantedLevels = Arrays.copyOfRange(wantedLevels, 0, l); |
154 | 154 | overviewSource.setMapLevels(wantedLevels); |
155 | } else | |
156 | setRes(maxRes); | |
155 | } else { | |
156 | setRes(maxRes); | |
157 | } | |
157 | 158 | } |
158 | 159 | } |
159 | 160 | |
288 | 289 | for (Point point: pointList) { |
289 | 290 | if (log.isDebugEnabled()) |
290 | 291 | log.debug("got point", point); |
291 | if (bounds.contains(point.getLocation()) == false){ | |
292 | if (!bounds.contains(point.getLocation())){ | |
292 | 293 | if (log.isDebugEnabled()) |
293 | 294 | log.debug(point, "dropped, is outside of tile boundary"); |
294 | 295 | continue; |
438 | 439 | else return name; |
439 | 440 | } |
440 | 441 | |
441 | private List<String> creMsgList(List<String[]> msgs){ | |
442 | ArrayList< String> list = new ArrayList<String>(); | |
442 | private static List<String> creMsgList(List<String[]> msgs){ | |
443 | ArrayList< String> list = new ArrayList<>(); | |
443 | 444 | for (int i = 0; i < msgs.size(); i++){ |
444 | 445 | String[] block = msgs.get(i); |
445 | for (String s : block){ | |
446 | list.add(s); | |
447 | } | |
448 | if (i < msgs.size()-1){ | |
449 | // separate blocks | |
446 | list.addAll(Arrays.asList(block)); | |
447 | if (i < msgs.size() - 1) { | |
448 | // separate blocks | |
450 | 449 | list.add(""); |
451 | 450 | } |
452 | 451 | } |
32 | 32 | * |
33 | 33 | * @param cw The string to add. |
34 | 34 | */ |
35 | public void addCopyright(String cw); | |
35 | void addCopyright(String cw); | |
36 | 36 | |
37 | public void setMapLevels(LevelInfo[] levels); | |
37 | void setMapLevels(LevelInfo[] levels); | |
38 | 38 | } |
177 | 177 | writeTdbFile(); |
178 | 178 | } |
179 | 179 | |
180 | @Override | |
180 | 181 | public String getFilename() { |
181 | 182 | return Utils.joinPath(outputDir, overviewMapname, "tdb"); |
182 | 183 | } |
25 | 25 | */ |
26 | 26 | public class BaseFilter implements MapFilter { |
27 | 27 | /** |
28 | * Empty implementation of the init function. | |
29 | * | |
30 | * @param config Configuration information, giving parameters of the map | |
31 | * level that is being produced through this filter. | |
32 | */ | |
33 | public void init(FilterConfig config) { | |
34 | } | |
35 | ||
36 | /** | |
37 | 28 | * Empty implementation. |
38 | 29 | * |
39 | 30 | * @param element A map element. |
40 | 31 | * @param next This is used to pass the possibly transformed element onward. |
41 | 32 | */ |
33 | @Override | |
42 | 34 | public void doFilter(MapElement element, MapFilterChain next) { |
43 | 35 | throw new UnsupportedOperationException(); |
44 | 36 | } |
36 | 36 | this.filterDistance = filterDistance; |
37 | 37 | } |
38 | 38 | |
39 | @Override | |
39 | 40 | public void init(FilterConfig config) { |
40 | 41 | this.resolution = config.getResolution(); |
41 | 42 | this.maxErrorDistance = filterDistance * (1<< config.getShift()); |
48 | 49 | * @param element A map element that will be a line or a polygon. |
49 | 50 | * @param next This is used to pass the possibly transformed element onward. |
50 | 51 | */ |
52 | @Override | |
51 | 53 | public void doFilter(MapElement element, MapFilterChain next) { |
52 | 54 | // First off we don't touch things if at the highest level of detail |
53 | 55 | if (resolution == 24) { |
13 | 13 | private static final Logger log = Logger.getLogger(LineMergeFilter.class); |
14 | 14 | |
15 | 15 | private List<MapLine> linesMerged; |
16 | private final MultiHashMap<Coord, MapLine> startPoints = new MultiHashMap<Coord, MapLine>(); | |
17 | private final MultiHashMap<Coord, MapLine> endPoints = new MultiHashMap<Coord, MapLine>(); | |
16 | private final MultiHashMap<Coord, MapLine> startPoints = new MultiHashMap<>(); | |
17 | private final MultiHashMap<Coord, MapLine> endPoints = new MultiHashMap<>(); | |
18 | 18 | |
19 | 19 | private void addLine(MapLine line) { |
20 | 20 | linesMerged.add(line); |
55 | 55 | //TODO: This routine has a side effect: it modifies some of the MapLine instances |
56 | 56 | // instead of creating copies. It seems that this has no bad effect, but it is not clean |
57 | 57 | public List<MapLine> merge(List<MapLine> lines, int res) { |
58 | linesMerged = new ArrayList<MapLine>(lines.size()); //better use LinkedList?? | |
58 | linesMerged = new ArrayList<>(lines.size()); //better use LinkedList?? | |
59 | 59 | for (MapLine line : lines) { |
60 | 60 | if (line.getMinResolution() > res || line.getMaxResolution() < res) |
61 | 61 | continue; |
107 | 107 | |
108 | 108 | // No matching, create a copy of line |
109 | 109 | MapLine l = line.copy(); |
110 | List<Coord> p = new ArrayList<Coord>(line.getPoints()); //use better LinkedList for performance? | |
110 | List<Coord> p = new ArrayList<>(line.getPoints()); //use better LinkedList for performance? | |
111 | 111 | l.setPoints(p); |
112 | 112 | addLine(l); |
113 | 113 | } |
38 | 38 | this.subdiv = subdiv; |
39 | 39 | } |
40 | 40 | |
41 | @Override | |
41 | 42 | public void init(FilterConfig config) { |
42 | 43 | shift = config.getShift(); |
43 | 44 | } |
46 | 47 | * @param element A map element that will be a line or a polygon. |
47 | 48 | * @param next This is used to pass the element onward. |
48 | 49 | */ |
50 | @Override | |
49 | 51 | public void doFilter(MapElement element, MapFilterChain next) { |
50 | 52 | MapLine line = (MapLine) element; |
51 | 53 | |
128 | 130 | rotation = maxBitsPos[k]; |
129 | 131 | } |
130 | 132 | } |
131 | /* | |
132 | int savedBits = (numPoints-1 * maxReduction); | |
133 | if (savedBits > 100){ | |
134 | System.out.println("rotation of shape saves " + savedBits + " bits"); | |
135 | } | |
136 | */ | |
137 | 133 | if (rotation != 0){ |
138 | 134 | List<Coord> points = line.getPoints(); |
139 | 135 | if (minPointsRequired == 4) |
39 | 39 | |
40 | 40 | private int maxSize; |
41 | 41 | |
42 | @Override | |
42 | 43 | public void init(FilterConfig config) { |
43 | 44 | int shift = config.getShift(); |
44 | 45 | if (shift > 15) |
50 | 51 | // divided by the maximum allowed size - so if the height and |
51 | 52 | // width are not too large, the result will be <= 1.0 |
52 | 53 | public static double testDims(int height, int width) { |
53 | return (double)Math.max(Math.abs(height), Math.abs(width)) / MAX_SIZE; | |
54 | return (double) Math.max(Math.abs(height), Math.abs(width)) / MAX_SIZE; | |
54 | 55 | } |
55 | 56 | |
56 | 57 | /** |
60 | 61 | * @param element A map element. |
61 | 62 | * @param next This is used to pass the possibly transformed element onward. |
62 | 63 | */ |
64 | @Override | |
63 | 65 | public void doFilter(MapElement element, MapFilterChain next) { |
64 | 66 | // We do not deal with shapes. |
65 | 67 | assert !(element instanceof MapShape) && element instanceof MapLine; |
85 | 87 | |
86 | 88 | MapLine l = line.copy(); |
87 | 89 | |
88 | List<Coord> coords = new ArrayList<Coord>(); | |
90 | List<Coord> coords = new ArrayList<>(); | |
89 | 91 | boolean first = true; |
90 | 92 | |
91 | 93 | /** |
135 | 137 | // Add points while not too big and then start again with a fresh line. |
136 | 138 | for (Coord co: points){ |
137 | 139 | dim.addToBounds(co); |
138 | if (dim.getMaxDim() > maxSize) { | |
140 | if (prev != null && dim.getMaxDim() > maxSize) { | |
139 | 141 | if (first) |
140 | 142 | log.debug("bigness saving first part"); |
141 | 143 | else |
147 | 149 | |
148 | 150 | first = false; |
149 | 151 | dim.reset(); |
150 | coords = new ArrayList<Coord>(); | |
152 | coords = new ArrayList<>(); | |
151 | 153 | coords.add(prev); |
152 | 154 | dim.addToBounds(prev); |
153 | 155 | dim.addToBounds(co); |
172 | 174 | * @return a reference to a new list of points |
173 | 175 | */ |
174 | 176 | private static List<Coord> splitLinesToMaxSize(List<Coord> coords, int maxSize){ |
175 | List<Coord> testedCoords = new ArrayList<Coord>(coords); | |
177 | List<Coord> testedCoords = new ArrayList<>(coords); | |
176 | 178 | int posToTest = coords.size() -2; |
177 | 179 | while (posToTest >= 0){ |
178 | 180 | Coord p1 = testedCoords.get(posToTest); |
42 | 42 | |
43 | 43 | private int level; |
44 | 44 | private boolean isRoutable; |
45 | ||
46 | @Override | |
45 | 47 | public void init(FilterConfig config) { |
46 | 48 | this.level = config.getLevel(); |
47 | 49 | this.isRoutable = config.hasNet(); |
56 | 58 | * @param element A map element. |
57 | 59 | * @param next This is used to pass the possibly transformed element onward. |
58 | 60 | */ |
61 | @Override | |
59 | 62 | public void doFilter(MapElement element, MapFilterChain next) { |
60 | 63 | // We do not deal with shapes. |
61 | 64 | assert !(element instanceof MapShape) && element instanceof MapLine; |
104 | 107 | if (remaining <= MAX_POINTS_IN_LINE) { |
105 | 108 | last = true; |
106 | 109 | wantedSize = remaining; |
107 | } else if (remaining < 2 * MAX_POINTS_IN_LINE) | |
110 | } else if (remaining < 2 * MAX_POINTS_IN_LINE) { | |
108 | 111 | wantedSize = remaining / 2 + 1; |
112 | } | |
109 | 113 | } |
110 | 114 | } |
111 | 115 | } |
33 | 33 | * @param config Configuration information, giving parameters of the map |
34 | 34 | * level that is being produced through this filter. |
35 | 35 | */ |
36 | public void init(FilterConfig config); | |
36 | default void init(FilterConfig config){} | |
37 | 37 | |
38 | 38 | /** |
39 | 39 | * Filter an element. The filter looks at the element and can simply |
51 | 51 | * @param element A map element. |
52 | 52 | * @param next This is used to pass the possibly transformed element onward. |
53 | 53 | */ |
54 | public void doFilter(MapElement element, MapFilterChain next); | |
54 | void doFilter(MapElement element, MapFilterChain next); | |
55 | 55 | } |
34 | 34 | * |
35 | 35 | * @param element The map element. |
36 | 36 | */ |
37 | public void doFilter(MapElement element); | |
37 | void doFilter(MapElement element); | |
38 | 38 | |
39 | 39 | } |
31 | 31 | protected int shift; |
32 | 32 | protected int resolution; |
33 | 33 | |
34 | @Override | |
34 | 35 | public void init(FilterConfig config) { |
35 | 36 | shift = config.getShift(); |
36 | 37 | resolution = config.getResolution(); |
30 | 30 | |
31 | 31 | public static final int MAX_POINT_IN_ELEMENT = 250; |
32 | 32 | |
33 | // public PolygonSplitterFilter() { | |
34 | // } | |
35 | ||
36 | 33 | /** |
37 | 34 | * This filter splits a polygon if any of the subsequent filters throws a |
38 | 35 | * {@link MustSplitException}. |
41 | 38 | * @param element A map element, only polygons will be processed. |
42 | 39 | * @param next This is used to pass the possibly transformed element onward. |
43 | 40 | */ |
41 | @Override | |
44 | 42 | public void doFilter(MapElement element, MapFilterChain next) { |
45 | 43 | assert element instanceof MapShape; |
46 | 44 | MapShape shape = (MapShape) element; |
48 | 46 | try { |
49 | 47 | next.doFilter(shape); |
50 | 48 | } catch (MustSplitException e) { |
51 | List<MapShape> outputs = new ArrayList<MapShape>(); | |
49 | List<MapShape> outputs = new ArrayList<>(); | |
52 | 50 | split(shape, outputs); // split in half |
53 | 51 | for (MapShape s : outputs) { |
54 | 52 | doFilter(s, next); // recurse as components could still be too big |
37 | 37 | * @param config configuration information, giving parameters of the map level |
38 | 38 | * that is being produced through this filter. |
39 | 39 | */ |
40 | @Override | |
40 | 41 | public void init(FilterConfig config) { |
41 | 42 | int shift = config.getShift(); |
42 | 43 | if (shift > 15) |
51 | 52 | * @param element A map element, only polygons will be processed. |
52 | 53 | * @param next This is used to pass the possibly transformed element onward. |
53 | 54 | */ |
55 | @Override | |
54 | 56 | public void doFilter(MapElement element, MapFilterChain next) { |
55 | 57 | assert element instanceof MapShape; |
56 | 58 | MapShape shape = (MapShape) element; |
61 | 63 | return; |
62 | 64 | } |
63 | 65 | |
64 | List<MapShape> outputs = new ArrayList<MapShape>(); | |
66 | List<MapShape> outputs = new ArrayList<>(); | |
65 | 67 | |
66 | 68 | // Do an initial split |
67 | 69 | split(shape, outputs); |
29 | 29 | public class RemoveEmpty implements MapFilter { |
30 | 30 | private static final Logger log = Logger.getLogger(RemoveEmpty.class); |
31 | 31 | |
32 | public void init(FilterConfig config) { | |
33 | } | |
34 | ||
35 | 32 | /** |
36 | 33 | * If this is a line (or a shape, which extends a line) then we check |
37 | 34 | * to see if it is empty or only a single point. If it is then it |
40 | 37 | * @param element A map element. |
41 | 38 | * @param next This is used to pass the possibly transformed element onward. |
42 | 39 | */ |
40 | @Override | |
43 | 41 | public void doFilter(MapElement element, MapFilterChain next) { |
44 | 42 | if (element instanceof MapShape) { |
45 | 43 | MapShape mapShape = (MapShape) element; |
32 | 32 | final Coord[] areaTest = new Coord[3]; |
33 | 33 | |
34 | 34 | private boolean checkPreserved; |
35 | ||
36 | @Override | |
35 | 37 | public void init(FilterConfig config) { |
36 | 38 | checkPreserved = config.getLevel() == 0 && config.hasNet(); |
37 | 39 | } |
40 | 42 | * @param element A map element that will be a line or a polygon. |
41 | 43 | * @param next This is used to pass the possibly transformed element onward. |
42 | 44 | */ |
45 | @Override | |
43 | 46 | public void doFilter(MapElement element, MapFilterChain next) { |
44 | 47 | MapLine line = (MapLine) element; |
45 | 48 | List<Coord> points = line.getPoints(); |
27 | 27 | private boolean keepNodes; |
28 | 28 | private int level; |
29 | 29 | |
30 | @Override | |
30 | 31 | public void init(FilterConfig config) { |
31 | 32 | shift = config.getShift(); |
32 | 33 | keepNodes = config.getLevel() == 0 && config.hasNet(); |
37 | 38 | * @param element A map element that will be a line or a polygon. |
38 | 39 | * @param next This is used to pass the possibly transformed element onward. |
39 | 40 | */ |
41 | @Override | |
40 | 42 | public void doFilter(MapElement element, MapFilterChain next) { |
41 | 43 | MapLine line = (MapLine) element; |
42 | 44 | if(shift == 0) { |
39 | 39 | public class ShapeMergeFilter{ |
40 | 40 | private static final Logger log = Logger.getLogger(ShapeMergeFilter.class); |
41 | 41 | private final int resolution; |
42 | private final static ShapeHelper DUP_SHAPE = new ShapeHelper(new ArrayList<Coord>(0)); | |
42 | private static final ShapeHelper DUP_SHAPE = new ShapeHelper(new ArrayList<>(0)); | |
43 | 43 | private final boolean orderByDecreasingArea; |
44 | 44 | |
45 | 45 | public ShapeMergeFilter(int resolution, boolean orderByDecreasingArea) { |
159 | 159 | sharedPoints.add(c); |
160 | 160 | } |
161 | 161 | } |
162 | if (sharedPoints.size() == 0 || sh0.getPoints().size() - sharedPoints.size()> PolygonSplitterFilter.MAX_POINT_IN_ELEMENT) { | |
162 | if (sharedPoints.isEmpty() || sh0.getPoints().size() - sharedPoints.size()> PolygonSplitterFilter.MAX_POINT_IN_ELEMENT) { | |
163 | 163 | // merge will not work |
164 | 164 | noMerge.add(sh0); |
165 | 165 | continue; |
286 | 286 | * @return merged shape or 1st shape if no common point found or {@code dupShape} |
287 | 287 | * if both shapes describe the same area. |
288 | 288 | */ |
289 | private ShapeHelper tryMerge(ShapeHelper sh1, ShapeHelper sh2) { | |
289 | private static ShapeHelper tryMerge(ShapeHelper sh1, ShapeHelper sh2) { | |
290 | 290 | |
291 | 291 | // both clockwise or both ccw ? |
292 | 292 | boolean sameDir = sh1.areaTestVal > 0 && sh2.areaTestVal > 0 || sh1.areaTestVal < 0 && sh2.areaTestVal < 0; |
397 | 397 | } |
398 | 398 | pos++; |
399 | 399 | } |
400 | return; | |
401 | 400 | } |
402 | 401 | |
403 | 402 | /** |
483 | 482 | } |
484 | 483 | |
485 | 484 | private static class ShapeHelper { |
486 | final private List<Coord> points; | |
485 | private final List<Coord> points; | |
487 | 486 | long id; |
488 | 487 | long areaTestVal; |
489 | 488 | |
503 | 502 | } |
504 | 503 | } |
505 | 504 | |
506 | public final static long SINGLE_POINT_AREA = 1L << Coord.DELTA_SHIFT * 1L << Coord.DELTA_SHIFT; | |
505 | public static final long SINGLE_POINT_AREA = 1L << Coord.DELTA_SHIFT * 1L << Coord.DELTA_SHIFT; | |
507 | 506 | |
508 | 507 | /** |
509 | 508 | * Calculate the high precision area size test value. |
514 | 513 | public static long calcAreaSizeTestVal(List<Coord> points){ |
515 | 514 | if (points.size() < 4) |
516 | 515 | return 0; // straight line cannot enclose an area |
517 | if (points.get(0).highPrecEquals(points.get(points.size()-1)) == false){ | |
516 | if (!points.get(0).highPrecEquals(points.get(points.size()-1))){ | |
518 | 517 | log.error("shape is not closed"); |
519 | 518 | return 0; |
520 | 519 | } |
527 | 526 | signedAreaSize += (long) (c2.getHighPrecLon() + c1.getHighPrecLon()) |
528 | 527 | * (c1.getHighPrecLat() - c2.getHighPrecLat()); |
529 | 528 | } |
530 | if (Math.abs(signedAreaSize) < SINGLE_POINT_AREA){ | |
531 | if (log.isDebugEnabled()) { | |
532 | log.debug("very small shape near", points.get(0).toOSMURL(), "signed area in high prec map units:", signedAreaSize ); | |
533 | } | |
529 | if (Math.abs(signedAreaSize) < SINGLE_POINT_AREA && log.isDebugEnabled()) { | |
530 | log.debug("very small shape near", points.get(0).toOSMURL(), "signed area in high prec map units:", signedAreaSize ); | |
534 | 531 | } |
535 | 532 | return signedAreaSize; |
536 | 533 | } |
552 | 549 | } |
553 | 550 | String n1 = o1.getName(); |
554 | 551 | String n2 = o2.getName(); |
555 | if (n1 == n2) | |
556 | return 0; | |
552 | ||
557 | 553 | if (n1 == null) { |
558 | 554 | return (n2 == null) ? 0 : 1; |
559 | 555 | } |
32 | 32 | size = s; |
33 | 33 | } |
34 | 34 | |
35 | @Override | |
35 | 36 | public void init(FilterConfig config) { |
36 | 37 | minSize = size * (1 << config.getShift()); |
37 | 38 | // don't remove roads on level 0 |
45 | 46 | * @param element A map element that will be a line or a polygon. |
46 | 47 | * @param next This is used to pass the possibly transformed element onward. |
47 | 48 | */ |
49 | @Override | |
48 | 50 | public void doFilter(MapElement element, MapFilterChain next) { |
49 | 51 | MapLine line = (MapLine) element; |
50 | 52 |
23 | 23 | import uk.me.parabola.mkgmap.general.MapLine; |
24 | 24 | |
25 | 25 | /** |
26 | * This is a filter that smooths out lines at low resolutions. If the element | |
26 | * This is a filter that smoothes out lines at low resolutions. If the element | |
27 | 27 | * has no size at all at the given resolution, then it is not passed on down |
28 | 28 | * the chain at all is excluded from the map at that resolution. |
29 | 29 | * |
35 | 35 | |
36 | 36 | private int shift; |
37 | 37 | |
38 | @Override | |
38 | 39 | public void init(FilterConfig config) { |
39 | 40 | this.shift = config.getShift(); |
40 | 41 | } |
57 | 58 | * @param element A map element that will be a line or a polygon. |
58 | 59 | * @param next This is used to pass the possibly transformed element onward. |
59 | 60 | */ |
61 | @Override | |
60 | 62 | public void doFilter(MapElement element, MapFilterChain next) { |
61 | 63 | MapLine line = (MapLine) element; |
62 | 64 | |
76 | 78 | } |
77 | 79 | |
78 | 80 | // Create a new list to rewrite the points into. |
79 | List<Coord> coords = new ArrayList<Coord>(n); | |
81 | List<Coord> coords = new ArrayList<>(n); | |
80 | 82 | |
81 | 83 | // Get the step size, we want to place a point every time the |
82 | 84 | // average exceeds this size. |
46 | 46 | return null; |
47 | 47 | |
48 | 48 | class LineCollector { |
49 | private final List<List<Coord>> ret = new ArrayList<List<Coord>>(4); | |
49 | private final List<List<Coord>> ret = new ArrayList<>(4); | |
50 | 50 | private List<Coord> currentLine; |
51 | 51 | private Coord last; |
52 | 52 | |
57 | 57 | // we start a new line if there isn't a current one, or if the first |
58 | 58 | // point of the segment is not equal to the last one in the line. |
59 | 59 | if (currentLine == null || !segment[0].equals(last)) { |
60 | currentLine = new ArrayList<Coord>(5); | |
60 | currentLine = new ArrayList<>(5); | |
61 | 61 | currentLine.add(segment[0]); |
62 | 62 | currentLine.add(segment[1]); |
63 | 63 | ret.add(currentLine); |
36 | 36 | * @param p The coordinates of the point to add. The type here |
37 | 37 | * will change to Node. |
38 | 38 | */ |
39 | public void addToBounds(Coord p); | |
39 | void addToBounds(Coord p); | |
40 | 40 | |
41 | 41 | /** |
42 | 42 | * Add a point to the map. |
43 | 43 | * |
44 | 44 | * @param point The point to add. |
45 | 45 | */ |
46 | public void addPoint(MapPoint point); | |
46 | void addPoint(MapPoint point); | |
47 | 47 | |
48 | 48 | /** |
49 | 49 | * Add a line to the map. |
50 | 50 | * |
51 | 51 | * @param line The line information. |
52 | 52 | */ |
53 | public void addLine(MapLine line); | |
53 | void addLine(MapLine line); | |
54 | 54 | |
55 | 55 | /** |
56 | 56 | * Add the given shape (polygon) to the map. A shape is very similar to |
59 | 59 | * |
60 | 60 | * @param shape The polygon to add. |
61 | 61 | */ |
62 | public void addShape(MapShape shape); | |
62 | void addShape(MapShape shape); | |
63 | 63 | |
64 | 64 | |
65 | 65 | /** |
67 | 67 | * differently so that we can join up roads that are split into several |
68 | 68 | * segments and to do routing etc. |
69 | 69 | */ |
70 | public void addRoad(MapRoad road); | |
70 | void addRoad(MapRoad road); | |
71 | 71 | |
72 | 72 | /** |
73 | 73 | * Add a routing restriction to the map. This is something such as |
74 | 74 | * no left turn. |
75 | 75 | * @param exceptMask For exceptions eg. no-left-turn except for buses. |
76 | 76 | */ |
77 | public int addRestriction(GeneralRouteRestriction grr); | |
77 | int addRestriction(GeneralRouteRestriction grr); | |
78 | 78 | } |
79 | 79 |
41 | 41 | public class MapDetails implements MapCollector, MapDataSource { |
42 | 42 | private static final Logger log = Logger.getLogger(MapDetails.class); |
43 | 43 | |
44 | private final List<MapLine> lines = new ArrayList<MapLine>(); | |
45 | private final List<MapShape> shapes = new ArrayList<MapShape>(); | |
46 | private final List<MapPoint> points = new ArrayList<MapPoint>(); | |
44 | private final List<MapLine> lines = new ArrayList<>(); | |
45 | private final List<MapShape> shapes = new ArrayList<>(); | |
46 | private final List<MapPoint> points = new ArrayList<>(); | |
47 | 47 | |
48 | 48 | private int minLatHp = Integer.MAX_VALUE; |
49 | 49 | private int minLonHp = Integer.MAX_VALUE; |
51 | 51 | private int maxLonHp = Integer.MIN_VALUE; |
52 | 52 | |
53 | 53 | // Keep lists of all items that were used. |
54 | private final Map<Integer, Integer> pointOverviews = new HashMap<Integer, Integer>(); | |
55 | private final Map<Integer, Integer> lineOverviews = new HashMap<Integer, Integer>(); | |
56 | private final Map<Integer, Integer> shapeOverviews = new HashMap<Integer, Integer>(); | |
54 | private final Map<Integer, Integer> pointOverviews = new HashMap<>(); | |
55 | private final Map<Integer, Integer> lineOverviews = new HashMap<>(); | |
56 | private final Map<Integer, Integer> shapeOverviews = new HashMap<>(); | |
57 | 57 | |
58 | 58 | private final RoadNetwork roadNetwork = new RoadNetwork(); |
59 | 59 | |
196 | 196 | * @return A list of overviews. |
197 | 197 | */ |
198 | 198 | public List<Overview> getOverviews() { |
199 | List<Overview> ovlist = new ArrayList<Overview>(); | |
199 | List<Overview> ovlist = new ArrayList<>(); | |
200 | 200 | |
201 | 201 | for (Map.Entry<Integer, Integer> ent : pointOverviews.entrySet()) { |
202 | 202 | Overview ov = new PointOverview(ent.getKey(), ent.getValue()); |
15 | 15 | package uk.me.parabola.mkgmap.general; |
16 | 16 | |
17 | 17 | import java.util.Arrays; |
18 | import java.util.Objects; | |
18 | 19 | |
19 | 20 | import uk.me.parabola.imgfmt.app.Coord; |
20 | 21 | import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes; |
174 | 175 | } |
175 | 176 | |
176 | 177 | public void setIsIn(String isIn) { |
177 | if(isIn != null) | |
178 | this.isIn = isIn.toUpperCase(); | |
179 | } | |
180 | ||
178 | if (isIn != null) { | |
179 | this.isIn = isIn.toUpperCase(); | |
180 | } | |
181 | } | |
181 | 182 | |
182 | 183 | /** |
183 | 184 | * This is the type code that goes in the .img file so that the GPS device |
201 | 202 | if (this.type != other.type) |
202 | 203 | return false; |
203 | 204 | |
204 | String thisName = getName(); | |
205 | String otherName = other.getName(); | |
206 | ||
207 | if (thisName == null && otherName == null) | |
208 | return true; | |
209 | if (thisName!=null && otherName!=null && thisName.equals(otherName)) | |
210 | return true; | |
211 | return false; | |
205 | return Objects.equals(getName(), other.getName()); | |
212 | 206 | } |
213 | 207 | |
214 | 208 | public boolean hasExtendedType() { |
67 | 67 | this.points = points; |
68 | 68 | // preserve first and last point, so that points which are shared by |
69 | 69 | // different ways are kept |
70 | if (points.size() > 0 && this instanceof MapShape == false){ | |
70 | if (points.size() > 0 && !(this instanceof MapShape)) { | |
71 | 71 | points.get(0).preserved(true); |
72 | 72 | points.get(points.size()-1).preserved(true); |
73 | 73 | } |
28 | 28 | import java.time.Instant; |
29 | 29 | import java.util.ArrayList; |
30 | 30 | import java.util.Arrays; |
31 | import java.util.Collections; | |
31 | 32 | import java.util.Date; |
32 | 33 | import java.util.HashMap; |
33 | 34 | import java.util.HashSet; |
222 | 223 | } |
223 | 224 | |
224 | 225 | private static Set<String> getValidOptions(PrintStream err) { |
225 | String path = "/help/en/options"; | |
226 | try (InputStream stream = Main.class.getResourceAsStream(path)) { | |
226 | try (InputStream stream = Main.class.getResourceAsStream("/help/en/options")) { | |
227 | 227 | if (stream == null) |
228 | return null; | |
229 | ||
230 | Set<String> result = new HashSet<>(); | |
228 | return Collections.emptySet(); | |
229 | ||
230 | Set<String> knownOptions = new HashSet<>(); | |
231 | 231 | BufferedReader r = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); |
232 | 232 | |
233 | 233 | Pattern p = Pattern.compile("^--?([a-zA-Z0-9-]*).*$"); |
235 | 235 | while ((line = r.readLine()) != null) { |
236 | 236 | Matcher matcher = p.matcher(line); |
237 | 237 | if (matcher.matches()) { |
238 | String opt = matcher.group(1); | |
239 | result.add(opt); | |
238 | knownOptions.add(matcher.group(1)); | |
240 | 239 | } |
241 | 240 | } |
242 | return result; | |
241 | return knownOptions; | |
243 | 242 | |
244 | 243 | } catch (IOException e) { |
245 | 244 | err.println("Could not read valid options"); |
246 | return null; | |
245 | return Collections.emptySet(); | |
247 | 246 | } |
248 | 247 | } |
249 | 248 | |
283 | 282 | args.setSort(getSort(args)); |
284 | 283 | |
285 | 284 | log.info("Submitting job " + filename); |
286 | FilenameTask task = new FilenameTask(new Callable<String>() { | |
287 | public String call() { | |
288 | log.threadTag(filename); | |
289 | if (filename.startsWith("test-map:") || new File(filename).exists()){ | |
290 | String output = mp.makeMap(args, filename); | |
291 | log.debug("adding output name", output); | |
292 | log.threadTag(null); | |
293 | return output; | |
294 | } else { | |
295 | log.error("file " + filename + " doesn't exist"); | |
296 | return null; | |
297 | } | |
285 | FilenameTask task = new FilenameTask(() -> { | |
286 | log.threadTag(filename); | |
287 | if (filename.startsWith("test-map:") || new File(filename).exists()){ | |
288 | String output = mp.makeMap(args, filename); | |
289 | log.debug("adding output name", output); | |
290 | log.threadTag(null); | |
291 | return output; | |
292 | } else { | |
293 | log.error("input file '" + filename + "' doesn't exist"); | |
294 | return null; | |
298 | 295 | } |
299 | 296 | }); |
300 | 297 | task.setArgs(args); |
317 | 314 | // This option always appears first. We use it to turn on/off |
318 | 315 | // generation of the overview files if there is only one file |
319 | 316 | // to process. |
320 | int n = Integer.valueOf(val); | |
317 | int n = Integer.parseInt(val); | |
321 | 318 | if (n > 0) // TODO temporary, this option will become properly default of on. |
322 | 319 | createTdbFiles = true; |
323 | 320 | |
361 | 358 | } |
362 | 359 | |
363 | 360 | public void removeOption(String opt) { |
364 | if (Objects.equals("tdbfile", opt)) | |
361 | if ("tdbfile".equals(opt)) | |
365 | 362 | createTdbFiles = false; |
366 | 363 | } |
367 | 364 | |
438 | 435 | } |
439 | 436 | ++checked; |
440 | 437 | boolean performChecks = true; |
441 | if (Objects.equals("classpath:styles", styleFile) && !Objects.equals("default", name)){ | |
442 | performChecks = false; | |
438 | if ("classpath:styles".equals(styleFile) && !"default".equals(name)) { | |
439 | performChecks = false; | |
443 | 440 | } |
444 | 441 | Style style = readOneStyle(name, performChecks); |
445 | 442 | if (style == null){ |
547 | 544 | // save the result for later use |
548 | 545 | future.setFilename(future.get()); |
549 | 546 | filenames.add(future); |
550 | } else | |
547 | } else { | |
551 | 548 | Thread.sleep(100); |
549 | } | |
552 | 550 | } catch (ExecutionException e) { |
553 | 551 | // Re throw the underlying exception |
554 | 552 | Throwable cause = e.getCause(); |
691 | 689 | } |
692 | 690 | } |
693 | 691 | |
692 | ||
694 | 693 | private void fileOptions(CommandArgs args) { |
695 | 694 | boolean indexOpt = args.exists("index"); |
696 | 695 | boolean gmapsuppOpt = args.exists("gmapsupp"); |
44 | 44 | drawStreetnames(map, div, lat, lng); |
45 | 45 | } |
46 | 46 | |
47 | private void drawStreetnames(Map map, Subdivision div, double slat, double slon) { | |
47 | private static void drawStreetnames(Map map, Subdivision div, double slat, double slon) { | |
48 | 48 | |
49 | 49 | char[] hexChars = "0123456789ABCDEF".toCharArray(); |
50 | 50 |
63 | 63 | String fname = OverviewBuilder.getOverviewImgName(args.getMapname()); |
64 | 64 | |
65 | 65 | File f = new File(args.getOutputDir(), fname); |
66 | if (f.exists() && f.isFile()) { | |
67 | try { | |
68 | Files.delete(f.toPath()); | |
69 | log.warn("removed " + f); | |
70 | } catch (IOException e) { | |
71 | log.warn("removing " + f + "failed with " + e.getMessage()); | |
72 | } | |
73 | } | |
66 | tryRemove(f); | |
74 | 67 | } |
75 | 68 | } |
76 | 69 | return makeMap(args, src, ""); |
81 | 74 | } catch (FileNotFoundException e) { |
82 | 75 | System.err.println("Could not open file: " + filename); |
83 | 76 | return filename; |
77 | } | |
78 | } | |
79 | ||
80 | private static void tryRemove(File f) { | |
81 | if (f.exists() && f.isFile()) { | |
82 | try { | |
83 | Files.delete(f.toPath()); | |
84 | log.warn("removed " + f); | |
85 | } catch (IOException e) { | |
86 | log.warn("removing " + f + "failed with " + e.getMessage()); | |
87 | } | |
84 | 88 | } |
85 | 89 | } |
86 | 90 | |
148 | 152 | * @throws FileNotFoundException For non existing files. |
149 | 153 | * @throws FormatException When the file format is not valid. |
150 | 154 | */ |
151 | private static LoadableMapDataSource loadFromFile(CommandArgs args, String name) throws | |
152 | FileNotFoundException, FormatException | |
153 | { | |
155 | private static LoadableMapDataSource loadFromFile(CommandArgs args, String name) throws FileNotFoundException { | |
154 | 156 | LoadableMapDataSource src = MapReader.createMapReader(name); |
155 | 157 | src.config(args.getProperties()); |
156 | 158 | log.info("Started loading", name); |
157 | src.load(name, args.getProperties().getProperty("transparent", false) == false); | |
159 | src.load(name, !args.getProperties().getProperty("transparent", false)); | |
158 | 160 | log.info("Finished loading", name); |
159 | 161 | return src; |
160 | 162 | } |
32 | 32 | * @param filename The input filename. |
33 | 33 | * @return The output filename; the name of the file that was created. |
34 | 34 | */ |
35 | public String makeMap(CommandArgs args, String filename); | |
35 | String makeMap(CommandArgs args, String filename); | |
36 | 36 | } |
13 | 13 | package uk.me.parabola.mkgmap.main; |
14 | 14 | |
15 | 15 | import java.io.BufferedReader; |
16 | import java.io.FileInputStream; | |
16 | 17 | import java.io.FileNotFoundException; |
17 | import java.io.FileInputStream; | |
18 | import java.io.InputStreamReader; | |
19 | import java.nio.charset.StandardCharsets; | |
20 | 18 | import java.io.FileWriter; |
21 | 19 | import java.io.IOException; |
22 | 20 | import java.io.InputStream; |
21 | import java.io.InputStreamReader; | |
23 | 22 | import java.io.PrintStream; |
24 | 23 | import java.io.PrintWriter; |
25 | 24 | import java.io.Reader; |
26 | 25 | import java.lang.reflect.Field; |
26 | import java.nio.charset.StandardCharsets; | |
27 | 27 | import java.util.ArrayList; |
28 | 28 | import java.util.Arrays; |
29 | 29 | import java.util.Collections; |
30 | import java.util.Formatter; | |
31 | 30 | import java.util.List; |
32 | 31 | import java.util.Locale; |
33 | 32 | import java.util.Objects; |
168 | 167 | showMatches = true; |
169 | 168 | } else if (s.startsWith("--no-print")) { |
170 | 169 | print = false; |
171 | } else | |
170 | } else { | |
172 | 171 | a.add(s); |
172 | } | |
173 | 173 | } |
174 | 174 | return a.toArray(new String[a.size()]); |
175 | 175 | } |
238 | 238 | List<String> actual = new ArrayList<>(); |
239 | 239 | List<String> expected = new ArrayList<>(); |
240 | 240 | for (Way w : ways) { |
241 | OsmConverter normal = new StyleTester("styletester.style", new LocalMapCollector(results), false); | |
241 | OsmConverter normal = new StyleTester(STYLETESTER_STYLE, new LocalMapCollector(results), false); | |
242 | 242 | |
243 | 243 | String prefix = "WAY " + w.getId() + ": "; |
244 | 244 | normal.convertWay(w.copy()); |
247 | 247 | results.clear(); |
248 | 248 | |
249 | 249 | if (!noStrict) { |
250 | OsmConverter strict = new StyleTester("styletester.style", new LocalMapCollector(strictResults), true); | |
250 | OsmConverter strict = new StyleTester(STYLETESTER_STYLE, new LocalMapCollector(strictResults), true); | |
251 | 251 | strict.convertWay(w.copy()); |
252 | 252 | strict.end(); |
253 | 253 | expected.addAll(formatResults(prefix, strictResults)); |
294 | 294 | return givenResults; |
295 | 295 | } |
296 | 296 | |
297 | @Override | |
297 | 298 | public void convertWay(Way way) { |
298 | 299 | converter.convertWay(way); |
299 | 300 | } |
300 | 301 | |
302 | @Override | |
301 | 303 | public void convertNode(Node node) { |
302 | 304 | converter.convertNode(node); |
303 | 305 | } |
304 | 306 | |
307 | @Override | |
305 | 308 | public void convertRelation(Relation relation) { |
306 | 309 | converter.convertRelation(relation); |
307 | 310 | } |
308 | 311 | |
312 | @Override | |
309 | 313 | public void setBoundingBox(Area bbox) { |
310 | 314 | converter.setBoundingBox(bbox); |
311 | 315 | } |
312 | 316 | |
317 | @Override | |
313 | 318 | public void end() { |
314 | 319 | converter.end(); |
315 | } | |
316 | ||
317 | @Override | |
318 | public Boolean getDriveOnLeft() { | |
319 | return null; // unknown | |
320 | 320 | } |
321 | 321 | |
322 | 322 | private static void printResult(String prefix, List<String> results) { |
342 | 342 | // read the rest of the file |
343 | 343 | readStyles(br, line); |
344 | 344 | } |
345 | /*else if ("".equals(line) || line.startsWith("#")) { | |
346 | // ignore blank lines. | |
347 | }*/ | |
348 | 345 | } |
349 | 346 | |
350 | 347 | return ways; |
408 | 405 | * toString methods on MapLine and MapRoad. |
409 | 406 | */ |
410 | 407 | private static String lineToString(MapLine el) { |
411 | Formatter fmt = new Formatter(); | |
412 | fmt.format("Line 0x%x, labels=%s, res=%d-%d", | |
408 | StringBuilder sb = new StringBuilder(); | |
409 | sb.append(String.format("Line 0x%x, labels=%s, res=%d-%d", | |
413 | 410 | el.getType(), Arrays.toString(el.getLabels()), |
414 | el.getMinResolution(), el.getMaxResolution()); | |
411 | el.getMinResolution(), el.getMaxResolution())); | |
415 | 412 | if (el.isDirection()) |
416 | fmt.format(" oneway"); | |
417 | ||
418 | fmt.format(" "); | |
413 | sb.append(" oneway"); | |
414 | ||
415 | sb.append(' '); | |
419 | 416 | for (Coord co : el.getPoints()) |
420 | fmt.format("(%s),", co); | |
421 | ||
422 | return fmt.toString(); | |
417 | sb.append(String.format("(%s),", co)); | |
418 | ||
419 | return sb.toString(); | |
423 | 420 | } |
424 | 421 | |
425 | 422 | /** |
427 | 424 | * toString methods on MapLine and MapRoad. |
428 | 425 | */ |
429 | 426 | private static String roadToString(MapRoad el) { |
430 | StringBuffer sb = new StringBuffer(lineToString(el)); | |
431 | sb.delete(0, 4); | |
432 | sb.insert(0, "Road"); | |
433 | Formatter fmt = new Formatter(sb); | |
434 | fmt.format(" road class=%d speed=%d", el.getRoadDef().getRoadClass(), | |
435 | getRoadSpeed(el.getRoadDef())); | |
436 | return fmt.toString(); | |
427 | StringBuilder sb = new StringBuilder(lineToString(el)); | |
428 | sb.replace(0, 4, "Road"); | |
429 | sb.append(String.format(" road class=%d speed=%d", el.getRoadDef().getRoadClass(), | |
430 | getRoadSpeed(el.getRoadDef()))); | |
431 | return sb.toString(); | |
437 | 432 | } |
438 | 433 | |
439 | 434 | /** |
483 | 478 | * @param coll A map collector to receive the created elements. |
484 | 479 | |
485 | 480 | */ |
486 | private StyledConverter makeStyleConverter(String styleFile, MapCollector coll) throws FileNotFoundException { | |
481 | private static StyledConverter makeStyleConverter(String styleFile, MapCollector coll) throws FileNotFoundException { | |
487 | 482 | Style style = new StyleImpl(styleFile, null); |
488 | 483 | return new StyledConverter(style, coll, new EnhancedProperties()); |
489 | 484 | } |
545 | 540 | * @return A simple list of rules with a resolving method that applies |
546 | 541 | * each rule in turn to the element until there is match. |
547 | 542 | */ |
543 | @Override | |
548 | 544 | public Rule getWayRules() { |
549 | 545 | ReferenceRuleSet r = new ReferenceRuleSet(); |
550 | 546 | r.addAll((ReferenceRuleSet) getLineRules()); |
559 | 555 | * |
560 | 556 | * @return A Reference rule set of the lines. |
561 | 557 | */ |
558 | @Override | |
562 | 559 | public Rule getLineRules() { |
563 | 560 | ReferenceRuleSet r = new ReferenceRuleSet(); |
564 | 561 | |
579 | 576 | * |
580 | 577 | * @return A Reference rule set of the polygons. |
581 | 578 | */ |
579 | @Override | |
582 | 580 | public Rule getPolygonRules() { |
583 | 581 | ReferenceRuleSet r = new ReferenceRuleSet(); |
584 | 582 | |
592 | 590 | return r; |
593 | 591 | } |
594 | 592 | |
593 | @Override | |
595 | 594 | public Rule getRelationRules() { |
596 | 595 | ReferenceRuleSet r = new ReferenceRuleSet(); |
597 | 596 | |
605 | 604 | return r; |
606 | 605 | } |
607 | 606 | |
607 | @Override | |
608 | 608 | public Set<String> getUsedTags() { |
609 | 609 | return null; |
610 | 610 | } |
646 | 646 | if (showMatches) { |
647 | 647 | if (a.isFound()) { |
648 | 648 | out.println("# Matched: " + rule); |
649 | } else if (a.isActionsOnly()) | |
649 | } else if (a.isActionsOnly()) { | |
650 | 650 | out.println("# Matched for actions: " + rule); |
651 | } | |
651 | 652 | } |
652 | 653 | |
653 | 654 | if (a.isResolved()) |
693 | 694 | if (rule.containsExpression(exp)) |
694 | 695 | return true; |
695 | 696 | } |
696 | if (getFinalizeRule()!= null && getFinalizeRule().containsExpression(exp)) | |
697 | return true; | |
698 | return false; | |
697 | return getFinalizeRule()!= null && getFinalizeRule().containsExpression(exp); | |
699 | 698 | } |
700 | 699 | } |
701 | 700 |
74 | 74 | * @param n The number of characters in the first block. The minimum size of the TYP file is |
75 | 75 | * less than the buffer size. |
76 | 76 | */ |
77 | private void writeAlteredTyp(String outFilename, FileInputStream in, byte[] buf, int n) { | |
77 | private static void writeAlteredTyp(String outFilename, FileInputStream in, byte[] buf, int n) { | |
78 | 78 | try (FileOutputStream out = new FileOutputStream(outFilename)) { |
79 | 79 | do { |
80 | 80 | out.write(buf, 0, n); |
96 | 96 | * @param path The original name |
97 | 97 | * @return The modified name. |
98 | 98 | */ |
99 | private String makeOutName(String path) { | |
99 | private static String makeOutName(String path) { | |
100 | 100 | File f = new File(path); |
101 | 101 | File dir = f.getParentFile(); |
102 | 102 |
82 | 82 | } |
83 | 83 | |
84 | 84 | // an action will be performed, so we may have to invalidate the cache |
85 | boolean invalidate_cache = false; | |
86 | for (Action a : actions){ | |
87 | if (a.perform(element)){ | |
88 | invalidate_cache = true; | |
89 | } | |
85 | boolean invalidateCache = false; | |
86 | for (Action a : actions) { | |
87 | invalidateCache |= a.perform(element); | |
90 | 88 | } |
91 | if (invalidate_cache) | |
89 | if (invalidateCache) | |
92 | 90 | cacheId++; |
93 | 91 | |
94 | 92 | if (type != null && finalizeRule != null) { |
142 | 140 | } |
143 | 141 | |
144 | 142 | public String toString() { |
145 | StringBuilder fmt = new StringBuilder(); | |
143 | StringBuilder sb = new StringBuilder(); | |
146 | 144 | if (expression != null) |
147 | fmt.append(expression); | |
145 | sb.append(expression); | |
148 | 146 | |
149 | fmt.append(" {"); | |
147 | sb.append(" {"); | |
150 | 148 | for (Action a : actions) |
151 | fmt.append(a); | |
152 | fmt.append("}"); | |
149 | sb.append(a); | |
150 | sb.append("}"); | |
153 | 151 | |
154 | 152 | if (type != null) { |
155 | fmt.append(' '); | |
156 | fmt.append(type); | |
153 | sb.append(' '); | |
154 | sb.append(type); | |
157 | 155 | } |
158 | 156 | |
159 | return fmt.toString(); | |
157 | return sb.toString(); | |
160 | 158 | } |
161 | 159 | |
162 | 160 | public void setFinalizeRule(Rule finalizeRule) { |
50 | 50 | public class CombinedStyleFileLoader extends StyleFileLoader { |
51 | 51 | private static final Logger log = Logger.getLogger(CombinedStyleFileLoader.class); |
52 | 52 | |
53 | private final Map<String, String> files = new HashMap<String, String>(); | |
53 | private final Map<String, String> files = new HashMap<>(); | |
54 | 54 | private final String styleName; |
55 | 55 | private static final Pattern STYLE_SUFFIX = Pattern.compile("\\.style$"); |
56 | 56 | private static final Pattern FILENAME_START_MARK = Pattern.compile("<<<"); |
71 | 71 | private void loadFiles(Reader in) { |
72 | 72 | BufferedReader r = new BufferedReader(in); |
73 | 73 | |
74 | StringBuffer currentFile = new StringBuffer(); | |
74 | StringBuilder currentFile = new StringBuilder(); | |
75 | 75 | try { |
76 | 76 | String line; |
77 | 77 | String currentName = null; |
86 | 86 | line = FILENAME_END_MARK.matcher(line).replaceFirst(""); |
87 | 87 | log.debug("reading file", line); |
88 | 88 | currentName = line; |
89 | currentFile = new StringBuffer(); | |
89 | currentFile = new StringBuilder(); | |
90 | 90 | } else { |
91 | currentFile.append(line); | |
92 | currentFile.append('\n'); | |
91 | currentFile.append(line).append('\n'); | |
93 | 92 | } |
94 | 93 | } |
95 | 94 | if (currentName == null) { |
169 | 168 | } |
170 | 169 | |
171 | 170 | private static void convertToDirectory(String name, String dirname) throws IOException { |
172 | CombinedStyleFileLoader loader = new CombinedStyleFileLoader(name); | |
173 | File dir = new File(dirname); | |
174 | dir.mkdir(); | |
175 | for (String s : loader.files.keySet()) { | |
176 | File ent = new File(dir, s); | |
177 | ent.getParentFile().mkdirs(); | |
178 | FileWriter writer = new FileWriter(ent); | |
179 | BufferedReader r = null; | |
180 | try { | |
181 | r = new BufferedReader(loader.open(s)); | |
182 | String line; | |
183 | while ((line = r.readLine()) != null) { | |
184 | writer.write(line); | |
185 | writer.write('\n'); | |
186 | } | |
187 | } finally { | |
188 | if (r != null) r.close(); | |
189 | writer.close(); | |
190 | } | |
191 | } | |
192 | loader.close(); | |
171 | try (CombinedStyleFileLoader loader = new CombinedStyleFileLoader(name)) { | |
172 | File dir = new File(dirname); | |
173 | dir.mkdir(); | |
174 | for (String s : loader.files.keySet()) { | |
175 | File ent = new File(dir, s); | |
176 | ent.getParentFile().mkdirs(); | |
177 | try (FileWriter writer = new FileWriter(ent); | |
178 | BufferedReader r = new BufferedReader(loader.open(s))) { | |
179 | String line; | |
180 | while ((line = r.readLine()) != null) { | |
181 | writer.write(line); | |
182 | writer.write('\n'); | |
183 | } | |
184 | } | |
185 | } | |
186 | } | |
193 | 187 | } |
194 | 188 | |
195 | 189 | private static void convertToFile(File file, PrintStream out) throws IOException { |
208 | 202 | out.print(entry.getName()); |
209 | 203 | out.println(">>>"); |
210 | 204 | |
211 | BufferedReader r = new BufferedReader(new FileReader(entry)); | |
212 | String line; | |
213 | while ((line = r.readLine()) != null) | |
214 | out.println(line); | |
215 | r.close(); | |
205 | try (BufferedReader r = new BufferedReader(new FileReader(entry))) { | |
206 | String line; | |
207 | while ((line = r.readLine()) != null) | |
208 | out.println(line); | |
209 | } | |
216 | 210 | } else { |
217 | 211 | convertToFile(out, entry.listFiles(new NoHiddenFilter()), entry.getName()); |
218 | 212 | } |
50 | 50 | this.way = way; |
51 | 51 | this.gt = type; |
52 | 52 | // note that the gt.getType() may not be a routable type when overlays are used |
53 | if (type.isRoad() && MapObject.hasExtendedType(gt.getType()) == false) { | |
53 | if (type.isRoad() && !MapObject.hasExtendedType(gt.getType())) { | |
54 | 54 | this.roadClass = (byte) gt.getRoadClass(); |
55 | 55 | this.roadSpeed = (byte) gt.getRoadSpeed(); |
56 | 56 | recalcRoadClass(way); |
121 | 121 | * @param el an element |
122 | 122 | * @return {@code true} the road class has been changed, else {@code false} |
123 | 123 | */ |
124 | private final static short roadClassTagKey = TagDict.getInstance().xlate("mkgmap:road-class"); | |
124 | private static final short TKM_ROAD_CLASS = TagDict.getInstance().xlate("mkgmap:road-class"); | |
125 | 125 | public boolean recalcRoadClass(Element el) { |
126 | 126 | // save the original road class value |
127 | 127 | byte oldRoadClass = roadClass; |
128 | 128 | |
129 | String val = el.getTag(roadClassTagKey); | |
129 | String val = el.getTag(TKM_ROAD_CLASS); | |
130 | 130 | if (val != null) { |
131 | 131 | if (val.startsWith("-")) { |
132 | 132 | roadClass -= Byte.decode(val.substring(1)); |
166 | 166 | * @param el an element |
167 | 167 | * @return {@code true} the road speed has been changed, else {@code false} |
168 | 168 | */ |
169 | private final static short roadSpeedTagKey = TagDict.getInstance().xlate("mkgmap:road-speed"); | |
170 | private final static short roadSpeedClassTagKey = TagDict.getInstance().xlate("mkgmap:road-speed-class"); | |
169 | private static final short TKM_ROAD_SPEED = TagDict.getInstance().xlate("mkgmap:road-speed"); | |
170 | private static final short TKM_ROAD_SPEED_CLASS = TagDict.getInstance().xlate("mkgmap:road-speed-class"); | |
171 | 171 | public boolean recalcRoadSpeed(Element el) { |
172 | 172 | // save the original road speed value |
173 | 173 | byte oldRoadSpeed = roadSpeed; |
174 | 174 | |
175 | 175 | // check if the road speed is modified |
176 | String roadSpeedOverride = el.getTag(roadSpeedClassTagKey); | |
176 | String roadSpeedOverride = el.getTag(TKM_ROAD_SPEED_CLASS); | |
177 | 177 | if (roadSpeedOverride != null) { |
178 | 178 | try { |
179 | 179 | byte rs = Byte.decode(roadSpeedOverride); |
193 | 193 | } |
194 | 194 | |
195 | 195 | // check if the road speed should be modified more |
196 | String val = el.getTag(roadSpeedTagKey); | |
196 | String val = el.getTag(TKM_ROAD_SPEED); | |
197 | 197 | if(val != null) { |
198 | 198 | if(val.startsWith("-")) { |
199 | 199 | roadSpeed -= Byte.decode(val.substring(1)); |
227 | 227 | } |
228 | 228 | |
229 | 229 | public boolean isValid() { |
230 | if (way == null) | |
231 | return false; | |
232 | if (way.getPoints() == null || way.getPoints().size()<2) | |
233 | return false; | |
234 | return true; | |
230 | return way != null && way.getPoints().size() >= 2; | |
235 | 231 | } |
236 | 232 | |
237 | 233 | public String toString(){ |
60 | 60 | * Nothing needs doing in this case. |
61 | 61 | */ |
62 | 62 | public void close() { |
63 | // Nothing to do | |
63 | 64 | } |
64 | 65 | |
65 | 66 | public String[] list() { |
66 | 67 | log.debug("dir list", dir); |
67 | List<String> res = new ArrayList<String>(); | |
68 | List<String> res = new ArrayList<>(); | |
68 | 69 | |
69 | 70 | File[] allFiles = dir.listFiles(); |
70 | 71 | if (allFiles != null) { |
145 | 145 | * |
146 | 146 | * The output is (a & c) | (b & c) | ... |
147 | 147 | */ |
148 | private Op distribute(Op op) { | |
148 | private static Op distribute(Op op) { | |
149 | 149 | Op ab = op.getFirst(); |
150 | 150 | Op a = ab.getFirst(); |
151 | 151 | Op b = ab.getSecond(); |
179 | 179 | /** |
180 | 180 | * Order the child nodes so that the 'best' one is on the left (first). |
181 | 181 | */ |
182 | private void orderBest(Op op) { | |
182 | private static void orderBest(Op op) { | |
183 | 183 | assert OPERATORS.contains(op.getType()); |
184 | 184 | |
185 | 185 | if (leftNodeWeight(op.getFirst()) > leftNodeWeight(op.getSecond())) { |
192 | 192 | * |
193 | 193 | * We prefer AND to OR and prefer everything else to AND. |
194 | 194 | */ |
195 | private int leftNodeWeight(Op op) { | |
195 | private static int leftNodeWeight(Op op) { | |
196 | 196 | switch (op.getType()) { |
197 | 197 | case AND: return 10; |
198 | 198 | case OR: return 20; |
205 | 205 | * |
206 | 206 | * Each node that is preceded by NOT is inverted. |
207 | 207 | */ |
208 | private Op removeAllNot(Op expr) { | |
208 | private static Op removeAllNot(Op expr) { | |
209 | 209 | if (expr == null) |
210 | 210 | return null; |
211 | 211 | |
231 | 231 | * @param op This will be a NOT node. |
232 | 232 | * @return A new expression, could be the same as given. |
233 | 233 | */ |
234 | private Op removeNot(Op op) { | |
234 | private static Op removeNot(Op op) { | |
235 | 235 | return invert(op.getFirst()); |
236 | 236 | } |
237 | 237 | |
238 | 238 | /** |
239 | 239 | * Invert an expression, ie apply NOT to it. |
240 | 240 | */ |
241 | private Op invert(Op op) { | |
241 | private static Op invert(Op op) { | |
242 | 242 | switch (op.getType()) { |
243 | 243 | case NOT: |
244 | 244 | Op f = op.getFirst(); |
282 | 282 | case FUNCTION: |
283 | 283 | case OPEN_PAREN: |
284 | 284 | case CLOSE_PAREN: |
285 | default: | |
285 | 286 | throw new ExitException("Programming error, tried to invert invalid node " + op); |
286 | 287 | } |
287 | return null; | |
288 | 288 | } |
289 | 289 | |
290 | 290 | /** |
293 | 293 | * This is used when inverting nodes because !(a>0) is (a<=0 | a!=*) because |
294 | 294 | * the statement is true when the tag does not exist. |
295 | 295 | */ |
296 | private Op neWith(Op op) { | |
296 | private static Op neWith(Op op) { | |
297 | 297 | return new OrOp().set( |
298 | 298 | new NotExistsOp().setFirst(op.getFirst()), |
299 | 299 | op |
305 | 305 | * |
306 | 306 | * Eg: given (A&B)&(C&D) we return (A&(B&(C&D))) |
307 | 307 | */ |
308 | private void reAssociate(Op op, NodeType kind) { | |
308 | private static void reAssociate(Op op, NodeType kind) { | |
309 | 309 | assert op.isType(kind); |
310 | 310 | assert kind == OR || kind == AND; |
311 | 311 | |
331 | 331 | * If any of A,B.. happen to be AND terms, then these are merged into the |
332 | 332 | * chain first. If there is an OR on the left it will float to the back. |
333 | 333 | */ |
334 | private void arrangeAndChain(Op op) { | |
334 | private static void arrangeAndChain(Op op) { | |
335 | 335 | Op last = op; |
336 | 336 | List<Op> terms = new ArrayList<>(); |
337 | 337 | terms.add(op.getFirst()); |
352 | 352 | } |
353 | 353 | |
354 | 354 | if (terms.size() > 1) |
355 | terms.sort(Comparator.comparingInt(this::selectivity)); | |
355 | terms.sort(Comparator.comparingInt(ExpressionArranger::selectivity)); | |
356 | 356 | |
357 | 357 | Op current = op; |
358 | 358 | for (Op o : terms) { |
375 | 375 | * Ideally you might want to consider tag frequencies, since highway is a very |
376 | 376 | * common tag, it would be better to push it behind other tags. |
377 | 377 | */ |
378 | private int selectivity(Op op) { | |
378 | private static int selectivity(Op op) { | |
379 | 379 | // Operations that involve a non-indexable function must always go to the back. |
380 | 380 | if (op.getFirst().isType(FUNCTION)) { |
381 | 381 | StyleFunction func = (StyleFunction) op.getFirst(); |
16 | 16 | package uk.me.parabola.mkgmap.osmstyle; |
17 | 17 | |
18 | 18 | import java.io.BufferedReader; |
19 | import java.io.Closeable; | |
19 | 20 | import java.io.FileNotFoundException; |
20 | 21 | import java.io.IOException; |
21 | 22 | import java.io.InputStream; |
45 | 46 | * |
46 | 47 | * @author Steve Ratcliffe |
47 | 48 | */ |
48 | public class JarFileLoader extends StyleFileLoader { | |
49 | public class JarFileLoader extends StyleFileLoader implements Closeable { | |
49 | 50 | private static final Logger log = Logger.getLogger(JarFileLoader.class); |
50 | 51 | private JarFile jarFile; |
51 | 52 | private String prefix; |
62 | 63 | } |
63 | 64 | } |
64 | 65 | |
65 | private String makeJarUrl(String url) { | |
66 | private static String makeJarUrl(String url) { | |
66 | 67 | if (url.toLowerCase().startsWith("jar:")) |
67 | 68 | return url; |
68 | 69 | else |
92 | 93 | * @param style a style name or null to find any version file |
93 | 94 | * @return return prefix of (first) entry that contains file version |
94 | 95 | */ |
95 | private String searchVersion(JarFile file, String style) { | |
96 | private static String searchVersion(JarFile file, String style) { | |
96 | 97 | Enumeration<JarEntry> en = file.entries(); |
97 | 98 | String flatEnd = style==null ? "version" : style + "/version"; |
98 | 99 | String end = "/" + flatEnd; |
142 | 143 | } |
143 | 144 | } |
144 | 145 | |
145 | protected void finalize() throws Throwable { | |
146 | super.finalize(); | |
147 | close(); | |
148 | } | |
149 | ||
150 | 146 | public String[] list() { |
151 | 147 | Enumeration<JarEntry> en = jarFile.entries(); |
152 | List<String> list = new ArrayList<String>(); | |
148 | List<String> list = new ArrayList<>(); | |
153 | 149 | while (en.hasMoreElements()) { |
154 | 150 | JarEntry entry = en.nextElement(); |
155 | 151 |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.mkgmap.osmstyle; |
13 | 13 | |
14 | import java.util.Arrays; | |
15 | 14 | import java.util.List; |
16 | 15 | import java.util.Properties; |
17 | import java.util.regex.Pattern; | |
18 | 16 | |
19 | 17 | import it.unimi.dsi.fastutil.shorts.ShortArrayList; |
18 | import uk.me.parabola.mkgmap.CommandArgs; | |
20 | 19 | import uk.me.parabola.mkgmap.reader.osm.Element; |
21 | 20 | import uk.me.parabola.mkgmap.reader.osm.TagDict; |
22 | 21 | import uk.me.parabola.mkgmap.reader.osm.Tags; |
29 | 28 | public class NameFinder { |
30 | 29 | private final ShortArrayList compiledNameTagList; |
31 | 30 | |
32 | private static final Pattern COMMA_OR_SPACE_PATTERN = Pattern.compile("[,\\s]+"); | |
33 | private static final short nameTagKey = TagDict.getInstance().xlate("name"); | |
31 | private static final short TK_NAME = TagDict.getInstance().xlate("name"); | |
34 | 32 | |
35 | 33 | public NameFinder(Properties props) { |
36 | 34 | this.compiledNameTagList = computeCompiledNameTags(props); |
37 | } | |
38 | ||
39 | public static List<String> getNameTags(Properties props) { | |
40 | String nameTagProp = props.getProperty("name-tag-list", "name"); | |
41 | return Arrays.asList(COMMA_OR_SPACE_PATTERN.split(nameTagProp)); | |
42 | 35 | } |
43 | 36 | |
44 | 37 | /** |
49 | 42 | private static ShortArrayList computeCompiledNameTags(Properties props) { |
50 | 43 | if (props == null) |
51 | 44 | return null; |
52 | String nameTagProp = props.getProperty("name-tag-list", "name"); | |
53 | if ("name".equals(nameTagProp)) | |
45 | List<String> nametags = CommandArgs.getNameTags(props); | |
46 | if (nametags.size() == 1 && "name".equals(nametags.get(0))) | |
54 | 47 | return null; |
55 | return TagDict.compileTags(COMMA_OR_SPACE_PATTERN.split(nameTagProp)); | |
48 | return TagDict.compileTags(nametags.toArray(new String[0])); | |
56 | 49 | } |
57 | 50 | |
58 | 51 | |
63 | 56 | */ |
64 | 57 | public String getName(Element el) { |
65 | 58 | if (compiledNameTagList == null) |
66 | return el.getTag(nameTagKey); | |
59 | return el.getTag(TK_NAME); | |
67 | 60 | |
68 | 61 | for (short tagKey : compiledNameTagList) { |
69 | 62 | String val = el.getTag(tagKey); |
81 | 74 | */ |
82 | 75 | public String getName(Tags tags) { |
83 | 76 | if (compiledNameTagList == null) |
84 | return tags.get(nameTagKey); | |
77 | return tags.get(TK_NAME); | |
85 | 78 | |
86 | 79 | for (short tagKey : compiledNameTagList) { |
87 | 80 | String val = tags.get(tagKey); |
103 | 96 | for (short tagKey : compiledNameTagList) { |
104 | 97 | String val = el.getTag(tagKey); |
105 | 98 | if (val != null) { |
106 | if (tagKey != nameTagKey) { | |
99 | if (tagKey != TK_NAME) { | |
107 | 100 | // add or replace name |
108 | el.addTag(nameTagKey, val); | |
101 | el.addTag(TK_NAME, val); | |
109 | 102 | } |
110 | 103 | break; |
111 | 104 | } |
31 | 31 | import java.util.Set; |
32 | 32 | import java.util.stream.Collectors; |
33 | 33 | |
34 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; | |
35 | import uk.me.parabola.imgfmt.Utils; | |
34 | 36 | import uk.me.parabola.imgfmt.app.Coord; |
35 | 37 | import uk.me.parabola.log.Logger; |
36 | 38 | import uk.me.parabola.mkgmap.general.MapPoint; |
209 | 211 | default: |
210 | 212 | valid = false; |
211 | 213 | log.error("Invalid Action value", ruleParts[2], "in nearby poi rule", i + 1, rule, |
212 | "- 'delete-poi', 'delete-name' or'merge-at-mid-point' expected."); | |
214 | "- 'delete-poi', or 'delete-name' expected."); | |
213 | 215 | break; |
214 | 216 | } |
215 | 217 | } |
325 | 327 | if (biggestCloud == null || biggestCloud.isEmpty()) |
326 | 328 | break; |
327 | 329 | |
328 | final Coord middle = calcMiddle(biggestCloud); | |
330 | final Coord middle = calcMiddle(biggestCloud).getDisplayedCoord(); | |
329 | 331 | final Set<MapPoint> done = new HashSet<>(biggestCloud); |
330 | 332 | removeSimpleDuplicates(biggestCloud); |
331 | 333 | |
332 | 334 | // select point that is closest to the middle |
333 | 335 | MapPoint bestPoint = biggestCloud.stream() |
334 | .min(Comparator.comparingDouble(mp -> middle.distance(mp.getLocation()))) | |
336 | .min(Comparator.comparingDouble(mp -> middle.distance(mp.getLocation().getDisplayedCoord()))) | |
335 | 337 | .orElse(biggestCloud.iterator().next()); // should not happen, stream is not empty |
336 | 338 | |
337 | 339 | performAction(rule.action, bestPoint, biggestCloud, toKeep); |
345 | 347 | } |
346 | 348 | |
347 | 349 | private void removeSimpleDuplicates(Set<MapPoint> biggestCloud) { |
348 | Set<Coord> locations = new HashSet<>(); | |
350 | Long2ObjectOpenHashMap<MapPoint> locations = new Long2ObjectOpenHashMap<>(); | |
349 | 351 | Iterator<MapPoint> iter = biggestCloud.iterator(); |
350 | 352 | while (iter.hasNext()) { |
351 | 353 | MapPoint mp = iter.next(); |
352 | if (!locations.add(mp.getLocation())) { | |
354 | if (locations.put(Utils.coord2Long(mp.getLocation()), mp) != null) { | |
353 | 355 | if (log.isInfoEnabled()) { |
354 | 356 | log.info("Removed duplicate", getLogInfo(mp)); |
355 | 357 | } |
360 | 362 | |
361 | 363 | private static Map<MapPoint, Set<MapPoint>> buildGroups(List<MapPoint> points, int maxDistance, List<MapPoint> toKeep) { |
362 | 364 | final KdTree<MapPoint> kdTree = new KdTree<>(); |
363 | points.forEach(kdTree::add); | |
365 | points.forEach(kdTree::add); // should better use getDisplayedCoord() | |
364 | 366 | Map<MapPoint, Set<MapPoint>> groupsMap = new LinkedHashMap<>(); |
365 | 367 | for (MapPoint mp : points) { |
366 | 368 | Set<MapPoint> set = kdTree.findClosePoints(mp, maxDistance); |
380 | 382 | final MapPoint midPoint = bestPoint; |
381 | 383 | biggestCloud.stream().filter(mp -> mp != midPoint).forEach(mp -> { |
382 | 384 | if (log.isInfoEnabled()) { |
383 | double dist = mp.getLocation().distance(bestPoint.getLocation()); | |
384 | log.info(String.format("Removed name from nearby(<%d m)", (long) Math.ceil(dist)), getLogInfo(mp)); | |
385 | double dist = mp.getLocation().getDisplayedCoord().distance(bestPoint.getLocation().getDisplayedCoord()); | |
386 | log.info(String.format("Removed name from nearby(<= %d m)", (long) Math.ceil(dist)), getLogInfo(mp)); | |
385 | 387 | } |
386 | 388 | mp.setName(null); |
387 | 389 | }); |
397 | 399 | private void logRemoval(Set<MapPoint> biggestCloud, MapPoint bestPoint) { |
398 | 400 | for (MapPoint mp : biggestCloud) { |
399 | 401 | if (mp != bestPoint) { |
400 | double dist = mp.getLocation().distance(bestPoint.getLocation()); | |
401 | log.info(String.format("Removed nearby (<%d m)", (long) Math.ceil(dist)), getLogInfo(mp)); | |
402 | double dist = mp.getLocation().getDisplayedCoord().distance(bestPoint.getLocation().getDisplayedCoord()); | |
403 | log.info(String.format("Removed nearby (<= %d m)", (long) Math.ceil(dist)), getLogInfo(mp)); | |
402 | 404 | } |
403 | 405 | } |
404 | 406 | } |
409 | 411 | double lon = 0; |
410 | 412 | final int n = points.size(); |
411 | 413 | for (MapPoint mp : points) { |
412 | Coord p = mp.getLocation(); | |
414 | Coord p = mp.getLocation().getDisplayedCoord(); | |
413 | 415 | lat += (double) p.getHighPrecLat() / n; |
414 | 416 | lon += (double) p.getHighPrecLon() / n; |
415 | 417 | } |
428 | 430 | if (n != null) { |
429 | 431 | sb.append(" for element "); |
430 | 432 | if (FakeIdGenerator.isFakeId(n.getId())) { |
431 | sb.append("generated from ").append(n.getOriginalId()); | |
433 | sb.append("generated from ").append(n.getOrigElement()).append(' ').append(n.getOriginalId()); | |
432 | 434 | } else { |
433 | 435 | sb.append(n.toBrowseURL()).append(" at ").append(n.getLocation().toOSMURL()); |
434 | 436 | } |
45 | 45 | * it is replaced by three lines with the type 0x12, 0x14, 0x15. |
46 | 46 | * |
47 | 47 | * @author Steve Ratcliffe |
48 | * @see <a href="TODO: find url">Example of the technique</a> | |
49 | 48 | * |
50 | 49 | */ |
51 | 50 | public class OverlayReader { |
52 | private final Map<Integer, List<Integer>> overlays = new HashMap<Integer, List<Integer>>(); | |
51 | private final Map<Integer, List<Integer>> overlays = new HashMap<>(); | |
53 | 52 | private final Reader reader; |
54 | 53 | private final String filename; |
55 | 54 | |
64 | 63 | String line = ts.readLine(); |
65 | 64 | |
66 | 65 | // Remove comments before parsing |
67 | int commentstart = line.indexOf("#"); | |
66 | int commentstart = line.indexOf('#'); | |
68 | 67 | if (commentstart != -1) |
69 | 68 | line = line.substring(0, commentstart); |
70 | 69 | |
82 | 81 | /** |
83 | 82 | * Read the line of replacements. |
84 | 83 | */ |
85 | private List<Integer> readReplacements(TokenScanner ts, String line) { | |
86 | List<Integer> l = new ArrayList<Integer>(); | |
84 | private static List<Integer> readReplacements(TokenScanner ts, String line) { | |
85 | List<Integer> l = new ArrayList<>(); | |
87 | 86 | |
88 | 87 | String[] nums = line.split("[ ,]"); |
89 | 88 | for (String n : nums) { |
114 | 113 | for (ListIterator<Integer> t=integerList.listIterator(1); t.hasNext(); ) { |
115 | 114 | newline = new MapLine(line); |
116 | 115 | newline.setType(t.next()); |
117 | newline.setPoints(new ArrayList<Coord>(points)); | |
116 | newline.setPoints(new ArrayList<>(points)); | |
118 | 117 | adder.add(newline); |
119 | 118 | } |
120 | 119 | } else { |
20 | 20 | import java.util.Arrays; |
21 | 21 | import java.util.Collections; |
22 | 22 | import java.util.HashMap; |
23 | import java.util.HashSet; | |
24 | 23 | import java.util.LinkedHashSet; |
25 | 24 | import java.util.List; |
26 | 25 | import java.util.Map; |
27 | 26 | import java.util.Set; |
27 | import java.util.stream.Collectors; | |
28 | 28 | |
29 | 29 | import uk.me.parabola.imgfmt.app.mdr.Mdr7; |
30 | 30 | import uk.me.parabola.log.Logger; |
110 | 110 | |
111 | 111 | if (tok.getType() == TokType.SYMBOL) { |
112 | 112 | |
113 | String punc = ts.nextValue(); | |
114 | String val; | |
115 | if (punc.equals(":") || punc.equals("=")) { | |
116 | val = ts.readLine(); | |
117 | } else { | |
113 | switch (ts.nextValue()) { | |
114 | case ":": | |
115 | case "=": | |
116 | processOption(key, ts.readLine()); | |
117 | break; | |
118 | default: | |
118 | 119 | ts.skipLine(); |
119 | continue; | |
120 | 120 | } |
121 | processOption(key, val); | |
121 | ||
122 | 122 | } else if (key != null){ |
123 | 123 | throw new IllegalArgumentException("don't understand line with " + key ); |
124 | 124 | } else { |
125 | 125 | ts.skipLine(); |
126 | 126 | } |
127 | 127 | } |
128 | ||
128 | 129 | /** |
129 | 130 | * process lines starting with prefix1 or prefix2. |
130 | 131 | */ |
133 | 134 | if (prefix1 == null) |
134 | 135 | continue; |
135 | 136 | String prefix2 = options.getProperty("prefix2:" + lang, null); |
136 | List<String> p1 = prefix1 != null ? Arrays.asList(prefix1.split(",")) : Collections.emptyList(); | |
137 | List<String> p1 = Arrays.asList(prefix1.split(",")); | |
137 | 138 | List<String> p2 = prefix2 != null ? Arrays.asList(prefix2.split(",")) : Collections.emptyList(); |
138 | 139 | langPrefixMap.put(lang, genPrefix(p1, p2)); |
139 | 140 | } |
166 | 167 | } |
167 | 168 | countryLanguageMap .put(iso, langs); |
168 | 169 | languages.addAll(langs); |
169 | default: | |
170 | 170 | break; |
171 | 171 | } |
172 | 172 | } |
177 | 177 | * @param prefix2 list of prepositions |
178 | 178 | * @return all combinations |
179 | 179 | */ |
180 | private List<String> genPrefix (List<String> prefix1, List<String> prefix2) { | |
180 | private static List<String> genPrefix (List<String> prefix1, List<String> prefix2) { | |
181 | 181 | List<String> prefixes = new ArrayList<>(); |
182 | 182 | for (String p1 : prefix1) { |
183 | 183 | p1 = stripBlanksAndQuotes(p1); |
187 | 187 | } |
188 | 188 | prefixes.add(p1 + " "); |
189 | 189 | } |
190 | sortByLength(prefixes); | |
190 | 191 | return prefixes; |
191 | 192 | } |
192 | 193 | |
195 | 196 | * @param s the string |
196 | 197 | * @return the modified string |
197 | 198 | */ |
198 | private String stripBlanksAndQuotes(String s) { | |
199 | private static String stripBlanksAndQuotes(String s) { | |
199 | 200 | s = s.trim(); |
200 | 201 | if (s.startsWith("'") && s.endsWith("'") || s.startsWith("\"") && s.endsWith("\"")) { |
201 | 202 | return s.substring(1, s.length()-1); |
220 | 221 | if (country == null) |
221 | 222 | return; |
222 | 223 | |
223 | List<String> prefixesCountry = getSearchStrings(country, MODE_PREFIX); | |
224 | List<String> suffixesCountry = getSearchStrings(country, MODE_SUFFIX); | |
224 | final List<String> prefixesCountry = getSearchStrings(country, MODE_PREFIX); | |
225 | final List<String> suffixesCountry = getSearchStrings(country, MODE_SUFFIX); | |
225 | 226 | |
226 | // perform brute force search, seems to be fast enough | |
227 | 227 | String[] labels = road.getLabels(); |
228 | 228 | for (int i = 0; i < labels.length; i++) { |
229 | 229 | String label = labels[i]; |
230 | if (label == null || label.length() == 0) | |
230 | if (label == null || label.isEmpty()) | |
231 | 231 | continue; |
232 | boolean modified = false; | |
233 | for (String prefix : prefixesCountry) { | |
234 | if (label.charAt(0) < 7) | |
235 | break; // label starts with shield code | |
236 | if (label.length() < prefix.length()) | |
237 | continue; | |
238 | if (prefix.equalsIgnoreCase(label.substring(0, prefix.length()))) { | |
239 | if (prefix.endsWith(" ")) { | |
240 | label = prefix.substring(0, prefix.length() - 1) + (char) 0x1e | |
241 | + label.substring(prefix.length()); | |
242 | } else { | |
243 | label = prefix + (char) 0x1b + label.substring(prefix.length()); | |
244 | } | |
245 | modified = true; | |
246 | break; | |
232 | label = applyPrefixes(label, prefixesCountry); | |
233 | label = applySuffixes(label, suffixesCountry); | |
234 | if (!label.equals(labels[i])) { | |
235 | labels[i] = label; | |
236 | log.debug("modified", label, country, road.getRoadDef()); | |
237 | } | |
238 | } | |
239 | } | |
240 | ||
241 | static String applyPrefixes(String label, List<String> prefixesCountry) { | |
242 | if (label.charAt(0) < 7) | |
243 | return label; // label starts with shield code | |
244 | // perform brute force search, seems to be fast enough | |
245 | for (String prefix : prefixesCountry) { | |
246 | if (label.length() >= prefix.length() && prefix.equalsIgnoreCase(label.substring(0, prefix.length()))) { | |
247 | if (prefix.endsWith(" ")) { | |
248 | return prefix.substring(0, prefix.length() - 1) + (char) 0x1e + label.substring(prefix.length()); | |
247 | 249 | } |
248 | } | |
249 | for (String suffix : suffixesCountry) { | |
250 | int len = label.length(); | |
251 | if (len < suffix.length()) | |
252 | continue; | |
253 | int pos = len - suffix.length(); | |
254 | if (suffix.equalsIgnoreCase(label.substring(pos, len))) { | |
255 | if (suffix.startsWith(" ")) | |
256 | label = label.substring(0, pos) + (char) 0x1f + suffix.substring(1); | |
257 | else | |
258 | label = label.substring(0, pos) + (char) 0x1c + suffix; | |
259 | modified = true; | |
260 | break; | |
250 | return prefix + (char) 0x1b + label.substring(prefix.length()); | |
251 | } | |
252 | } | |
253 | return label; | |
254 | } | |
255 | ||
256 | private static String applySuffixes(String label, List<String> suffixesCountry) { | |
257 | // perform brute force search, seems to be fast enough | |
258 | for (String suffix : suffixesCountry) { | |
259 | int len = label.length(); | |
260 | int pos = len - suffix.length(); | |
261 | if (pos >= 0 && suffix.equalsIgnoreCase(label.substring(pos, len))) { | |
262 | if (suffix.startsWith(" ")) { | |
263 | return label.substring(0, pos) + (char) 0x1f + suffix.substring(1); | |
261 | 264 | } |
262 | } | |
263 | if (modified) { | |
264 | labels[i] = label; | |
265 | log.debug("modified",label,country,road.getRoadDef()); | |
266 | } | |
267 | } | |
268 | } | |
269 | ||
265 | return label.substring(0, pos) + (char) 0x1c + suffix; | |
266 | } | |
267 | } | |
268 | return label; | |
269 | } | |
270 | ||
270 | 271 | /** |
271 | 272 | * Build list of prefixes or suffixes for a given country. |
272 | 273 | * @param country String with 3 letter ISO code |
275 | 276 | */ |
276 | 277 | private List<String> getSearchStrings(String country, int mode) { |
277 | 278 | Map<String, List<String>> cache = (mode == MODE_PREFIX) ? countryPrefixMap : countrySuffixMap; |
278 | List<String> res = cache.get(country); | |
279 | if (res == null) { | |
279 | return cache.computeIfAbsent(country, k-> { | |
280 | 280 | // compile the list |
281 | List<String> languages = countryLanguageMap.get(country); | |
282 | if (languages == null) | |
283 | res = Collections.emptyList(); | |
284 | else { | |
285 | List<List<String>> all = new ArrayList<>(); | |
286 | for (String lang : languages) { | |
287 | List<String> prefixes = mode == MODE_PREFIX ? langPrefixMap.get(lang) : langSuffixMap.get(lang); | |
288 | if(prefixes != null) | |
289 | all.add(prefixes); | |
290 | } | |
291 | if(all.isEmpty()) | |
292 | res = Collections.emptyList(); | |
293 | else if (all.size() == 1) { | |
294 | res = all.get(0); | |
295 | } | |
296 | else { | |
297 | Set<String> allPrefixesSet = new HashSet<>(); | |
298 | for (List<String> prefOneLang : all) | |
299 | allPrefixesSet.addAll(prefOneLang); | |
300 | res = new ArrayList<>(allPrefixesSet); | |
301 | sortByLength(res); | |
302 | ||
303 | } | |
304 | } | |
305 | // cache the result | |
306 | cache.put(country, res); | |
307 | } | |
308 | return res; | |
281 | List<String> languageList = countryLanguageMap.get(country); | |
282 | if (languageList == null) | |
283 | return Collections.emptyList(); | |
284 | final Map<String, List<String>> map = mode == MODE_PREFIX ? langPrefixMap : langSuffixMap; | |
285 | List<String> res = languageList.stream() | |
286 | .map(lang -> map.getOrDefault(lang, Collections.emptyList())) | |
287 | .flatMap(List::stream) | |
288 | .distinct() | |
289 | .collect(Collectors.toList()); | |
290 | if (res.isEmpty()) | |
291 | return Collections.emptyList(); | |
292 | sortByLength(res); | |
293 | return res; | |
294 | }); | |
309 | 295 | } |
310 | 296 | |
311 | 297 | /** |
312 | 298 | * Sort by string length so that longest string comes first. |
313 | 299 | * @param strings |
314 | 300 | */ |
315 | private void sortByLength(List<String> strings) { | |
301 | private static void sortByLength(List<String> strings) { | |
316 | 302 | strings.sort((o1, o2) -> Integer.compare(o2.length(), o1.length())); |
317 | 303 | } |
318 | 304 | } |
315 | 315 | return false; |
316 | 316 | for (String tagKey : evaluated) { |
317 | 317 | for (String s : actionList.getChangeableTags()) { |
318 | int pos = s.indexOf("="); | |
318 | int pos = s.indexOf('='); | |
319 | 319 | String key = pos > 0 ? s.substring(0, pos) : s; |
320 | 320 | if (tagKey.equals(key)) { |
321 | 321 | return true; |
52 | 52 | int cacheId; |
53 | 53 | boolean compiled = false; |
54 | 54 | |
55 | private static final short executeFinalizeRulesTagKey = TagDict.getInstance().xlate("mkgmap:execute_finalize_rules"); | |
55 | private static final short TKM_EXECUTE_FINALIZE_RULES = TagDict.getInstance().xlate("mkgmap:execute_finalize_rules"); | |
56 | 56 | |
57 | 57 | private RuleIndex index = new RuleIndex(); |
58 | 58 | private final Set<String> usedTags = new HashSet<>(); |
98 | 98 | return cacheId; |
99 | 99 | } |
100 | 100 | if (lastRule != null && lastRule.getFinalizeRule() != null |
101 | && "true".equals(el.getTag(executeFinalizeRulesTagKey))) { | |
101 | && "true".equals(el.getTag(TKM_EXECUTE_FINALIZE_RULES))) { | |
102 | 102 | cacheId = lastRule.getFinalizeRule().resolveType(cacheId, el, a); |
103 | 103 | } |
104 | 104 | return cacheId; |
57 | 57 | File dir = file; |
58 | 58 | if (name != null) { |
59 | 59 | dir = new File(file, name); |
60 | if (dir.exists() == false) | |
60 | if (!dir.exists()) | |
61 | 61 | throw new FileNotFoundException("style " + name + " not found in " + dir); |
62 | 62 | if (!dir.isDirectory()) |
63 | 63 | dir = file; |
145 | 145 | } |
146 | 146 | |
147 | 147 | String proto = url.getProtocol().toLowerCase(); |
148 | if (proto.equals("jar")) { | |
148 | if ("jar".equals(proto)) { | |
149 | 149 | log.debug("classpath loading from jar with url", url); |
150 | 150 | return new JarFileLoader(url); |
151 | } else if (proto.equals("file")) { | |
151 | } else if ("file".equals(proto)) { | |
152 | 152 | log.debug("classpath loading from directory", url.getPath()); |
153 | 153 | return new DirectoryFileLoader(new File(url.getPath())); |
154 | 154 | } |
35 | 35 | import java.util.Map; |
36 | 36 | import java.util.Map.Entry; |
37 | 37 | import java.util.Set; |
38 | import java.util.regex.Pattern; | |
39 | 38 | |
40 | 39 | import uk.me.parabola.imgfmt.ExitException; |
41 | 40 | import uk.me.parabola.log.Logger; |
41 | import uk.me.parabola.mkgmap.CommandArgs; | |
42 | 42 | import uk.me.parabola.mkgmap.Options; |
43 | 43 | import uk.me.parabola.mkgmap.general.LevelInfo; |
44 | 44 | import uk.me.parabola.mkgmap.general.LineAdder; |
79 | 79 | private static final String FILE_OPTIONS = "options"; |
80 | 80 | private static final String FILE_OVERLAYS = "overlays"; |
81 | 81 | |
82 | // Patterns | |
83 | private static final Pattern COMMA_OR_SPACE_PATTERN = Pattern.compile("[,\\s]+"); | |
84 | ||
85 | 82 | // A handle on the style directory or file. |
86 | 83 | private final StyleFileLoader fileLoader; |
87 | 84 | private final String location; |
162 | 159 | ListIterator<StyleImpl> listIterator = baseStyles.listIterator(baseStyles.size()); |
163 | 160 | while (listIterator.hasPrevious()) |
164 | 161 | mergeRules(listIterator.previous()); |
165 | ||
166 | // OR: other way | |
167 | //for (StyleImpl s : baseStyles) | |
168 | // mergeRules(s); | |
169 | } | |
170 | ||
162 | } | |
163 | ||
164 | @Override | |
171 | 165 | public String getOption(String name) { |
172 | 166 | return generalOptions.get(name); |
173 | 167 | } |
174 | 168 | |
169 | @Override | |
175 | 170 | public StyleInfo getInfo() { |
176 | 171 | return info; |
177 | 172 | } |
178 | 173 | |
174 | @Override | |
179 | 175 | public Rule getNodeRules() { |
180 | 176 | nodes.prepare(); |
181 | 177 | return nodes; |
182 | 178 | } |
183 | 179 | |
180 | @Override | |
184 | 181 | public Rule getWayRules() { |
185 | 182 | RuleSet r = new RuleSet(); |
186 | 183 | r.addAll(lines); |
189 | 186 | return r; |
190 | 187 | } |
191 | 188 | |
189 | @Override | |
192 | 190 | public Rule getLineRules() { |
193 | 191 | lines.prepare(); |
194 | 192 | return lines; |
195 | 193 | } |
196 | 194 | |
195 | @Override | |
197 | 196 | public Rule getPolygonRules() { |
198 | 197 | polygons.prepare(); |
199 | 198 | return polygons; |
200 | 199 | } |
201 | 200 | |
201 | @Override | |
202 | 202 | public Rule getRelationRules() { |
203 | 203 | relations.prepare(); |
204 | 204 | return relations; |
205 | 205 | } |
206 | 206 | |
207 | @Override | |
207 | 208 | public LineAdder getOverlays(final LineAdder lineAdder) { |
208 | 209 | LineAdder adder = null; |
209 | 210 | |
212 | 213 | } |
213 | 214 | return adder; |
214 | 215 | } |
215 | ||
216 | ||
217 | @Override | |
218 | public Set<String> getUsedTagsPOI() { | |
219 | return new HashSet<>(nodes.getUsedTags()); | |
220 | } | |
221 | ||
222 | @Override | |
216 | 223 | public Set<String> getUsedTags() { |
217 | 224 | Set<String> set = new HashSet<>(); |
218 | 225 | set.addAll(relations.getUsedTags()); |
225 | 232 | // if they are not found in the style file. This is mostly to work |
226 | 233 | // around situations that we haven't thought of - the style is expected |
227 | 234 | // to get it right for itself. |
228 | String s = getOption("extra-used-tags"); | |
229 | if (s != null && !s.trim().isEmpty()) | |
230 | set.addAll(Arrays.asList(COMMA_OR_SPACE_PATTERN.split(s))); | |
235 | set.addAll(CommandArgs.stringToList(getOption("extra-used-tags"), "extra-used-tags")); | |
231 | 236 | |
232 | 237 | // There are a lot of tags that are used within mkgmap that |
233 | 238 | try (InputStream is = this.getClass().getResourceAsStream("/styles/builtin-tag-list");) { |
328 | 333 | Options opts = new Options(opt -> { |
329 | 334 | String key = opt.getOption(); |
330 | 335 | String val = opt.getValue(); |
331 | if (key.equals("name-tag-list")) { | |
336 | if ("name-tag-list".equals(key)) { | |
332 | 337 | if (!"name".equals(val)) { |
333 | 338 | System.err.println("Warning: option name-tag-list used in the style options is ignored. " |
334 | 339 | + "Please use only the command line option to specify this value."); |
358 | 363 | Options opts = new Options(opt -> { |
359 | 364 | String word = opt.getOption(); |
360 | 365 | String value = opt.getValue(); |
361 | if (word.equals("summary")) | |
366 | if ("summary".equals(word)) | |
362 | 367 | info.setSummary(value); |
363 | else if (word.equals("version")) { | |
368 | else if ("version".equals(word)) { | |
364 | 369 | info.setVersion(value); |
365 | } else if (word.equals("base-style")) { | |
370 | } else if ("base-style".equals(word)) { | |
366 | 371 | info.addBaseStyleName(value); |
367 | } else if (word.equals("description")) { | |
372 | } else if ("description".equals(word)) { | |
368 | 373 | info.setLongDescription(value); |
369 | 374 | } |
370 | 375 | }); |
295 | 295 | } |
296 | 296 | |
297 | 297 | boolean wasReversed = false; |
298 | String oneWay = way.getTag(onewayTagKey); | |
298 | String oneWay = way.getTag(TK_ONEWAY); | |
299 | 299 | if (oneWay != null){ |
300 | 300 | if("-1".equals(oneWay) || "reverse".equals(oneWay)) { |
301 | 301 | // it's a oneway street in the reverse direction |
303 | 303 | // the oneway tag to "yes" |
304 | 304 | way.reverse(); |
305 | 305 | wasReversed = true; |
306 | way.addTag(onewayTagKey, "yes"); | |
307 | } | |
308 | ||
309 | if (way.tagIsLikeYes(onewayTagKey)) { | |
310 | way.addTag(onewayTagKey, "yes"); | |
306 | way.addTag(TK_ONEWAY, "yes"); | |
307 | } | |
308 | ||
309 | if (way.tagIsLikeYes(TK_ONEWAY)) { | |
310 | way.addTag(TK_ONEWAY, "yes"); | |
311 | 311 | if (foundType.isRoad() && hasSkipDeadEndCheckNode(way)) |
312 | 312 | way.addTag("mkgmap:dead-end-check", "false"); |
313 | 313 | } else { |
314 | way.deleteTag(onewayTagKey); | |
314 | way.deleteTag(TK_ONEWAY); | |
315 | 315 | } |
316 | 316 | } |
317 | 317 | ConvertedWay cw = new ConvertedWay(lineIndex++, way, foundType); |
320 | 320 | roads.add(cw); |
321 | 321 | numRoads++; |
322 | 322 | if (!cw.isFerry()) { |
323 | String countryIso = LocatorConfig.get().getCountryISOCode(way.getTag(countryTagKey)); | |
323 | String countryIso = LocatorConfig.get().getCountryISOCode(way.getTag(TKM_COUNTRY)); | |
324 | 324 | if (countryIso != null) { |
325 | 325 | boolean drivingSideIsLeft = LocatorConfig.get().getDriveOnLeftFlag(countryIso); |
326 | 326 | if (drivingSideIsLeft) |
361 | 361 | shape.setPoints(way.getPoints()); |
362 | 362 | |
363 | 363 | long areaVal = 0; |
364 | String tagStringVal = way.getTag(drawLevelTagKey); | |
364 | String tagStringVal = way.getTag(TKM_DRAW_LEVEL); | |
365 | 365 | if (tagStringVal != null) { |
366 | 366 | try { |
367 | 367 | areaVal = Integer.parseInt(tagStringVal); |
413 | 413 | * |
414 | 414 | * @param way The OSM way. |
415 | 415 | */ |
416 | private static final short styleFilterTagKey = TagDict.getInstance().xlate("mkgmap:stylefilter"); | |
417 | private static final short makeCycleWayTagKey = TagDict.getInstance().xlate("mkgmap:make-cycle-way"); | |
416 | private static final short TKM_STYLEFILTER = TagDict.getInstance().xlate("mkgmap:stylefilter"); | |
417 | private static final short TKM_MAKE_CYCLE_WAY = TagDict.getInstance().xlate("mkgmap:make-cycle-way"); | |
418 | 418 | private long lastRoadId = 0; |
419 | 419 | private int lineCacheId = 0; |
420 | 420 | private BitSet routingWarningWasPrinted = new BitSet(); |
433 | 433 | |
434 | 434 | preConvertRules(way); |
435 | 435 | |
436 | String styleFilterTag = way.getTag(styleFilterTagKey); | |
436 | String styleFilterTag = way.getTag(TKM_STYLEFILTER); | |
437 | 437 | Rule rules; |
438 | 438 | if ("polyline".equals(styleFilterTag)) |
439 | 439 | rules = lineRules; |
449 | 449 | rules = wayRules; |
450 | 450 | } |
451 | 451 | Way cycleWay = null; |
452 | String cycleWayTag = way.getTag(makeCycleWayTagKey); | |
452 | String cycleWayTag = way.getTag(TKM_MAKE_CYCLE_WAY); | |
453 | 453 | if ("yes".equals(cycleWayTag)){ |
454 | way.deleteTag("mkgmap:make-cycle-way"); | |
454 | way.deleteTag(TKM_MAKE_CYCLE_WAY); | |
455 | 455 | cycleWay = makeCycleWay(way); |
456 | 456 | way.addTag("bicycle", "no"); // make sure that bicycles are using the added bicycle way |
457 | 457 | } |
510 | 510 | } |
511 | 511 | |
512 | 512 | private int lineIndex = 0; |
513 | private static final short onewayTagKey = TagDict.getInstance().xlate("oneway"); | |
513 | private static final short TK_ONEWAY = TagDict.getInstance().xlate("oneway"); | |
514 | 514 | |
515 | 515 | /** One type result for nodes to avoid recreating one for each node. */ |
516 | 516 | private NodeTypeResult nodeTypeResult = new NodeTypeResult(); |
634 | 634 | cycleWay.addTag("access", "no"); |
635 | 635 | cycleWay.addTag("bicycle", "yes"); |
636 | 636 | cycleWay.addTag("mkgmap:synthesised", "yes"); |
637 | cycleWay.addTag(onewayTagKey, "no"); | |
637 | cycleWay.addTag(TK_ONEWAY, "no"); | |
638 | 638 | // remove explicit access tags |
639 | 639 | cycleWay.deleteTag("foot"); |
640 | 640 | cycleWay.deleteTag("motorcar"); |
660 | 660 | @Override |
661 | 661 | public void augmentWith(uk.me.parabola.mkgmap.reader.osm.ElementSaver elementSaver) { |
662 | 662 | // wayRules doesn't need to be done (or must be done first) because is concat. of line & polygon rules |
663 | //wayRules.augmentWith(elementSaver); | |
664 | 663 | nodeRules.augmentWith(elementSaver); |
665 | 664 | lineRules.augmentWith(elementSaver); |
666 | 665 | polygonRules.augmentWith(elementSaver); |
825 | 824 | while (pos < points.size()) { |
826 | 825 | int right = Math.min(points.size(), pos + max); |
827 | 826 | Way w = new Way(orig.getId(), points.subList(pos, right)); |
828 | w.setFakeId(); | |
827 | w.markAsGeneratedFrom(orig); | |
829 | 828 | clippedBorders.add(w); |
830 | 829 | pos += max - 1; |
831 | 830 | if (pos + 1 == points.size()) |
1059 | 1058 | + points.get(0).toOSMURL() + ")"); |
1060 | 1059 | else { |
1061 | 1060 | boolean clockwise = dir > 0; |
1061 | boolean dirIsWrong = (Boolean.TRUE.equals(driveOnLeft) && !clockwise || Boolean.FALSE.equals(driveOnLeft) && clockwise); | |
1062 | 1062 | if (points.get(0) == points.get(points.size() - 1)) { |
1063 | 1063 | // roundabout is a loop |
1064 | if (driveOnLeft && !clockwise || !driveOnLeft && clockwise) { | |
1064 | if (dirIsWrong) { | |
1065 | 1065 | log.warn("Roundabout " + way.getId() + " direction is wrong - reversing it (see " |
1066 | 1066 | + centre.toOSMURL() + ")"); |
1067 | 1067 | way.reverse(); |
1068 | 1068 | } |
1069 | } else if (driveOnLeft && !clockwise || !driveOnLeft && clockwise) { | |
1069 | } else if (dirIsWrong) { | |
1070 | 1070 | // roundabout is a line |
1071 | 1071 | log.warn("Roundabout segment " + way.getId() + " direction looks wrong (see " |
1072 | 1072 | + points.get(0).toOSMURL() + ")"); |
1220 | 1220 | line.setPoints(points); |
1221 | 1221 | |
1222 | 1222 | |
1223 | if (way.tagIsLikeYes(onewayTagKey)) | |
1223 | if (way.tagIsLikeYes(TK_ONEWAY)) | |
1224 | 1224 | line.setDirection(true); |
1225 | 1225 | |
1226 | 1226 | clipper.clipLine(line, lineAdder); |
1232 | 1232 | TagDict.getInstance().xlate("mkgmap:label:3"), |
1233 | 1233 | TagDict.getInstance().xlate("mkgmap:label:4"), |
1234 | 1234 | }; |
1235 | private static final short highResOnlyTagKey = TagDict.getInstance().xlate("mkgmap:highest-resolution-only"); | |
1236 | private static final short skipSizeFilterTagKey = TagDict.getInstance().xlate("mkgmap:skipSizeFilter"); | |
1237 | private static final short drawLevelTagKey = TagDict.getInstance().xlate("mkgmap:drawLevel"); | |
1238 | ||
1239 | private static final short countryTagKey = TagDict.getInstance().xlate("mkgmap:country"); | |
1240 | private static final short regionTagKey = TagDict.getInstance().xlate("mkgmap:region"); | |
1241 | private static final short cityTagKey = TagDict.getInstance().xlate("mkgmap:city"); | |
1242 | private static final short postal_codeTagKey = TagDict.getInstance().xlate("mkgmap:postal_code"); | |
1243 | private static final short streetTagKey = TagDict.getInstance().xlate("mkgmap:street"); | |
1244 | private static final short housenumberTagKey = TagDict.getInstance().xlate("mkgmap:housenumber"); | |
1245 | private static final short phoneTagKey = TagDict.getInstance().xlate("mkgmap:phone"); | |
1246 | private static final short is_inTagKey = TagDict.getInstance().xlate("mkgmap:is_in"); | |
1235 | private static final short TKM_HIGHEST_RES_ONLY = TagDict.getInstance().xlate("mkgmap:highest-resolution-only"); | |
1236 | private static final short TKM_SKIP_SIZE_FILTER = TagDict.getInstance().xlate("mkgmap:skipSizeFilter"); | |
1237 | private static final short TKM_DRAW_LEVEL = TagDict.getInstance().xlate("mkgmap:drawLevel"); | |
1238 | ||
1239 | private static final short TKM_COUNTRY = TagDict.getInstance().xlate("mkgmap:country"); | |
1240 | private static final short TKM_REGION = TagDict.getInstance().xlate("mkgmap:region"); | |
1241 | private static final short TKM_CITY = TagDict.getInstance().xlate("mkgmap:city"); | |
1242 | private static final short TKM_POSTAL_CODE = TagDict.getInstance().xlate("mkgmap:postal_code"); | |
1243 | private static final short TKM_STREET = TagDict.getInstance().xlate("mkgmap:street"); | |
1244 | private static final short TKM_HOUSENUMBER = TagDict.getInstance().xlate("mkgmap:housenumber"); | |
1245 | private static final short TKM_PHONE = TagDict.getInstance().xlate("mkgmap:phone"); | |
1246 | private static final short TKM_IS_IN = TagDict.getInstance().xlate("mkgmap:is_in"); | |
1247 | 1247 | |
1248 | 1248 | private void elementSetup(MapElement ms, GType gt, Element element) { |
1249 | 1249 | String[] labels = new String[4]; |
1264 | 1264 | ms.setMinResolution(gt.getMinResolution()); |
1265 | 1265 | ms.setMaxResolution(gt.getMaxResolution()); |
1266 | 1266 | |
1267 | if (element.tagIsLikeYes(highResOnlyTagKey)){ | |
1267 | if (element.tagIsLikeYes(TKM_HIGHEST_RES_ONLY)){ | |
1268 | 1268 | ms.setMinResolution(ms.getMaxResolution()); |
1269 | 1269 | } |
1270 | 1270 | |
1271 | if (ms instanceof MapLine && element.tagIsLikeYes(skipSizeFilterTagKey)){ | |
1271 | if (ms instanceof MapLine && element.tagIsLikeYes(TKM_SKIP_SIZE_FILTER)){ | |
1272 | 1272 | ((MapLine)ms).setSkipSizeFilter(true); |
1273 | 1273 | } |
1274 | 1274 | |
1275 | 1275 | // Now try to get some address info for POIs |
1276 | 1276 | |
1277 | String country = element.getTag(countryTagKey); | |
1278 | String region = element.getTag(regionTagKey); | |
1279 | String city = element.getTag(cityTagKey); | |
1280 | String zip = element.getTag(postal_codeTagKey); | |
1281 | String street = element.getTag(streetTagKey); | |
1282 | String houseNumber = element.getTag(housenumberTagKey); | |
1283 | String phone = element.getTag(phoneTagKey); | |
1284 | String isIn = element.getTag(is_inTagKey); | |
1277 | String country = element.getTag(TKM_COUNTRY); | |
1278 | String region = element.getTag(TKM_REGION); | |
1279 | String city = element.getTag(TKM_CITY); | |
1280 | String zip = element.getTag(TKM_POSTAL_CODE); | |
1281 | String street = element.getTag(TKM_STREET); | |
1282 | String houseNumber = element.getTag(TKM_HOUSENUMBER); | |
1283 | String phone = element.getTag(TKM_PHONE); | |
1284 | String isIn = element.getTag(TKM_IS_IN); | |
1285 | 1285 | |
1286 | 1286 | if(country != null) |
1287 | 1287 | ms.setCountry(country); |
1753 | 1753 | if (wayPOI != null && wayPOI.contains("[" + cp.getNode().getId() + "]")){ |
1754 | 1754 | byte nodeAccess = AccessTagsAndBits.evalAccessTags(cp.getNode()); |
1755 | 1755 | if (nodeAccess != cw.getAccess()){ |
1756 | List<Way> wayList = poiRestrictions.get(cp.getNode()); | |
1757 | if (wayList == null){ | |
1758 | wayList = new ArrayList<>(); | |
1759 | poiRestrictions.put(cp.getNode(), wayList); | |
1760 | } | |
1761 | wayList.add(way); | |
1756 | poiRestrictions.computeIfAbsent(cp.getNode(), k -> new ArrayList<>()).add(way); | |
1762 | 1757 | } |
1763 | 1758 | } |
1764 | 1759 | } |
2193 | 2188 | List<Coord> otherPoints = connectedWay.getPoints(); |
2194 | 2189 | Coord otherFirst = otherPoints.get(0); |
2195 | 2190 | Coord otherLast = otherPoints.get(otherPoints.size() - 1); |
2196 | if (otherFirst == otherLast || !connectedWay.tagIsLikeYes(onewayTagKey)) | |
2191 | if (otherFirst == otherLast || !connectedWay.tagIsLikeYes(TK_ONEWAY)) | |
2197 | 2192 | isDeadEnd = false; |
2198 | 2193 | else { |
2199 | 2194 | Coord pOther; |
2230 | 2225 | continue; |
2231 | 2226 | Way way = cw.getWay(); |
2232 | 2227 | if ("true".equals(way.getTag("mkgmap:way-has-pois"))) { |
2233 | String wayPOI = ""; | |
2228 | StringBuilder wayPOI = new StringBuilder(); | |
2234 | 2229 | |
2235 | 2230 | List<Coord> points = way.getPoints(); |
2236 | 2231 | int numPoints = points.size(); |
2264 | 2259 | } |
2265 | 2260 | if (usedInThisWay) { |
2266 | 2261 | cp.setUsed(true); |
2267 | wayPOI += "[" + node.getId() + "]"; | |
2262 | wayPOI.append('[').append(node.getId()).append(']'); | |
2268 | 2263 | } |
2269 | 2264 | } |
2270 | 2265 | } |
2271 | if (wayPOI.isEmpty()) { | |
2266 | if (wayPOI.length() == 0) { | |
2272 | 2267 | way.deleteTag("mkgmap:way-has-pois"); |
2273 | 2268 | log.info("ignoring CoordPOI(s) for way", way.toBrowseURL(), "because routing is not affected."); |
2274 | 2269 | } else { |
2275 | way.addTag(WAY_POI_NODE_IDS, wayPOI); | |
2270 | way.addTag(WAY_POI_NODE_IDS, wayPOI.toString()); | |
2276 | 2271 | } |
2277 | 2272 | } |
2278 | 2273 | } |
38 | 38 | if (t == null || t.getType() == TokType.EOF) |
39 | 39 | throw new SyntaxException(ts, "No garmin type information given"); |
40 | 40 | |
41 | if (!t.getValue().equals("[")) { | |
41 | if (!"[".equals(t.getValue())) { | |
42 | 42 | throw new SyntaxException(ts, "No type definition"); |
43 | 43 | } |
44 | 44 | |
59 | 59 | while (!ts.isEndOfFile()) { |
60 | 60 | ts.skipSpace(); |
61 | 61 | String w = ts.nextValue(); |
62 | if (w.equals("]")) | |
62 | if ("]".equals(w)) | |
63 | 63 | break; |
64 | 64 | |
65 | if (w.equals("level")) { | |
65 | if ("level".equals(w)) { | |
66 | 66 | setLevel(ts, gt); |
67 | } else if (w.equals("resolution")) { | |
67 | } else if ("resolution".equals(w)) { | |
68 | 68 | setResolution(ts, gt); |
69 | } else if (w.equals("default_name")) { | |
69 | } else if ("default_name".equals(w)) { | |
70 | 70 | gt.setDefaultName(nextValue(ts)); |
71 | } else if (w.equals("road_class")) { | |
71 | } else if ("road_class".equals(w)) { | |
72 | 72 | gt.setRoadClass(nextIntValue(ts)); |
73 | } else if (w.equals("road_speed")) { | |
73 | } else if ("road_speed".equals(w)) { | |
74 | 74 | gt.setRoadSpeed(nextIntValue(ts)); |
75 | } else if (w.equals("copy")) { | |
75 | } else if ("copy".equals(w)) { | |
76 | 76 | // Reserved |
77 | } else if (w.equals("continue")) { | |
77 | } else if ("continue".equals(w)) { | |
78 | 78 | gt.setContinueSearch(true); |
79 | 79 | // By default no propagate of actions on continue |
80 | 80 | gt.propagateActions(false); |
81 | } else if (w.equals("propagate") || w.equals("with_actions") || w.equals("withactions")) { | |
81 | } else if ("propagate".equals(w) || "with_actions".equals(w) || "withactions".equals(w)) { | |
82 | 82 | gt.propagateActions(true); |
83 | } else if (w.equals("no_propagate")) { | |
83 | } else if ("no_propagate".equals(w)) { | |
84 | 84 | gt.propagateActions(false); |
85 | } else if (w.equals("oneway")) { | |
85 | } else if ("oneway".equals(w)) { | |
86 | 86 | // reserved |
87 | } else if (w.equals("access")) { | |
87 | } else if ("access".equals(w)) { | |
88 | 88 | // reserved |
89 | 89 | } else { |
90 | 90 | throw new SyntaxException(ts, "Unrecognised type command '" + w + '\''); |
127 | 127 | for (int i = 0; i < usedTypes.size(); i++){ |
128 | 128 | int usedType = usedTypes.get(i); |
129 | 129 | String typeOverlaidMsg = ". Type is overlaid with " + GType.formatType(usedType); |
130 | if (GType.checkType(kind, usedType) == false){ | |
130 | if (!GType.checkType(kind, usedType)) { | |
131 | 131 | String msg = "Warning: invalid type " + type + " for " + kind + " in style file " + ts.getFileName() + ", line " + ts.getLinenumber(); |
132 | 132 | if (fromOverlays) |
133 | 133 | msg += typeOverlaidMsg; |
135 | 135 | } |
136 | 136 | if (kind == FeatureKind.POLYLINE && gt.getMinLevel() == 0 && gt.getMaxLevel() >= 0){ |
137 | 137 | if (GType.isSpecialRoutableLineType(usedType)){ |
138 | if (gt.hasRoadAttribute() == false){ | |
138 | if (!gt.hasRoadAttribute()) { | |
139 | 139 | String msg = "Warning: routable type " + type + " is used for non-routable line with level 0. This may break routing. Style file "+ ts.getFileName() + ", line " + ts.getLinenumber(); |
140 | 140 | if (fromOverlays) |
141 | 141 | msg += typeOverlaidMsg; |
153 | 153 | foundRoutableType = true; |
154 | 154 | } |
155 | 155 | } |
156 | if (gt.hasRoadAttribute() && foundRoutableType == false && gt.getMinLevel() == 0 && gt.getMaxLevel() >= 0){ | |
156 | if (gt.hasRoadAttribute() && !foundRoutableType && gt.getMinLevel() == 0 && gt.getMaxLevel() >= 0){ | |
157 | 157 | String msg = "Warning: non-routable type " + type + " is used in combination with road_class/road_speed. Line will not be routable. Style file "+ ts.getFileName() + ", line " + ts.getLinenumber(); |
158 | 158 | if (fromOverlays) |
159 | 159 | msg += ". Type is overlaid, but not with a routable type"; |
30 | 30 | import uk.me.parabola.mkgmap.filters.DouglasPeuckerFilter; |
31 | 31 | import uk.me.parabola.mkgmap.general.MapPoint; |
32 | 32 | import uk.me.parabola.mkgmap.reader.osm.CoordPOI; |
33 | import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator; | |
34 | 33 | import uk.me.parabola.mkgmap.reader.osm.Node; |
35 | 34 | import uk.me.parabola.mkgmap.reader.osm.RestrictionRelation; |
36 | 35 | import uk.me.parabola.mkgmap.reader.osm.Way; |
422 | 421 | Coord p = points.get(i); |
423 | 422 | if (p.isToRemove()) { |
424 | 423 | if (pass >= maxPass - 1) { |
425 | log.warn("removed point in last pass. Way", getUsableId(way), p.toDegreeString()); | |
424 | log.warn("removed point in last pass. Way", way.getBasicLogInformation(), p.toDegreeString()); | |
426 | 425 | } |
427 | 426 | points.remove(i); |
428 | 427 | anotherPassRequired = true; |
446 | 445 | } |
447 | 446 | p = replacement; |
448 | 447 | if (pass >= maxPass - 1) { |
449 | log.warn("changed point in last pass. Way", getUsableId(way), p.toDegreeString()); | |
448 | log.warn("changed point in last pass. Way", way.getBasicLogInformation(), p.toDegreeString()); | |
450 | 449 | } |
451 | 450 | // replace point in way |
452 | 451 | points.set(i, p); |
1459 | 1458 | way.getLastPoint().setEndOfWay(b); |
1460 | 1459 | } |
1461 | 1460 | } |
1462 | ||
1463 | ||
1464 | private static String getUsableId(Way w) { | |
1465 | return "Way " + (FakeIdGenerator.isFakeId(w.getId()) ? " generated from " : " ") + w.getOriginalId(); | |
1466 | } | |
1467 | 1461 | } |
33 | 33 | * @param el |
34 | 34 | * @return true if one or more tags of the element were changed. |
35 | 35 | */ |
36 | public boolean perform(Element el); | |
36 | boolean perform(Element el); | |
37 | 37 | } |
34 | 34 | * @author Steve Ratcliffe |
35 | 35 | */ |
36 | 36 | public class ActionReader { |
37 | Set<String> VALID_ACCESS = new HashSet<>(Arrays.asList("yes", "no", "true", "false", "1", "0")); | |
37 | private static final Set<String> VALID_ACCESS = new HashSet<>(Arrays.asList("yes", "no", "true", "false", "1", "0")); | |
38 | 38 | |
39 | 39 | private final TokenScanner scanner; |
40 | 40 |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.mkgmap.osmstyle.actions; |
13 | 13 | |
14 | import java.util.List; | |
14 | import static uk.me.parabola.imgfmt.app.net.AccessTagsAndBits.ACCESS_TAGS_COMPILED; | |
15 | 15 | |
16 | 16 | import uk.me.parabola.mkgmap.reader.osm.Element; |
17 | import static uk.me.parabola.imgfmt.app.net.AccessTagsAndBits.*; | |
18 | 17 | |
19 | 18 | /** |
20 | 19 | * Add one value to all mkgmap access tags, optionally changing them if they already exist. |
40 | 39 | |
41 | 40 | public boolean perform(Element el) { |
42 | 41 | // 1st build the value |
43 | Element tags = valueTags!=null? valueTags: el; | |
42 | Element tags = valueTags != null ? valueTags : el; | |
44 | 43 | String accessValue = null; |
45 | 44 | for (ValueBuilder value : getValueBuilder()) { |
46 | 45 | accessValue = value.build(tags, el); |
66 | 65 | * @param value the value to be set |
67 | 66 | */ |
68 | 67 | private void setTag(Element el, Short tagKey, String value) { |
69 | String tv = el.getTag(tagKey); | |
70 | if (tv != null && !modify) | |
71 | return; | |
72 | ||
73 | el.addTag(tagKey, value); | |
68 | if (modify) { | |
69 | el.addTag(tagKey, value); | |
70 | } else { | |
71 | String tv = el.getTag(tagKey); | |
72 | if (tv == null) { | |
73 | el.addTag(tagKey, value); | |
74 | } | |
75 | } | |
74 | 76 | } |
75 | 77 | |
76 | 78 | public void setValueTags(Element valueTags) { |
78 | 80 | } |
79 | 81 | |
80 | 82 | public String toString() { |
81 | StringBuilder sb = new StringBuilder(); | |
82 | sb.append(modify ? "setaccess " : "addaccess "); | |
83 | List<ValueBuilder> values = getValueBuilder(); | |
84 | for (int i = 0; i < values.size(); i++) { | |
85 | sb.append(values.get(i)); | |
86 | if (i < values.size() - 1) | |
87 | sb.append(" | "); | |
88 | } | |
89 | sb.append(';'); | |
90 | return sb.toString(); | |
83 | return modify ? "setaccess " : "addaccess " + calcValueBuildersString() + ";"; | |
91 | 84 | } |
92 | 85 | } |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.mkgmap.osmstyle.actions; |
13 | 13 | |
14 | import java.util.ArrayList; | |
15 | import java.util.List; | |
16 | ||
14 | 17 | import uk.me.parabola.mkgmap.reader.osm.Element; |
15 | 18 | |
16 | 19 | /** |
20 | 23 | * We have a list of possible substitutions. |
21 | 24 | */ |
22 | 25 | public class AddLabelAction extends ValueBuildedAction { |
26 | private final List<String> labels = new ArrayList<>(4); | |
23 | 27 | |
24 | 28 | /** |
25 | 29 | * Search for the first matching pattern and set the first unset element label |
31 | 35 | * @return |
32 | 36 | */ |
33 | 37 | public boolean perform(Element el) { |
34 | for (int index = 1; index <=4; index++) { | |
38 | labels.clear(); | |
39 | for (int index = 1; index <= 4; index++) { | |
40 | String tag = "mkgmap:label:" + index; | |
41 | String label = el.getTag(tag); | |
35 | 42 | // find the first unset label and set it |
36 | if (el.getTag("mkgmap:label:"+index) == null) { | |
37 | for (ValueBuilder vb : getValueBuilder()) { | |
38 | String s = vb.build(el, el); | |
39 | if (s != null) { | |
40 | // now check if the new label is different to all other labels | |
41 | for (int n = index-1; n>= 1; n--) { | |
42 | if (s.equals(el.getTag("mkgmap:label:"+n))) { | |
43 | // value is equal to a previous label | |
44 | // do not use it | |
45 | return false; | |
46 | } | |
47 | } | |
48 | ||
49 | // set the label | |
50 | el.addTag("mkgmap:label:"+index, s); | |
51 | return true; | |
52 | } | |
43 | if (label != null) { | |
44 | labels.add(label); | |
45 | continue; | |
46 | } | |
47 | for (ValueBuilder vb : getValueBuilder()) { | |
48 | String s = vb.build(el, el); | |
49 | if (s != null) { | |
50 | // now check if the new label is different to all other labels | |
51 | if (labels.contains(s)) | |
52 | return false; | |
53 | // set the label | |
54 | el.addTag(tag, s); | |
55 | return true; | |
53 | 56 | } |
54 | return false; | |
55 | 57 | } |
58 | return false; | |
56 | 59 | } |
57 | 60 | return false; |
58 | 61 | } |
59 | 62 | |
60 | 63 | public String toString() { |
61 | StringBuilder sb = new StringBuilder(); | |
62 | sb.append("addlabel "); | |
63 | for (ValueBuilder vb : getValueBuilder()) { | |
64 | sb.append(vb); | |
65 | sb.append(" | "); | |
66 | } | |
67 | sb.setLength(sb.length() - 3); | |
68 | return sb.toString(); | |
64 | return "addlabel " + calcValueBuildersString(); | |
69 | 65 | } |
70 | 66 | } |
14 | 14 | * Create date: 15-Nov-2008 |
15 | 15 | */ |
16 | 16 | package uk.me.parabola.mkgmap.osmstyle.actions; |
17 | ||
18 | import java.util.List; | |
19 | 17 | |
20 | 18 | import uk.me.parabola.mkgmap.reader.osm.Element; |
21 | 19 | import uk.me.parabola.mkgmap.reader.osm.TagDict; |
47 | 45 | } |
48 | 46 | |
49 | 47 | public boolean perform(Element el) { |
50 | if (!modify){ | |
51 | String tv = el.getTag(tagKey); | |
52 | if (tv != null) | |
53 | return false; | |
54 | } | |
55 | Element tags = valueTags!=null? valueTags: el; | |
48 | if (!modify && el.getTag(tagKey) != null) | |
49 | return false; | |
50 | ||
51 | Element tags = valueTags != null ? valueTags : el; | |
56 | 52 | |
57 | 53 | for (ValueBuilder value : getValueBuilder()) { |
58 | 54 | String newval = value.build(tags, el); |
70 | 66 | } |
71 | 67 | |
72 | 68 | public String toString() { |
73 | StringBuilder sb = new StringBuilder(); | |
74 | sb.append(modify ? "set " : "add "); | |
75 | sb.append(tag); | |
76 | sb.append("="); | |
77 | List<ValueBuilder> values = getValueBuilder(); | |
78 | for (int i = 0; i < values.size(); i++) { | |
79 | sb.append(values.get(i)); | |
80 | if (i < values.size() - 1) | |
81 | sb.append(" | "); | |
82 | } | |
83 | sb.append(';'); | |
84 | return sb.toString(); | |
69 | return modify ? "set " : "add " + tag + "=" + calcValueBuildersString() + ";"; | |
85 | 70 | } |
86 | 71 | } |
22 | 22 | */ |
23 | 23 | public class CountryISOFilter extends ValueFilter { |
24 | 24 | |
25 | public CountryISOFilter() { | |
26 | } | |
27 | ||
28 | 25 | protected String doFilter(String value, Element el) { |
29 | 26 | if (value == null) |
30 | 27 | return value; |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.mkgmap.osmstyle.actions; |
13 | 13 | |
14 | import uk.me.parabola.imgfmt.app.Coord; | |
15 | 14 | import uk.me.parabola.mkgmap.reader.osm.Element; |
16 | import uk.me.parabola.mkgmap.reader.osm.Node; | |
17 | 15 | |
18 | 16 | /** |
19 | 17 | * Delete all tags from an element. This is useful to stop its processing. |
22 | 20 | */ |
23 | 21 | public class DeleteAllTagsAction implements Action { |
24 | 22 | |
25 | // as long as there is not deleteAll method copy the tags of element without tags | |
26 | private final Element noTagElement; | |
27 | 23 | |
28 | public DeleteAllTagsAction() { | |
29 | this.noTagElement = new Node(0, new Coord(0,0)); | |
30 | } | |
31 | ||
32 | 24 | public boolean perform(Element el) { |
33 | // remove all tags by copying the tags from a no tag element | |
34 | el.copyTags(noTagElement); | |
25 | el.removeAllTags(); | |
35 | 26 | return true; |
36 | 27 | } |
37 | 28 |
15 | 15 | package uk.me.parabola.mkgmap.osmstyle.actions; |
16 | 16 | |
17 | 17 | import uk.me.parabola.mkgmap.reader.osm.Element; |
18 | import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator; | |
19 | 18 | |
20 | 19 | /** |
21 | 20 | * Sends a message to the console. |
30 | 29 | } |
31 | 30 | |
32 | 31 | public boolean perform(Element el) { |
33 | String e = value.build(el, el); | |
34 | String className = el.getClass().getSimpleName(); | |
35 | if (className.equals("GeneralRelation")) | |
36 | className = "Relation"; | |
37 | System.err.println(className + (FakeIdGenerator.isFakeId(el.getId()) ? " generated from " : " ") + el.getOriginalId() + " " + e); | |
32 | System.err.println(el.getBasicLogInformation() + " " + value.build(el, el)); | |
38 | 33 | return false; |
39 | 34 | } |
40 | 35 | } |
13 | 13 | package uk.me.parabola.mkgmap.osmstyle.actions; |
14 | 14 | |
15 | 15 | import uk.me.parabola.mkgmap.reader.osm.Element; |
16 | import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator; | |
17 | 16 | |
18 | 17 | /** |
19 | 18 | * Sends a message including the tags of an element to System.err. |
28 | 27 | } |
29 | 28 | |
30 | 29 | public boolean perform(Element el) { |
31 | String e = value.build(el, el); | |
32 | String className = el.getClass().getSimpleName(); | |
33 | if (className.equals("GeneralRelation")) | |
34 | className = "Relation"; | |
35 | System.err.println(className + (FakeIdGenerator.isFakeId(el.getId()) ? " generated from " : " ") + el.getOriginalId() + " " + el.toTagString() + " " + e); | |
30 | System.err.println(el.getBasicLogInformation() + " " + el.toTagString() + " " + value.build(el, el)); | |
36 | 31 | return false; |
37 | 32 | } |
38 | 33 |
30 | 30 | throw new SyntaxException(String.format("height filter reqires ft (feet) as target unit: '%s'", s)); |
31 | 31 | } |
32 | 32 | |
33 | @Override | |
33 | 34 | public String doFilter(String value, Element el) { |
34 | 35 | String s = super.doFilter(value, el); |
35 | if (s != null) | |
36 | s = "\u001f" + s; | |
37 | return s; | |
36 | return s != null ? "\u001f" + s : null; | |
38 | 37 | } |
39 | 38 | } |
22 | 22 | |
23 | 23 | /** |
24 | 24 | * Prepend a Garmin magic-character to the value. |
25 | * TODO: symbolic names? | |
26 | 25 | * |
27 | 26 | * @author Toby Speight |
28 | 27 | */ |
29 | 28 | public class HighwaySymbolFilter extends ValueFilter { |
30 | 29 | private final String prefix; |
31 | 30 | |
32 | private static final Map<String, String>symbols = new HashMap<String, String>(); | |
31 | private static final Map<String, String> symbols = new HashMap<>(); | |
33 | 32 | private static final int MAX_REF_LENGTH = 8; // enough for "A6144(M)" (RIP) |
34 | 33 | |
35 | 34 | private int maxAlphaNum = MAX_REF_LENGTH; // Max. length for alphanumeric (e.g., 'A67') |
38 | 37 | private static final Pattern spacePattern = Pattern.compile(" ", Pattern.LITERAL); |
39 | 38 | private static final Pattern semicolonPattern = Pattern.compile(";", Pattern.LITERAL); |
40 | 39 | static { |
41 | //symbols.put("ele", "\u001f"); // name.height separator | |
42 | ||
43 | // Now add other symbols | |
44 | 40 | symbols.put("interstate", "\u0001"); // US Interstate |
45 | 41 | symbols.put("shield", "\u0002"); // US Highway shield |
46 | 42 | symbols.put("round", "\u0003"); // US Highway round |
77 | 73 | public String doFilter(String value, Element el) { |
78 | 74 | if (value == null) return value; |
79 | 75 | |
80 | // is it mostly alphabetic? | |
81 | /* int alpha_balance = 0; | |
82 | for (char c : value.toCharArray()) { | |
83 | alpha_balance += (Character.isLetter(c)) ? 1 : -1; | |
84 | } | |
85 | if (alpha_balance > 0) return value; | |
86 | ||
87 | // remove space if there is exactly one | |
88 | int first_space = value.indexOf(" "); | |
89 | if (first_space >= 0 && value.indexOf(" ", first_space + 1) < 0) { | |
90 | value = value.replace(" ", ""); | |
91 | } */ | |
92 | ||
93 | ||
94 | 76 | // Nuke all spaces |
95 | // String shieldText = value.replace(" ", ""); | |
96 | 77 | String shieldText = spacePattern.matcher(value).replaceAll(Matcher.quoteReplacement("")); |
97 | 78 | // Also replace ";" with "/", to change B3;B4 to B3/B4 |
98 | //shieldText = shieldText.replace(";", "/"); | |
99 | 79 | shieldText = semicolonPattern.matcher(shieldText).replaceAll(Matcher.quoteReplacement("/")); |
100 | 80 | |
101 | 81 | // Check if value is alphanumeric |
27 | 27 | * @author Steve Ratcliffe |
28 | 28 | */ |
29 | 29 | public class NameAction extends ValueBuildedAction { |
30 | private final short label1TagKey = TagDict.getInstance().xlate("mkgmap:label:1"); | |
30 | private static final short TKM_LABEL_1 = TagDict.getInstance().xlate("mkgmap:label:1"); | |
31 | 31 | /** |
32 | 32 | * search for the first matching name pattern and set the element name |
33 | 33 | * to it. |
38 | 38 | * @return |
39 | 39 | */ |
40 | 40 | public boolean perform(Element el) { |
41 | if (el.getTag(label1TagKey) != null) | |
41 | if (el.getTag(TKM_LABEL_1) != null) | |
42 | 42 | return false; |
43 | 43 | |
44 | 44 | for (ValueBuilder vb : getValueBuilder()) { |
45 | 45 | String s = vb.build(el, el); |
46 | 46 | if (s != null) { |
47 | el.addTag(label1TagKey, s); | |
47 | el.addTag(TKM_LABEL_1, s); | |
48 | 48 | return true; |
49 | 49 | } |
50 | 50 | } |
52 | 52 | } |
53 | 53 | |
54 | 54 | public String toString() { |
55 | StringBuilder sb = new StringBuilder(); | |
56 | sb.append("name "); | |
57 | for (ValueBuilder vb : getValueBuilder()) { | |
58 | sb.append(vb); | |
59 | sb.append(" | "); | |
60 | } | |
61 | sb.setLength(sb.length() - 3); | |
62 | return sb.toString(); | |
55 | return "name " + calcValueBuildersString(); | |
63 | 56 | } |
64 | 57 | } |
39 | 39 | * @author Maxim Duester |
40 | 40 | */ |
41 | 41 | public class NotContainedFilter extends ValueFilter { |
42 | private String separator; | |
43 | private short tagKey; | |
42 | private final String quotedSeparator; | |
43 | private final short tagKey; | |
44 | 44 | |
45 | 45 | public NotContainedFilter(String arg) { |
46 | 46 | String[] temp = arg.split(":"); |
52 | 52 | |
53 | 53 | // set the separator (default to ;) |
54 | 54 | if (temp[0].length() > 0) |
55 | separator = temp[0]; | |
55 | quotedSeparator = Pattern.quote(temp[0]); | |
56 | 56 | else |
57 | separator = ";"; | |
57 | quotedSeparator = Pattern.quote(";"); | |
58 | 58 | // set the tag short value |
59 | 59 | tagKey = TagDict.getInstance().xlate(temp[1]); |
60 | 60 | } |
69 | 69 | return value; |
70 | 70 | |
71 | 71 | // split uses a regex we need to replace special characters |
72 | String[] temp = tagValue.split(Pattern.quote(separator)); | |
73 | ||
72 | String[] temp = tagValue.split(quotedSeparator); | |
74 | 73 | for (String s : temp) |
75 | 74 | if (s.equals(value)) |
76 | 75 | return null; |
28 | 28 | private final short tagKey; |
29 | 29 | |
30 | 30 | public NotEqualFilter(String s) { |
31 | ||
32 | 31 | tagKey = TagDict.getInstance().xlate(s); |
33 | ||
34 | 32 | } |
35 | 33 | |
36 | 34 | public String doFilter(String value, Element el) { |
38 | 36 | |
39 | 37 | String tagValue = el.getTag(tagKey); |
40 | 38 | |
41 | if (tagValue == null) | |
42 | return value; | |
43 | ||
44 | if (value.equals(tagValue)) | |
39 | if (tagValue != null && value.equals(tagValue)) | |
45 | 40 | return null; // Return nothing if value is identical to the tag's value |
46 | else | |
47 | return value; | |
41 | return value; | |
48 | 42 | |
49 | 43 | } |
50 | 44 | } |
99 | 99 | if ( !isLt && !isGt ) { |
100 | 100 | return temp[idx].trim(); |
101 | 101 | } else { |
102 | StringBuffer returnValue= new StringBuffer(); | |
102 | StringBuilder returnValue= new StringBuilder(); | |
103 | 103 | |
104 | 104 | // operator "<": collate all the parts before the partnumber |
105 | if ( isLt ) { | |
106 | for (int i=0;i<idx;i++) { | |
105 | if (isLt) { | |
106 | for (int i = 0; i < idx; i++) { | |
107 | 107 | returnValue.append(temp[i]).append(separator); |
108 | 108 | } |
109 | 109 | } |
110 | 110 | |
111 | 111 | // operator ">": collate all the parts after the partnumber |
112 | if ( isGt ) { | |
113 | for (int i=idx+1;i<temp.length;i++) { | |
112 | if (isGt) { | |
113 | for (int i = idx + 1; i < temp.length; i++) { | |
114 | 114 | returnValue.append(temp[i]).append(separator); |
115 | 115 | } |
116 | 116 | } |
32 | 32 | |
33 | 33 | static { |
34 | 34 | // Firstly, the symbols common to both encodings. |
35 | symbols_6bit = new HashMap<String, String>(); | |
35 | symbols_6bit = new HashMap<>(); | |
36 | 36 | symbols_6bit.put("ele", "\u001f"); // name.height separator |
37 | 37 | |
38 | 38 | // Copy to other encoding |
39 | symbols_8bit = new HashMap<String, String>(symbols_6bit); | |
39 | symbols_8bit = new HashMap<>(symbols_6bit); | |
40 | 40 | |
41 | 41 | // Now add other symbols |
42 | 42 | symbols_6bit.put("interstate", "\u002a"); // US Interstate |
16 | 16 | package uk.me.parabola.mkgmap.osmstyle.actions; |
17 | 17 | |
18 | 18 | import java.util.ArrayList; |
19 | import java.util.Formatter; | |
20 | 19 | import java.util.HashSet; |
21 | import java.util.Iterator; | |
22 | 20 | import java.util.List; |
23 | 21 | import java.util.Map; |
22 | import java.util.stream.Collectors; | |
24 | 23 | |
25 | 24 | import uk.me.parabola.mkgmap.reader.osm.Element; |
26 | 25 | import uk.me.parabola.mkgmap.reader.osm.Relation; |
27 | 26 | |
28 | 27 | /** |
29 | * This is an action that contains sub-actions. It is used for Relations | |
30 | * where you want to apply the commands to the elements that are contained | |
31 | * in the relation and not on the relation itself. | |
28 | * This is an action that contains sub-actions. It is used for Relations where | |
29 | * you want to apply the commands to the elements that are contained in the | |
30 | * relation and not on the relation itself. | |
32 | 31 | * |
33 | 32 | * @author Steve Ratcliffe |
34 | 33 | */ |
35 | 34 | public class SubAction implements Action { |
36 | private final List<Action> actionList = new ArrayList<Action>(); | |
35 | private final List<Action> actionList = new ArrayList<>(); | |
37 | 36 | private final String role; |
38 | 37 | private final String selector; |
39 | 38 | |
45 | 44 | public boolean perform(Element el) { |
46 | 45 | if (el instanceof Relation) |
47 | 46 | performOnSubElements((Relation) el); |
48 | return true; // probably false, but relation may contain itself in complex recursive structures | |
47 | return true; // probably false, but relation may contain itself in | |
48 | // complex recursive structures | |
49 | 49 | } |
50 | 50 | |
51 | 51 | private void performOnSubElements(Relation rel) { |
52 | List<Map.Entry<String,Element>> elements = rel.getElements(); | |
52 | List<Map.Entry<String, Element>> elements = rel.getElements(); | |
53 | 53 | |
54 | for (Action a : actionList) | |
54 | for (Action a : actionList) { | |
55 | 55 | if (a instanceof AddTagAction) |
56 | 56 | ((AddTagAction) a).setValueTags(rel); |
57 | 57 | else if (a instanceof AddAccessAction) |
58 | 58 | ((AddAccessAction) a).setValueTags(rel); |
59 | } | |
59 | 60 | |
60 | 61 | boolean once = "once".equals(selector); |
61 | boolean first_only = "first".equals(selector); | |
62 | HashSet<Element> elems = once ? new HashSet<Element>() : null; | |
62 | boolean onlyFirst = "first".equals(selector); | |
63 | HashSet<Element> elems = once ? new HashSet<>() : null; | |
63 | 64 | |
64 | for (Map.Entry<String,Element> r_el : elements) { | |
65 | if ((role == null || role.equals(r_el.getKey())) && | |
66 | (!once || elems.add(r_el.getValue()))) { | |
67 | ||
68 | for (Action a : actionList) | |
69 | a.perform(r_el.getValue()); | |
65 | for (Map.Entry<String, Element> r_el : elements) { | |
66 | if ((role == null || role.equals(r_el.getKey())) && (!once || elems.add(r_el.getValue()))) { | |
67 | actionList.forEach(a -> a.perform(r_el.getValue())); | |
70 | 68 | } |
71 | if (first_only) | |
69 | if (onlyFirst) | |
72 | 70 | break; |
73 | 71 | } |
74 | 72 | } |
78 | 76 | } |
79 | 77 | |
80 | 78 | public String toString() { |
81 | Formatter fmt = new Formatter(); | |
82 | fmt.format("apply"); | |
79 | StringBuilder sb = new StringBuilder(); | |
80 | sb.append("apply"); | |
83 | 81 | if (selector != null) { |
84 | fmt.format("_%s",selector); | |
82 | sb.append(String.format("_%s", selector)); | |
85 | 83 | } |
86 | 84 | if (role != null) |
87 | fmt.format(" role=%s ", role); | |
88 | ||
89 | fmt.format(" {"); | |
85 | sb.append(String.format(" role=%s ", role)); | |
90 | 86 | |
91 | for (Iterator<Action> it = actionList.iterator(); it.hasNext();) { | |
92 | Action a = it.next(); | |
93 | fmt.format(a.toString()); | |
94 | if (it.hasNext()) | |
95 | fmt.format(" "); | |
96 | } | |
97 | ||
98 | fmt.format("}"); | |
99 | return fmt.toString(); | |
87 | sb.append(actionList.stream().map(Action::toString).collect(Collectors.joining(" ", "{", "}"))); | |
88 | return sb.toString(); | |
100 | 89 | } |
101 | 90 | } |
36 | 36 | |
37 | 37 | if (i == -1) { // no occurrences of =>, let's try with ~> |
38 | 38 | i = arg.indexOf("~>"); |
39 | if ( i >= 0 ) isRegexp = true; | |
39 | if (i >= 0) | |
40 | isRegexp = true; | |
40 | 41 | } |
41 | 42 | |
42 | 43 | if (i >= 0) { |
55 | 56 | public String doFilter(String value, Element el) { |
56 | 57 | if (value == null) return null; |
57 | 58 | return pattern.matcher(value).replaceAll(isRegexp ? to : Matcher.quoteReplacement(to)); |
58 | // replaceAll expects a regexp as 1st argument | |
59 | // return (isRegexp ? value.replaceAll(from, to) : value.replace(from, to) ); | |
60 | 59 | } |
61 | 60 | } |
13 | 13 | package uk.me.parabola.mkgmap.osmstyle.actions; |
14 | 14 | |
15 | 15 | import java.util.ArrayList; |
16 | import java.util.HashSet; | |
17 | 16 | import java.util.List; |
18 | 17 | import java.util.Set; |
18 | import java.util.stream.Collectors; | |
19 | 19 | |
20 | 20 | public abstract class ValueBuildedAction implements Action { |
21 | 21 | |
22 | private final List<ValueBuilder> valueBuilder = new ArrayList<ValueBuilder>(); | |
22 | private final List<ValueBuilder> valueBuilder = new ArrayList<>(); | |
23 | 23 | |
24 | 24 | /** |
25 | 25 | * Adds a value building rule. |
34 | 34 | * @return all required tags |
35 | 35 | */ |
36 | 36 | public Set<String> getUsedTags() { |
37 | Set<String> set = new HashSet<String>(); | |
38 | for (ValueBuilder vb : valueBuilder) { | |
39 | set.addAll(vb.getUsedTags()); | |
40 | } | |
41 | return set; | |
37 | return valueBuilder.stream().flatMap(vb -> vb.getUsedTags().stream()).collect(Collectors.toSet()); | |
42 | 38 | } |
43 | 39 | |
44 | 40 | /** |
48 | 44 | protected List<ValueBuilder> getValueBuilder() { |
49 | 45 | return valueBuilder; |
50 | 46 | } |
47 | ||
48 | protected String calcValueBuildersString() { | |
49 | return valueBuilder.stream().map(ValueBuilder::toString).collect(Collectors.joining(" | ")); | |
50 | } | |
51 | 51 | } |
16 | 16 | package uk.me.parabola.mkgmap.osmstyle.actions; |
17 | 17 | |
18 | 18 | import java.util.ArrayList; |
19 | import java.util.HashSet; | |
20 | 19 | import java.util.List; |
20 | import java.util.Objects; | |
21 | 21 | import java.util.Set; |
22 | 22 | import java.util.regex.Matcher; |
23 | 23 | import java.util.regex.Pattern; |
24 | import java.util.stream.Collectors; | |
24 | 25 | |
25 | 26 | import uk.me.parabola.mkgmap.reader.osm.Element; |
26 | 27 | import uk.me.parabola.mkgmap.scan.SyntaxException; |
115 | 116 | case '\0': |
116 | 117 | if (c == '$') { |
117 | 118 | state = '$'; |
118 | } else | |
119 | } else { | |
119 | 120 | text.append(c); |
121 | } | |
120 | 122 | break; |
121 | 123 | case '$': |
122 | 124 | switch (c) { |
152 | 154 | } |
153 | 155 | } |
154 | 156 | |
155 | if (text.length() > 0) | |
157 | if (text.length() > 0) | |
156 | 158 | items.add(new ValueItem(text.toString())); |
157 | 159 | } |
158 | 160 | |
159 | private void addTagValue(String tagname, boolean is_local) { | |
161 | private void addTagValue(String tagname, boolean isLocal) { | |
160 | 162 | ValueItem item = new ValueItem(); |
161 | 163 | if (tagname.contains("|")) { |
162 | 164 | String[] parts = tagname.split("[ \t]*\\|", 2); |
163 | 165 | assert parts.length > 1; |
164 | 166 | |
165 | item.setTagname(parts[0], is_local); | |
167 | item.setTagname(parts[0], isLocal); | |
166 | 168 | |
167 | 169 | String s = parts[1]; |
168 | 170 | |
186 | 188 | } |
187 | 189 | } |
188 | 190 | } else { |
189 | item.setTagname(tagname, is_local); | |
191 | item.setTagname(tagname, isLocal); | |
190 | 192 | } |
191 | 193 | items.add(item); |
192 | 194 | } |
193 | 195 | |
194 | private void addFilter(ValueItem item, String expr) { | |
196 | private static void addFilter(ValueItem item, String expr) { | |
195 | 197 | Matcher matcher = NAME_ARG_SPLIT.matcher(expr); |
196 | 198 | |
197 | 199 | matcher.matches(); |
244 | 246 | } |
245 | 247 | |
246 | 248 | public String toString() { |
247 | StringBuilder sb = new StringBuilder("'"); | |
248 | for (ValueItem v : items) { | |
249 | sb.append(v); | |
250 | } | |
251 | sb.append("'"); | |
252 | return sb.toString(); | |
249 | return items.stream().map(ValueItem::toString).collect(Collectors.joining(" | ")); | |
253 | 250 | } |
254 | 251 | |
255 | 252 | public Set<String> getUsedTags() { |
256 | Set<String> set = new HashSet<>(); | |
257 | for (ValueItem v : items) { | |
258 | String tagname = v.getTagname(); | |
259 | if (tagname != null) | |
260 | set.add(tagname); | |
261 | } | |
262 | return set; | |
253 | return items.stream().map(ValueItem::getTagname).filter(Objects::nonNull).collect(Collectors.toSet()); | |
263 | 254 | } |
264 | 255 | } |
29 | 29 | private short tagKey; |
30 | 30 | private ValueFilter filter; |
31 | 31 | private String value; |
32 | private boolean tagname_is_local; | |
32 | private boolean tagnameIsLocal; | |
33 | 33 | |
34 | 34 | public ValueItem() { |
35 | 35 | } |
38 | 38 | this.value = value; |
39 | 39 | } |
40 | 40 | |
41 | public String getValue(Element el, Element local_el) { | |
41 | public String getValue(Element el, Element localElement) { | |
42 | 42 | if (tagname == null && value != null) |
43 | 43 | return value; // already known |
44 | 44 | |
45 | 45 | if (tagname != null) { |
46 | Element e = tagname_is_local ? local_el : el; | |
46 | Element e = tagnameIsLocal ? localElement : el; | |
47 | 47 | String tagval = e.getTag(tagKey); |
48 | 48 | if (filter != null) |
49 | value = filter.filter(tagval, local_el); | |
49 | value = filter.filter(tagval, localElement); | |
50 | 50 | else |
51 | 51 | value = tagval; |
52 | 52 | } |
67 | 67 | |
68 | 68 | public void setTagname(String tagname, boolean local) { |
69 | 69 | this.tagname = tagname; |
70 | this.tagname_is_local = local; | |
70 | this.tagnameIsLocal = local; | |
71 | 71 | this.tagKey = TagDict.getInstance().xlate(tagname); |
72 | 72 | } |
73 | 73 | |
74 | 74 | public String toString() { |
75 | // TODO: don't ignore filter. | |
75 | 76 | if (tagname == null) |
76 | 77 | return value; |
77 | if (tagname_is_local) { | |
78 | // TODO: don't ignore filter. | |
78 | if (tagnameIsLocal) { | |
79 | 79 | return "$(" + tagname + ")"; |
80 | 80 | } else { |
81 | // TODO: don't ignore filter. | |
82 | 81 | return "${" + tagname + "}"; |
83 | 82 | } |
84 | 83 | } |
54 | 54 | case '(': op = new OpenOp(); break; |
55 | 55 | case ')': op = new CloseOp(); break; |
56 | 56 | case '>': |
57 | if (value.equals(">=")) | |
57 | if (">=".equals(value)) | |
58 | 58 | op = new GTEOp(); |
59 | 59 | else |
60 | 60 | op = new GTOp(); |
61 | 61 | break; |
62 | 62 | case '<': |
63 | if (value.equals("<=")) | |
63 | if ("<=".equals(value)) | |
64 | 64 | op = new LTEOp(); |
65 | 65 | else |
66 | 66 | op = new LTOp(); |
67 | 67 | break; |
68 | 68 | case '!': |
69 | if (value.equals("!=")) | |
69 | if ("!=".equals(value)) | |
70 | 70 | op = new NotEqualOp(); |
71 | 71 | else |
72 | 72 | op = new NotOp(); |
206 | 206 | set.addAll(getSecond().getEvaluatedTagKeys()); |
207 | 207 | } else if (this instanceof NumericOp) { |
208 | 208 | set.addAll(getFirst().getEvaluatedTagKeys()); |
209 | } | |
210 | else if (this.isType(NodeType.EXISTS) || this.isType(NodeType.NOT_EXISTS) || this.isType(NodeType.NOT)) { | |
209 | } else if (this.isType(NodeType.EXISTS) || this.isType(NodeType.NOT_EXISTS) || this.isType(NodeType.NOT)) { | |
211 | 210 | set.addAll(getFirst().getEvaluatedTagKeys()); |
212 | } else if (this instanceof GetTagFunction) { | |
213 | set.add(getKeyValue()); | |
214 | 211 | } else if (this.getFirst() != null) { |
215 | 212 | System.err.println("Unhandled type of Op"); |
216 | 213 | } |
39 | 39 | if (lastCachedId > cacheId){ |
40 | 40 | throw new ExitException("fatal error: cache id invalid"); |
41 | 41 | } |
42 | lastRes = getFirst().eval(cacheId, el); | |
43 | if (lastRes == true) | |
44 | lastRes = getSecond().eval(cacheId, el); | |
42 | lastRes = getFirst().eval(cacheId, el) && getSecond().eval(cacheId, el); | |
45 | 43 | lastCachedId = cacheId; |
46 | 44 | } |
47 | //else System.out.println("cached: " + cacheId + " " + toString()); | |
48 | 45 | return lastRes; |
49 | 46 | } |
50 | ||
51 | ||
52 | 47 | |
53 | 48 | public int priority() { |
54 | 49 | return 5; |
21 | 21 | /** |
22 | 22 | * Get the second operand. |
23 | 23 | */ |
24 | public Op getSecond(); | |
24 | Op getSecond(); | |
25 | 25 | |
26 | 26 | /** |
27 | 27 | * Set the second operand. |
28 | 28 | */ |
29 | public void setSecond(Op second); | |
29 | @Override | |
30 | void setSecond(Op second); | |
30 | 31 | } |
37 | 37 | return -100; |
38 | 38 | } |
39 | 39 | |
40 | @Override | |
40 | 41 | public boolean hasHigherPriority(Op other) { |
41 | 42 | return other.isType(OPEN_PAREN); |
42 | 43 | } |
39 | 39 | return 10; |
40 | 40 | } |
41 | 41 | |
42 | @Override | |
42 | 43 | public String toString() { |
43 | return getFirst().toString() + "=" + getSecond(); | |
44 | return getFirst() + "=" + getSecond(); | |
44 | 45 | } |
45 | 46 | } |
29 | 29 | setType(NodeType.EXISTS); |
30 | 30 | } |
31 | 31 | |
32 | public <T extends Op> T setFirst(Op first) { | |
33 | return super.setFirst(first); | |
34 | } | |
35 | ||
36 | 32 | public boolean eval(Element el) { |
37 | 33 | return first.value(el) != null; |
38 | 34 | } |
38 | 38 | |
39 | 39 | private final String symbol; |
40 | 40 | |
41 | private NodeType(String symbol) { | |
41 | NodeType(String symbol) { | |
42 | 42 | this.symbol = symbol; |
43 | 43 | } |
44 | 44 |
27 | 27 | setType(NodeType.NOT_EQUALS); |
28 | 28 | } |
29 | 29 | |
30 | @Override | |
30 | 31 | public boolean eval(Element el) { |
31 | 32 | return !super.eval(el); |
32 | 33 | } |
33 | 34 | |
35 | @Override | |
34 | 36 | public String toString() { |
35 | return getFirst().toString() + "!=" + getSecond(); | |
37 | return getFirst() + "!=" + getSecond(); | |
36 | 38 | } |
37 | 39 | } |
38 | 38 | public String toString() { |
39 | 39 | if (this.hasHigherPriority(getFirst())) |
40 | 40 | return "!(" + first + ")"; |
41 | else | |
42 | return "!" + first; | |
41 | return "!" + first; | |
43 | 42 | } |
44 | 43 | } |
18 | 18 | setType(NodeType.NOT_REGEX); |
19 | 19 | } |
20 | 20 | |
21 | @Override | |
21 | 22 | public boolean eval(Element el) { |
22 | 23 | return !super.eval(el); |
23 | 24 | } |
24 | 25 | |
26 | @Override | |
25 | 27 | public String toString() { |
26 | return getFirst().toString() + "!~" + getSecond(); | |
28 | return getFirst() + "!~" + getSecond(); | |
27 | 29 | } |
28 | 30 | } |
59 | 59 | return doesCompare(inter); |
60 | 60 | } |
61 | 61 | |
62 | @Override | |
62 | 63 | public String toString() { |
63 | 64 | return first + getType().toSymbol() + getSecond(); |
64 | 65 | } |
131 | 131 | if (t == AND || t == OR) { |
132 | 132 | return AbstractOp.createOp(getType()) |
133 | 133 | .set(getFirst().copy(), getSecond().copy()); |
134 | } else | |
135 | return this; | |
134 | } | |
135 | return this; | |
136 | 136 | } |
137 | 137 | |
138 | 138 | default void augmentWith(uk.me.parabola.mkgmap.reader.osm.ElementSaver elementSaver) {} |
47 | 47 | * @param other The other operation. |
48 | 48 | * @return Always returns false. |
49 | 49 | */ |
50 | @Override | |
50 | 51 | public boolean hasHigherPriority(Op other) { |
51 | 52 | return false; |
52 | 53 | } |
39 | 39 | throw new ExitException("fatal error: cache id invalid"); |
40 | 40 | } |
41 | 41 | lastRes = getFirst().eval(cacheId, el); |
42 | if (lastRes == false) | |
42 | if (!lastRes) | |
43 | 43 | lastRes = getSecond().eval(cacheId, el); |
44 | 44 | lastCachedId = cacheId; |
45 | 45 | } |
46 | //else System.out.println("cached: " + cacheId + " " + toString()); | |
47 | 46 | return lastRes; |
48 | 47 | } |
49 | 48 |
44 | 44 | return 10; |
45 | 45 | } |
46 | 46 | |
47 | @Override | |
47 | 48 | public void setSecond(Op second) { |
48 | 49 | assert second.isType(VALUE); |
49 | 50 | super.setSecond(second); |
50 | 51 | pattern = Pattern.compile(second.getKeyValue()); |
51 | 52 | } |
52 | 53 | |
54 | @Override | |
53 | 55 | public String toString() { |
54 | return getFirst().toString() + "~" + getSecond(); | |
56 | return getFirst() + "~" + getSecond(); | |
55 | 57 | } |
56 | 58 | |
57 | 59 | } |
15 | 15 | */ |
16 | 16 | package uk.me.parabola.mkgmap.osmstyle.eval; |
17 | 17 | |
18 | import java.util.EnumMap; | |
18 | 19 | import java.util.HashMap; |
19 | 20 | import java.util.Map; |
20 | 21 | import java.util.regex.Matcher; |
30 | 31 | public class UnitConversions { |
31 | 32 | private static final Pattern CODE_RE = Pattern.compile("(.*)=>(.*)"); |
32 | 33 | |
33 | private static final Map<UnitType, Map<String, Double>> CONVERSIONS = new HashMap<>(); | |
34 | private static final EnumMap<UnitType, Map<String, Double>> CONVERSIONS = new EnumMap<>(UnitType.class); | |
34 | 35 | |
35 | 36 | private static final Map<String, Double> LENGTH_FACTORS = new HashMap<>(); |
36 | 37 | private static final Map<String, Double> SPEED_FACTORS = new HashMap<>(); |
150 | 151 | return null; |
151 | 152 | } |
152 | 153 | |
153 | private double getFactor(String unit) { | |
154 | assert isValid(); | |
155 | ||
156 | Double d = CONVERSIONS.get(unitType).get(unit); | |
157 | return d == null? 0: d; | |
158 | } | |
159 | ||
160 | 154 | private Double getConversion(String source) { |
161 | 155 | Double in = getInFactor(unitType, source); |
162 | 156 | if (in == null) |
184 | 178 | return defaultFactor; |
185 | 179 | } |
186 | 180 | |
187 | public static enum UnitType { | |
181 | public enum UnitType { | |
188 | 182 | LENGTH, |
189 | 183 | SPEED, |
190 | 184 | WEIGHT, |
14 | 14 | * Create date: 03-Nov-2008 |
15 | 15 | */ |
16 | 16 | package uk.me.parabola.mkgmap.osmstyle.eval; |
17 | ||
18 | import java.util.EnumMap; | |
17 | 19 | |
18 | 20 | import uk.me.parabola.mkgmap.reader.osm.Element; |
19 | 21 | |
38 | 40 | return 0; |
39 | 41 | } |
40 | 42 | |
43 | @Override | |
41 | 44 | public String value(Element el) { |
42 | 45 | return value; |
43 | 46 | } |
29 | 29 | super(value); |
30 | 30 | } |
31 | 31 | |
32 | @Override | |
32 | 33 | public final String value(Element el) { |
33 | 34 | // check if the element type is supported by this function |
34 | 35 | if (el instanceof Node ) { |
35 | if (supportsNode() == false) { | |
36 | if (!supportsNode()) { | |
36 | 37 | return null; |
37 | 38 | } |
38 | 39 | } else if (el instanceof Way) { |
39 | if (supportsWay() == false) { | |
40 | if (!supportsWay()) { | |
40 | 41 | return null; |
41 | 42 | } |
42 | 43 | } else if (el instanceof Relation) { |
43 | if (supportsRelation() == false) { | |
44 | if (!supportsRelation()) { | |
44 | 45 | return null; |
45 | 46 | } |
46 | 47 | } |
20 | 20 | */ |
21 | 21 | public class FunctionFactory { |
22 | 22 | |
23 | private FunctionFactory() { | |
24 | // hide default public constructor | |
25 | } | |
26 | ||
23 | 27 | /** |
24 | 28 | * Returns a new instance of a style function with the given name. |
25 | 29 | * |
27 | 31 | * @return the style function instance or {@code null} if there is no such function |
28 | 32 | */ |
29 | 33 | public static StyleFunction createFunction(String name) { |
30 | if ("length".equals(name)) | |
34 | switch (name) { | |
35 | case "length": | |
31 | 36 | return new LengthFunction(); |
32 | //} else if ("get_tag".equals(name)) | |
33 | // return new GetTagFunction(tag); | |
34 | if ("is_closed".equals(name)) { | |
37 | case "is_closed": | |
35 | 38 | return new IsClosedFunction(); |
39 | case "is_complete": | |
40 | return new IsCompleteFunction(); | |
41 | case "area_size": | |
42 | return new AreaSizeFunction(); | |
43 | case "maxspeedkmh": | |
44 | return new MaxSpeedFunction(SpeedUnit.KMH); | |
45 | case "maxspeedmph": | |
46 | return new MaxSpeedFunction(SpeedUnit.MPH); | |
47 | case "type": | |
48 | return new TypeFunction(); | |
49 | case "osmid": | |
50 | return new OsmIdFunction(); | |
51 | case "is_in": | |
52 | return new IsInFunction(); | |
53 | case "is_drive_on_left": | |
54 | return new IsDriveOnLeftFunction(); | |
55 | default: | |
56 | return null; | |
36 | 57 | } |
37 | if ("is_complete".equals(name)) { | |
38 | return new IsCompleteFunction(); | |
39 | } | |
40 | if ("area_size".equals(name)) | |
41 | return new AreaSizeFunction(); | |
42 | if ("maxspeedkmh".equals(name)) | |
43 | return new MaxSpeedFunction(SpeedUnit.KMH); | |
44 | if ("maxspeedmph".equals(name)) | |
45 | return new MaxSpeedFunction(SpeedUnit.MPH); | |
46 | if ("type".equals(name)) | |
47 | return new TypeFunction(); | |
48 | if ("osmid".equals(name)) | |
49 | return new OsmIdFunction(); | |
50 | if ("is_in".equals(name)) | |
51 | return new IsInFunction(); | |
52 | ||
53 | return null; | |
54 | 58 | } |
55 | 59 | } |
0 | package uk.me.parabola.mkgmap.osmstyle.function; | |
1 | ||
2 | import uk.me.parabola.log.Logger; | |
3 | import uk.me.parabola.mkgmap.build.LocatorConfig; | |
4 | import uk.me.parabola.mkgmap.reader.osm.Element; | |
5 | import uk.me.parabola.mkgmap.reader.osm.TagDict; | |
6 | ||
7 | /** | |
8 | * Returns the drive-on-left information for an element based on the information | |
9 | * stored in tag mkgmap:country and the LocatorConfig.xml. Returns "true" if | |
10 | * mkgmap:country is set and contains an iso code that has the driveOnLeft attribute in the LocatorConfig.xml. | |
11 | */ | |
12 | public class IsDriveOnLeftFunction extends CachedFunction { | |
13 | private static final Logger log = Logger.getLogger(IsDriveOnLeftFunction.class); | |
14 | private static final short TKM_ADM_LVL2 = TagDict.getInstance().xlate("mkgmap:admin_level2"); | |
15 | private static final short TKM_COUNTRY = TagDict.getInstance().xlate("mkgmap:country"); | |
16 | ||
17 | public IsDriveOnLeftFunction() { | |
18 | super(null); | |
19 | } | |
20 | ||
21 | protected String calcImpl(Element el) { | |
22 | String iso = el.getTag(TKM_ADM_LVL2); | |
23 | if (iso == null) | |
24 | iso = el.getTag(TKM_COUNTRY); | |
25 | if (iso == null && log.isInfoEnabled()) { | |
26 | log.info(getName(), el.getBasicLogInformation(), "Neither mkgmap:admin_level2 nor mkgmap:country is set, assuming this element is not in a drive-on-left country"); | |
27 | } | |
28 | return Boolean.toString(LocatorConfig.get().getDriveOnLeftFlag(iso)); | |
29 | } | |
30 | ||
31 | @Override | |
32 | public String getName() { | |
33 | return "is_drive_on_left"; | |
34 | } | |
35 | ||
36 | @Override | |
37 | public boolean supportsNode() { | |
38 | return true; | |
39 | } | |
40 | ||
41 | @Override | |
42 | public boolean supportsWay() { | |
43 | return true; | |
44 | } | |
45 | ||
46 | } |
170 | 170 | int assignedNumbers = 0; |
171 | 171 | startInRoad = startSegment; |
172 | 172 | endInRoad = endSegment; |
173 | if (housenumbers.isEmpty() == false) { | |
173 | if (!housenumbers.isEmpty()) { | |
174 | 174 | RoadSide rs = new RoadSide(); |
175 | 175 | if (left) |
176 | 176 | leftSide = rs; |
215 | 215 | private void fillNumbers(boolean left) { |
216 | 216 | NumberStyle style = NumberStyle.NONE; |
217 | 217 | List<HousenumberMatch> houses = getHouses(left); |
218 | if (houses.isEmpty() == false) { | |
218 | if (!houses.isEmpty()) { | |
219 | 219 | Set<CityInfo> cityInfos = new HashSet<>(); |
220 | 220 | Set<ZipCodeInfo> zipCodes = new HashSet<>(); |
221 | 221 | // get the sublist of house numbers |
231 | 231 | HousenumberMatch pred = null; |
232 | 232 | for (int i = 0; i< numHouses; i++) { |
233 | 233 | HousenumberMatch house = houses.get(i); |
234 | if (house.getCityInfo() != null && house.getCityInfo().isEmpty() == false) | |
234 | if (house.getCityInfo() != null && !house.getCityInfo().isEmpty()) | |
235 | 235 | cityInfos.add(house.getCityInfo()); |
236 | 236 | if (house.getZipCode() != null && house.getZipCode().getZipCode() != null) |
237 | 237 | zipCodes.add(house.getZipCode()); |
315 | 315 | rs.multipleZipCodes = (zipCodes.size() > 1); |
316 | 316 | if (cityInfos.size() == 1){ |
317 | 317 | CityInfo ci = cityInfos.iterator().next(); |
318 | if (ci.isEmpty() == false){ | |
319 | if (ci.equals(housenumberRoad.getRoadCityInfo()) == false) | |
320 | numbers.setCityInfo(left, ci); | |
318 | if (!ci.isEmpty() && !ci.equals(housenumberRoad.getRoadCityInfo())) { | |
319 | numbers.setCityInfo(left, ci); | |
321 | 320 | } |
322 | 321 | } |
323 | 322 | |
324 | 323 | if (zipCodes.size() == 1){ |
325 | 324 | ZipCodeInfo zipCodeInfo = zipCodes.iterator().next(); |
326 | if (zipCodeInfo.getZipCode() != null){ | |
327 | if (zipCodeInfo.equals(housenumberRoad.getRoadZipCode()) == false){ | |
325 | if (zipCodeInfo.getZipCode() != null) { | |
326 | if (!zipCodeInfo.equals(housenumberRoad.getRoadZipCode())) { | |
328 | 327 | // we found a zip code and the road doesn't yet have one, use it for the whole road |
329 | 328 | if (housenumberRoad.getRoadZipCode() == null){ |
330 | 329 | housenumberRoad.setZipCodeInfo(zipCodeInfo); |
356 | 355 | list.add(cn); |
357 | 356 | } |
358 | 357 | if (log.isInfoEnabled()) { |
359 | if (headerWasReported == false) { | |
358 | if (!headerWasReported) { | |
360 | 359 | MapRoad road = curr.getRoad(); |
361 | 360 | if (road.getStreet() == null && road.getName() == null) |
362 | 361 | log.info("final numbers for", road, curr.housenumberRoad.getName(), "in", road.getCity()); |
405 | 404 | } |
406 | 405 | } |
407 | 406 | for (curr = head; curr != null; curr = curr.next){ |
408 | while (curr.isPlausible() == false){ | |
407 | while (!curr.isPlausible()) { | |
409 | 408 | // this happens in the following cases: |
410 | 409 | // 1. correct OSM data, multiple houses build a block. Standing on the road |
411 | 410 | // you probably see a small service road which leads to the houses. |
471 | 470 | public ExtNumbers tryChange(int reason){ |
472 | 471 | ExtNumbers en = this; |
473 | 472 | if (reason == SR_FIX_ERROR){ |
474 | if (notInOrder(Numbers.LEFT) == false && notInOrder(Numbers.RIGHT) == false){ | |
473 | if (!notInOrder(Numbers.LEFT) && !notInOrder(Numbers.RIGHT)) { | |
475 | 474 | if (badNum < 0 && worstHouse != null) |
476 | 475 | badNum = worstHouse.getHousenumber(); |
477 | 476 | if (badNum > 0){ |
491 | 490 | changedInterval = true; |
492 | 491 | } else { |
493 | 492 | ExtNumbers test = en.hasNumbers() ? en : en.next; |
494 | if (test.getNumbers().isSimilar(this.getNumbers()) == false) | |
493 | if (!test.getNumbers().isSimilar(this.getNumbers())) | |
495 | 494 | changedInterval = true; |
496 | 495 | } |
497 | 496 | if (changedInterval) |
823 | 822 | toAdd = null; |
824 | 823 | else if (usedFraction > minFraction0To1 && wantedFraction < minFraction0To1 || usedFraction < maxFraction0To1 && wantedFraction > maxFraction0To1){ |
825 | 824 | toAdd = null; |
826 | } else if (allowSplitBetween == false && usedFraction > minFraction0To1 && usedFraction < maxFraction0To1){ | |
825 | } else if (!allowSplitBetween && usedFraction > minFraction0To1 && usedFraction < maxFraction0To1){ | |
827 | 826 | toAdd = null; |
828 | 827 | } else { |
829 | 828 | if (bestDist > 0.2){ |
969 | 968 | this.endInRoad = addAsNumberNode(splitSegment, toAdd); |
970 | 969 | |
971 | 970 | // distribute the houses to the new intervals |
972 | List<ArrayList<HousenumberMatch>> leftTargets = Arrays.asList(new ArrayList<HousenumberMatch>(),new ArrayList<HousenumberMatch>()); | |
973 | List<ArrayList<HousenumberMatch>> rightTargets = Arrays.asList(new ArrayList<HousenumberMatch>(),new ArrayList<HousenumberMatch>()); | |
971 | List<ArrayList<HousenumberMatch>> leftTargets = Arrays.asList(new ArrayList<>(),new ArrayList<>()); | |
972 | List<ArrayList<HousenumberMatch>> rightTargets = Arrays.asList(new ArrayList<>(),new ArrayList<>()); | |
974 | 973 | int target; |
975 | 974 | if (reason == SR_SPLIT_ROAD_END || reason == SR_OPT_LEN){ |
976 | 975 | for (int side = 0; side < 2; side++){ |
1004 | 1003 | else target = (atStart) ? 0 : 1; |
1005 | 1004 | targets.get(target).add(house); |
1006 | 1005 | } |
1007 | } else if (multipleZipOrCity(left) == false){ | |
1006 | } else if (!multipleZipOrCity(left)) { | |
1008 | 1007 | if (atStart) |
1009 | 1008 | targets.get(1).addAll(getHouses(left)); |
1010 | 1009 | else |
1228 | 1227 | for (ExtNumbers en1 = head; en1 != null; en1 = en1.next){ |
1229 | 1228 | if (anyChanges) |
1230 | 1229 | break; |
1231 | if (en1.hasNumbers() == false) | |
1230 | if (!en1.hasNumbers()) | |
1232 | 1231 | continue; |
1233 | 1232 | for (ExtNumbers en2 = en1.next; en2 != null; en2 = en2.next){ |
1234 | 1233 | if (anyChanges) |
1235 | 1234 | break; |
1236 | if (en2.hasNumbers() == false) | |
1235 | if (!en2.hasNumbers()) | |
1237 | 1236 | continue; |
1238 | 1237 | |
1239 | 1238 | int res = checkIntervals(streetName, en1, en2); |
1322 | 1321 | ok = checkIntervalBoundaries(s1, e1, s2, e2, left1 == left2 && en1.getRoad() == en2.getRoad()); |
1323 | 1322 | if (ok) |
1324 | 1323 | continue; |
1325 | if (en1.getRoad() != en2.getRoad() && en1.hasGaps == false && en2.hasGaps == false){ | |
1324 | if (en1.getRoad() != en2.getRoad() && !en1.hasGaps && !en2.hasGaps) { | |
1326 | 1325 | allOK = false; |
1327 | 1326 | continue; |
1328 | 1327 | } |
1356 | 1355 | List<HousenumberMatch> possibleRemoves1 = new ArrayList<>(); |
1357 | 1356 | List<HousenumberMatch> possibleRemoves2 = new ArrayList<>(); |
1358 | 1357 | |
1359 | if (en1.housenumberRoad.isRandom() == false && en2.housenumberRoad.isRandom() == false){ | |
1358 | if (!en1.housenumberRoad.isRandom() && !en2.housenumberRoad.isRandom()) { | |
1360 | 1359 | // check if we can move a house from en1 to en2 |
1361 | 1360 | for (HousenumberMatch house : houses1){ |
1362 | 1361 | if (house.getGroup() != null) |
1483 | 1482 | if (delta1 > 0 && delta2 > 0){ |
1484 | 1483 | if (en1.hasGaps != en2.hasGaps){ |
1485 | 1484 | if (en1.hasGaps){ |
1486 | if (possibleRemoves1.isEmpty() == false) | |
1485 | if (!possibleRemoves1.isEmpty()) | |
1487 | 1486 | splitNum = possibleRemoves1.get(0).getHousenumber(); |
1488 | 1487 | toSplit = en1; |
1489 | 1488 | } else { |
1490 | if (possibleRemoves2.isEmpty() == false) | |
1489 | if (!possibleRemoves2.isEmpty()) | |
1491 | 1490 | splitNum = possibleRemoves2.get(0).getHousenumber(); |
1492 | 1491 | toSplit = en2; |
1493 | 1492 | } |
1654 | 1653 | } |
1655 | 1654 | |
1656 | 1655 | public boolean hasNumbers(){ |
1657 | return getNumbers().isEmpty() == false; | |
1656 | return !getNumbers().isEmpty(); | |
1658 | 1657 | } |
1659 | 1658 | |
1660 | 1659 | /** |
1663 | 1662 | * @return |
1664 | 1663 | */ |
1665 | 1664 | public ExtNumbers splitLargeGaps(){ |
1666 | if (hasNumbers() == false) | |
1665 | if (!hasNumbers()) | |
1667 | 1666 | return this; |
1668 | 1667 | // calculate the length of each road segment and |
1669 | 1668 | // the overall length covered by this interval |
1857 | 1856 | double distToTarget = t.distance(p); |
1858 | 1857 | |
1859 | 1858 | if (beforeTarget){ |
1860 | if (Double.isNaN(lastDist) == false && lastDist < distToTarget) | |
1859 | if (!Double.isNaN(lastDist) && lastDist < distToTarget) | |
1861 | 1860 | beforeTarget = false; |
1862 | 1861 | } |
1863 | 1862 | if (beforeTarget && distToTarget < maxBefore || !beforeTarget && distToTarget < maxAfter){ |
1864 | 1863 | Double distLine = t.distToLineSegment(c1Dspl, c2Dspl); |
1865 | List<Coord> list = sortedByDistToLine.get(distLine); | |
1866 | if (list == null){ | |
1867 | list = new ArrayList<>(); | |
1868 | sortedByDistToLine.put(distLine, list); | |
1869 | } | |
1870 | list.add(t); | |
1864 | sortedByDistToLine.computeIfAbsent(distLine, k -> new ArrayList<>()).add(t); | |
1871 | 1865 | } |
1872 | 1866 | lastDist = distToTarget; |
1873 | 1867 | } |
1906 | 1900 | int countFilledSides = 0; |
1907 | 1901 | int countNotInOrder = 0; |
1908 | 1902 | for (ExtNumbers curr = this; curr != null; curr = curr.next){ |
1909 | if (curr.hasNumbers() == false) | |
1903 | if (!curr.hasNumbers()) | |
1910 | 1904 | continue; |
1911 | 1905 | countFilledIvls++; |
1912 | 1906 | if (curr.notInOrder(Numbers.LEFT)) |
1926 | 1920 | } |
1927 | 1921 | |
1928 | 1922 | private boolean isPlausible(){ |
1929 | if (getNumbers().isPlausible() == false) | |
1923 | if (!getNumbers().isPlausible()) | |
1930 | 1924 | return false; |
1931 | 1925 | if (multipleZipOrCity(true) || multipleZipOrCity(false)) |
1932 | 1926 | return false; |
82 | 82 | private HashMap<CityInfo, CityInfo> cityInfos = new HashMap<>(); |
83 | 83 | private HashMap<ZipCodeInfo, ZipCodeInfo> zipInfos = new HashMap<>(); |
84 | 84 | |
85 | private static final short housenumberTagKey1 = TagDict.getInstance().xlate("mkgmap:housenumber"); | |
86 | private static final short housenumberTagKey2 = TagDict.getInstance().xlate("addr:housenumber"); | |
87 | private static final short streetTagKey = TagDict.getInstance().xlate("mkgmap:street"); | |
88 | private static final short addrStreetTagKey = TagDict.getInstance().xlate("addr:street"); | |
89 | private static final short addrInterpolationTagKey = TagDict.getInstance().xlate("addr:interpolation"); | |
90 | private static final short addrPlaceTagKey = TagDict.getInstance().xlate("addr:place"); | |
91 | private static final short cityTagKey = TagDict.getInstance().xlate("mkgmap:city"); | |
92 | private static final short regionTagKey = TagDict.getInstance().xlate("mkgmap:region"); | |
93 | private static final short countryTagKey = TagDict.getInstance().xlate("mkgmap:country"); | |
94 | private static final short postalCodeTagKey = TagDict.getInstance().xlate("mkgmap:postal_code"); | |
95 | private static final short numbersTagKey = TagDict.getInstance().xlate("mkgmap:numbers"); | |
85 | private static final short TKM_HOUSENUMBER = TagDict.getInstance().xlate("mkgmap:housenumber"); | |
86 | private static final short TK_ADDR_HOUSENUMBER = TagDict.getInstance().xlate("addr:housenumber"); | |
87 | private static final short TKM_STREET = TagDict.getInstance().xlate("mkgmap:street"); | |
88 | private static final short TK_ADDR_STREET = TagDict.getInstance().xlate("addr:street"); | |
89 | private static final short TK_ADDR_INTERPOLATION = TagDict.getInstance().xlate("addr:interpolation"); | |
90 | private static final short TK_ADDR_PLACE = TagDict.getInstance().xlate("addr:place"); | |
91 | private static final short TKM_CITY = TagDict.getInstance().xlate("mkgmap:city"); | |
92 | private static final short TKM_REGION = TagDict.getInstance().xlate("mkgmap:region"); | |
93 | private static final short TKM_COUNTRY = TagDict.getInstance().xlate("mkgmap:country"); | |
94 | private static final short TKM_POSTAL_CODE = TagDict.getInstance().xlate("mkgmap:postal_code"); | |
95 | private static final short TKM_NUMBERS = TagDict.getInstance().xlate("mkgmap:numbers"); | |
96 | 96 | |
97 | 97 | public HousenumberGenerator(EnhancedProperties props) { |
98 | 98 | this.interpolationWays = new MultiHashMap<>(); |
102 | 102 | |
103 | 103 | numbersEnabled = props.containsKey("housenumbers"); |
104 | 104 | int n = props.getProperty("name-service-roads", 3); |
105 | if (n != nameSearchDepth){ | |
105 | if (n != nameSearchDepth) { | |
106 | 106 | nameSearchDepth = Math.min(25, Math.max(0, n)); |
107 | if (nameSearchDepth != n) | |
107 | if (nameSearchDepth != n) { | |
108 | 108 | System.err.println("name-service-roads=" + n + " was changed to name-service-roads=" + nameSearchDepth); |
109 | } | |
109 | 110 | } |
110 | 111 | } |
111 | 112 | |
115 | 116 | * @return the street name (or {@code null} if no street name set) |
116 | 117 | */ |
117 | 118 | private static String getStreetname(Element e) { |
118 | String streetname = e.getTag(streetTagKey); | |
119 | String streetname = e.getTag(TKM_STREET); | |
119 | 120 | if (streetname == null) { |
120 | streetname = e.getTag(addrStreetTagKey); | |
121 | streetname = e.getTag(TK_ADDR_STREET); | |
121 | 122 | } |
122 | 123 | return streetname; |
123 | 124 | } |
128 | 129 | * @return the house number (or {@code null} if no house number set) |
129 | 130 | */ |
130 | 131 | public static String getHousenumber(Element e) { |
131 | String res = e.getTag(housenumberTagKey1); | |
132 | String res = e.getTag(TKM_HOUSENUMBER); | |
132 | 133 | if (res != null) |
133 | 134 | return res; |
134 | return e.getTag(housenumberTagKey2); | |
135 | return e.getTag(TK_ADDR_HOUSENUMBER); | |
135 | 136 | } |
136 | 137 | |
137 | 138 | /** |
183 | 184 | int housenumber; |
184 | 185 | Pattern p = Pattern.compile("\\D*(\\d+)\\D?.*"); |
185 | 186 | Matcher m = p.matcher(housenumberString); |
186 | if (m.matches() == false) { | |
187 | if (!m.matches()) { | |
187 | 188 | return null; |
188 | 189 | } |
189 | 190 | try { |
196 | 197 | } |
197 | 198 | |
198 | 199 | |
199 | private HousenumberElem parseElement(Element el, String sign){ | |
200 | String city = el.getTag(cityTagKey); | |
201 | String region = el.getTag(regionTagKey); | |
202 | String country = el.getTag(countryTagKey); | |
200 | private HousenumberElem parseElement(Element el, String sign) { | |
201 | String city = el.getTag(TKM_CITY); | |
202 | String region = el.getTag(TKM_REGION); | |
203 | String country = el.getTag(TKM_COUNTRY); | |
203 | 204 | CityInfo ci = getCityInfos(city,region,country); |
204 | 205 | HousenumberElem house = new HousenumberElem(el, ci); |
205 | if (house.getLocation() == null){ | |
206 | if (house.getLocation() == null) { | |
206 | 207 | // there has been a report that indicates match.getLocation() == null |
207 | 208 | // could not reproduce so far but catching it here with some additional |
208 | 209 | // information. (WanMil) |
215 | 216 | |
216 | 217 | house.setSign(sign); |
217 | 218 | Integer hn = parseHousenumber(sign); |
218 | if (hn == null){ | |
219 | if (hn == null) { | |
219 | 220 | if (log.isDebugEnabled()) |
220 | 221 | log.debug("No housenumber (", el.toBrowseURL(), "): ", sign); |
221 | 222 | return null; |
222 | 223 | } |
223 | if (hn < 0 || hn > 1_000_000){ | |
224 | if (hn < 0 || hn > 1_000_000) { | |
224 | 225 | log.warn("Number looks wrong, is ignored",house.getSign(),hn,"element",el.toBrowseURL()); |
225 | 226 | return null; |
226 | 227 | } |
227 | 228 | house.setHousenumber(hn); |
228 | 229 | house.setStreet(getStreetname(el)); |
229 | house.setPlace(el.getTag(addrPlaceTagKey)); | |
230 | String zipStr = el.getTag(postalCodeTagKey); | |
230 | house.setPlace(el.getTag(TK_ADDR_PLACE)); | |
231 | String zipStr = el.getTag(TKM_POSTAL_CODE); | |
231 | 232 | ZipCodeInfo zip = getZipInfos(zipStr); |
232 | 233 | house.setZipCode(zip); |
233 | 234 | return house; |
238 | 239 | CityInfo ciOld = cityInfos.get(ci); |
239 | 240 | if (ciOld != null) |
240 | 241 | return ciOld; |
241 | // log.debug(ci); | |
242 | 242 | cityInfos.put(ci, ci); |
243 | 243 | return ci; |
244 | 244 | } |
252 | 252 | return zip; |
253 | 253 | } |
254 | 254 | |
255 | private HousenumberElem handleElement(Element el){ | |
255 | private HousenumberElem handleElement(Element el) { | |
256 | 256 | String sign = getHousenumber(el); |
257 | 257 | if (sign == null) |
258 | 258 | return null; |
268 | 268 | * @param n an OSM node |
269 | 269 | */ |
270 | 270 | public void addNode(Node n) { |
271 | if (numbersEnabled == false) { | |
271 | if (!numbersEnabled) { | |
272 | 272 | return; |
273 | 273 | } |
274 | if("false".equals(n.getTag(numbersTagKey))) | |
274 | if("false".equals(n.getTag(TKM_NUMBERS))) | |
275 | 275 | return; |
276 | 276 | |
277 | if ("true".equals(n.getTag(POIGeneratorHook.AREA2POI_TAG))){ | |
277 | if ("true".equals(n.getTag(POIGeneratorHook.TKM_AREA2POI))) { | |
278 | 278 | // ignore POI created for buildings |
279 | 279 | return; |
280 | 280 | } |
282 | 282 | if (houseElem == null) |
283 | 283 | return; |
284 | 284 | |
285 | if (n.getTag(HousenumberHooks.partOfInterpolationTagKey) != null) | |
285 | if (n.getTag(HousenumberHooks.TKM_PART_OF_INTERPOLATION) != null) | |
286 | 286 | interpolationNodes.put(n.getId(),houseElems.size()-1); |
287 | 287 | } |
288 | 288 | |
291 | 291 | * @param w a way |
292 | 292 | */ |
293 | 293 | public void addWay(Way w) { |
294 | if (numbersEnabled == false) { | |
294 | if (!numbersEnabled) { | |
295 | 295 | return; |
296 | 296 | } |
297 | if("false".equals(w.getTag(numbersTagKey))) | |
297 | if("false".equals(w.getTag(TKM_NUMBERS))) | |
298 | 298 | return; |
299 | 299 | |
300 | String ai = w.getTag(addrInterpolationTagKey); | |
301 | if (ai != null){ | |
300 | String ai = w.getTag(TK_ADDR_INTERPOLATION); | |
301 | if (ai != null) { | |
302 | 302 | // the way has the addr:interpolation=* tag, parse info |
303 | 303 | // created by the HousenumberHook |
304 | 304 | List<HousenumberElem> nodes = new ArrayList<>(); |
305 | String nodeIds = w.getTag(HousenumberHooks.mkgmapNodeIdsTagKey); | |
306 | if (nodeIds == null){ | |
305 | String nodeIds = w.getTag(HousenumberHooks.TKM_NODE_IDS); | |
306 | if (nodeIds == null) { | |
307 | 307 | // way was rejected by hook |
308 | 308 | } else { |
309 | 309 | String[] ids = nodeIds.split(","); |
310 | for (String idString : ids){ | |
310 | for (String idString : ids) { | |
311 | 311 | Long id = Long.decode(idString); |
312 | 312 | Integer elemPos = interpolationNodes.get(id); |
313 | if (elemPos != null){ | |
313 | if (elemPos != null) { | |
314 | 314 | HousenumberElem node = houseElems.get(elemPos); |
315 | if (node != null){ | |
315 | if (node != null) { | |
316 | 316 | assert node.getElement().getId() == id; |
317 | 317 | nodes.add(node); |
318 | 318 | } |
323 | 323 | return; |
324 | 324 | } |
325 | 325 | |
326 | if (w.hasIdenticalEndPoints()){ | |
326 | if (w.hasIdenticalEndPoints()) { | |
327 | 327 | // we are only interested in polygons now |
328 | 328 | handleElement(w); |
329 | 329 | } |
341 | 341 | */ |
342 | 342 | private void interpretInterpolationWay(Way w, List<HousenumberElem> nodes) { |
343 | 343 | int numNodes = nodes.size(); |
344 | String addrInterpolationMethod = w.getTag(addrInterpolationTagKey); | |
344 | String addrInterpolationMethod = w.getTag(TK_ADDR_INTERPOLATION); | |
345 | 345 | int step = 0; |
346 | 346 | switch (addrInterpolationMethod) { |
347 | 347 | case "all": |
361 | 361 | int pos = 0; |
362 | 362 | List<HousenumberIvl> hivls = new ArrayList<>(); |
363 | 363 | String streetName = null; |
364 | for (int i = 0; i+1 < numNodes; i++){ | |
364 | for (int i = 0; i+1 < numNodes; i++) { | |
365 | 365 | // the way have other points, find the sequence including the pair of nodes |
366 | 366 | HousenumberElem he1 = nodes.get(i); |
367 | 367 | HousenumberElem he2 = nodes.get(i+1); |
368 | int pos1 = -1, pos2 = -1; | |
369 | for (int k = pos; k < w.getPoints().size(); k++){ | |
370 | if (w.getPoints().get(k) == he1.getLocation()){ | |
368 | int pos1 = -1; | |
369 | for (int k = pos; k < w.getPoints().size(); k++) { | |
370 | if (w.getPoints().get(k) == he1.getLocation()) { | |
371 | 371 | pos1 = k; |
372 | 372 | break; |
373 | 373 | } |
374 | 374 | } |
375 | if (pos1 < 0){ | |
375 | if (pos1 < 0) { | |
376 | 376 | log.error("addr:interpolation node not found in way",w); |
377 | 377 | return; |
378 | 378 | } |
379 | for (int k = pos1+1; k < w.getPoints().size(); k++){ | |
380 | if (w.getPoints().get(k) == he2.getLocation()){ | |
379 | int pos2 = -1; | |
380 | for (int k = pos1+1; k < w.getPoints().size(); k++) { | |
381 | if (w.getPoints().get(k) == he2.getLocation()) { | |
381 | 382 | pos2 = k; |
382 | 383 | break; |
383 | 384 | } |
384 | 385 | } |
385 | if (pos2 < 0){ | |
386 | if (pos2 < 0) { | |
386 | 387 | log.error("addr:interpolation node not found in way",w); |
387 | 388 | return; |
388 | 389 | } |
389 | 390 | pos = pos2; |
390 | 391 | String street = he1.getStreet(); |
391 | if (street != null && street.equals(he2.getStreet())){ | |
392 | if (street != null && street.equals(he2.getStreet())) { | |
392 | 393 | if (streetName == null) |
393 | 394 | streetName = street; |
394 | else if (streetName.equals(street) == false){ | |
395 | else if (!streetName.equals(street)) { | |
395 | 396 | log.warn(w.toBrowseURL(),"addr:interpolation=even is used with different street names",streetName,street); |
396 | 397 | return; |
397 | 398 | } |
404 | 405 | hivl.setStep(step); |
405 | 406 | hivl.calcSteps(); |
406 | 407 | hivl.setPoints(w.getPoints().subList(pos1, pos2+1)); |
407 | // if (pos1 > 0){ | |
408 | // double angle = Utils.getAngle(w.getPoints().get(pos1-1), w.getPoints().get(pos1), w.getPoints().get(pos1+1)); | |
409 | // if (Math.abs(angle) > 75){ | |
410 | // log.warn(w.toBrowseURL(),"addr:interpolation way has sharp angle at number",start,"cannot use it"); | |
411 | // return; | |
412 | // } | |
413 | // | |
414 | // } | |
415 | 408 | |
416 | 409 | hivls.add(hivl); |
417 | if ("even".equals(addrInterpolationMethod) && (start % 2 != 0 || end % 2 != 0)){ | |
410 | if ("even".equals(addrInterpolationMethod) && (start % 2 != 0 || end % 2 != 0)) { | |
418 | 411 | log.warn(w.toBrowseURL(),"addr:interpolation=even is used with odd housenumber(s)",start,end); |
419 | 412 | return; |
420 | 413 | } |
421 | if ("odd".equals(addrInterpolationMethod) && (start % 2 == 0 || end % 2 == 0)){ | |
414 | if ("odd".equals(addrInterpolationMethod) && (start % 2 == 0 || end % 2 == 0)) { | |
422 | 415 | log.warn(w.toBrowseURL(),"addr:interpolation=odd is used with even housenumber(s)",start,end); |
423 | 416 | return; |
424 | 417 | } |
425 | 418 | |
426 | if (start == end && he1.getSign().equals(he2.getSign())){ | |
427 | // handle special case from CanVec imports | |
428 | if (pos1 == 0 && pos2 +1 == w.getPoints().size()){ | |
429 | hivl.setEqualEnds(); | |
430 | log.warn(w.toBrowseURL(),"addr:interpolation way connects two points with equal numbers, numbers are ignored"); | |
431 | } | |
419 | if (start == end && he1.getSign().equals(he2.getSign()) && pos1 == 0 | |
420 | && pos2 + 1 == w.getPoints().size()) { | |
421 | // handle special case from CanVec imports | |
422 | hivl.setEqualEnds(); | |
423 | log.warn(w.toBrowseURL(), | |
424 | "addr:interpolation way connects two points with equal numbers, numbers are ignored"); | |
432 | 425 | } |
433 | 426 | } |
434 | 427 | } |
445 | 438 | public void addRoad(Way osmRoad, MapRoad road) { |
446 | 439 | allRoads.add(road); |
447 | 440 | if (numbersEnabled) { |
448 | if("false".equals(osmRoad.getTag(numbersTagKey))) | |
441 | if("false".equals(osmRoad.getTag(TKM_NUMBERS))) | |
449 | 442 | road.setSkipHousenumberProcessing(true); |
450 | 443 | |
451 | 444 | /* |
453 | 446 | * only the first. This ensures that we don't try to assign numbers from bad |
454 | 447 | * matches to these copies. |
455 | 448 | */ |
456 | if(!road.isSkipHousenumberProcessing()){ | |
457 | if (firstRoadSameOSMWay != null){ | |
458 | if (firstRoadSameOSMWay.getRoadDef().getId() == road.getRoadDef().getId()){ | |
459 | if (firstRoadSameOSMWay.getPoints().equals(road.getPoints())){ | |
460 | road.setSkipHousenumberProcessing(true); | |
461 | return; | |
462 | } | |
463 | } | |
464 | } | |
449 | if(!road.isSkipHousenumberProcessing()) { | |
450 | if (firstRoadSameOSMWay != null | |
451 | && firstRoadSameOSMWay.getRoadDef().getId() == road.getRoadDef().getId() | |
452 | && firstRoadSameOSMWay.getPoints().equals(road.getPoints())) { | |
453 | road.setSkipHousenumberProcessing(true); | |
454 | return; | |
455 | } | |
465 | 456 | road.getPoints().get(0).setNumberNode(true); |
466 | 457 | road.getPoints().get(road.getPoints().size() - 1).setNumberNode(true); |
467 | 458 | firstRoadSameOSMWay = road; |
479 | 470 | public void addRelation(Relation r) { |
480 | 471 | String relType = r.getTag("type"); |
481 | 472 | // the wiki says that we should also evaluate type=street |
482 | if ("associatedStreet".equals(relType) || "street".equals(relType)){ | |
473 | if ("associatedStreet".equals(relType) || "street".equals(relType)) { | |
483 | 474 | List<Element> houses= new ArrayList<>(); |
484 | 475 | List<Element> streets = new ArrayList<>(); |
485 | 476 | for (Map.Entry<String, Element> member : r.getElements()) { |
499 | 490 | streets.add(w); |
500 | 491 | break; |
501 | 492 | case "": |
502 | if (w.getTag("highway") != null){ | |
493 | if (w.getTag("highway") != null) { | |
503 | 494 | streets.add(w); |
504 | 495 | continue; |
505 | 496 | } |
520 | 511 | String streetNameFromRoads = null; |
521 | 512 | List<Element> unnamedStreetElems = new ArrayList<>(); |
522 | 513 | boolean nameFromStreetsIsUnclear = false; |
523 | if (streets.isEmpty() == false) { | |
514 | if (!streets.isEmpty()) { | |
524 | 515 | for (Element street : streets) { |
525 | String roadName = street.getTag(streetTagKey); | |
516 | String roadName = street.getTag(TKM_STREET); | |
526 | 517 | if (roadName == null) |
527 | 518 | roadName = street.getTag("name"); |
528 | if (roadName == null){ | |
519 | if (roadName == null) { | |
529 | 520 | unnamedStreetElems.add(street); |
530 | 521 | continue; |
531 | 522 | } |
532 | 523 | if (streetNameFromRoads == null) |
533 | 524 | streetNameFromRoads = roadName; |
534 | else if (streetNameFromRoads.equals(roadName) == false) | |
525 | else if (!streetNameFromRoads.equals(roadName)) { | |
535 | 526 | nameFromStreetsIsUnclear = true; |
536 | } | |
537 | } | |
538 | if (streetName == null){ | |
539 | if (nameFromStreetsIsUnclear == false) | |
527 | } | |
528 | } | |
529 | } | |
530 | if (streetName == null) { | |
531 | if (!nameFromStreetsIsUnclear) { | |
540 | 532 | streetName = streetNameFromRoads; |
541 | else { | |
533 | } else { | |
542 | 534 | log.warn("Relation",r.toBrowseURL(),": ignored, street name is not clear."); |
543 | 535 | return; |
544 | 536 | } |
545 | 537 | |
546 | 538 | } else { |
547 | if (streetNameFromRoads != null){ | |
548 | if (nameFromStreetsIsUnclear == false && streetName.equals(streetNameFromRoads) == false){ | |
549 | if (unnamedStreetElems.isEmpty() == false){ | |
539 | if (streetNameFromRoads != null) { | |
540 | if (!nameFromStreetsIsUnclear && !streetName.equals(streetNameFromRoads)) { | |
541 | if (!unnamedStreetElems.isEmpty()) { | |
550 | 542 | log.warn("Relation",r.toBrowseURL(),": ignored, street name is not clear."); |
551 | 543 | return; |
552 | 544 | } |
553 | 545 | log.warn("Relation",r.toBrowseURL(),": street name is not clear, using the name from the way, not that of the relation."); |
554 | 546 | streetName = streetNameFromRoads; |
555 | 547 | } |
556 | else if (nameFromStreetsIsUnclear == true){ | |
548 | else if (nameFromStreetsIsUnclear) { | |
557 | 549 | log.warn("Relation",r.toBrowseURL(),": street name is not clear, using the name from the relation."); |
558 | 550 | } |
559 | 551 | } |
560 | 552 | } |
561 | 553 | int countModHouses = 0; |
562 | if (streetName != null && streetName.isEmpty() == false){ | |
554 | if (streetName != null && !streetName.isEmpty()) { | |
563 | 555 | for (Element house : houses) { |
564 | if (addStreetTagFromRel(r, house, streetName) ) | |
556 | if (addStreetTagFromRel(r, house, streetName)) | |
565 | 557 | countModHouses++; |
566 | 558 | } |
567 | 559 | for (Element street : unnamedStreetElems) { |
568 | street.addTag(streetTagKey, streetName); | |
560 | street.addTag(TKM_STREET, streetName); | |
569 | 561 | street.addTag("name", streetName); |
570 | 562 | } |
571 | 563 | } |
572 | if (log.isInfoEnabled()){ | |
573 | if (countModHouses > 0 || !unnamedStreetElems.isEmpty()){ | |
564 | if (log.isInfoEnabled()) { | |
565 | if (countModHouses > 0 || !unnamedStreetElems.isEmpty()) { | |
574 | 566 | if (countModHouses > 0) |
575 | 567 | log.info("Relation",r.toBrowseURL(),": added tag mkgmap:street=",streetName,"to",countModHouses,"of",houses.size(),"house members" ); |
576 | 568 | if (!unnamedStreetElems.isEmpty()) |
587 | 579 | * Add the tag mkgmap:street=streetName to the element of the |
588 | 580 | * relation if it does not already have a street name tag. |
589 | 581 | */ |
590 | private static boolean addStreetTagFromRel(Relation r, Element house, String streetName){ | |
582 | private static boolean addStreetTagFromRel(Relation r, Element house, String streetName) { | |
591 | 583 | String addrStreet = getStreetname(house); |
592 | if (addrStreet == null){ | |
593 | house.addTag(streetTagKey, streetName); | |
584 | if (addrStreet == null) { | |
585 | house.addTag(TKM_STREET, streetName); | |
594 | 586 | if (log.isDebugEnabled()) |
595 | 587 | log.debug("Relation",r.toBrowseURL(),": adding tag mkgmap:street=" + streetName, "to house",house.toBrowseURL()); |
596 | 588 | return true; |
597 | 589 | } |
598 | else if (addrStreet.equals(streetName) == false){ | |
599 | if (house.getTag(streetTagKey) != null){ | |
590 | if (!addrStreet.equals(streetName)) { | |
591 | if (house.getTag(TKM_STREET) != null) { | |
600 | 592 | 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"); |
601 | house.deleteTag(streetTagKey); | |
602 | } | |
603 | else | |
593 | house.deleteTag(TKM_STREET); | |
594 | } else { | |
604 | 595 | log.warn("Relation",r.toBrowseURL(),": street name from relation doesn't match existing name for house",house.toBrowseURL()); |
596 | } | |
605 | 597 | } |
606 | 598 | return false; |
607 | 599 | } |
622 | 614 | initialHousesForRoads = null; |
623 | 615 | log.info("found",hnrList.size(),"road candidates for address search"); |
624 | 616 | |
625 | useAddrPlaceTag(hnrList); | |
617 | useAddrPlaceTag(); | |
626 | 618 | Map<MapRoad, HousenumberRoad> road2HousenumberRoadMap = new HashMap<>(); |
627 | for (HousenumberRoad hnr : hnrList){ | |
619 | for (HousenumberRoad hnr : hnrList) { | |
628 | 620 | road2HousenumberRoadMap.put(hnr.getRoad(), hnr); |
629 | 621 | } |
630 | 622 | Int2ObjectOpenHashMap<HashSet<MapRoad>> nodeId2RoadLists = new Int2ObjectOpenHashMap<>(); |
631 | for (MapRoad road : allRoads){ | |
632 | for (Coord co : road.getPoints()){ | |
623 | for (MapRoad road : allRoads) { | |
624 | for (Coord co : road.getPoints()) { | |
633 | 625 | if (co.getId() == 0) |
634 | 626 | continue; |
635 | 627 | HashSet<MapRoad> connectedRoads = nodeId2RoadLists.get(co.getId()); |
636 | if (connectedRoads == null){ | |
628 | if (connectedRoads == null) { | |
637 | 629 | connectedRoads = new HashSet<>(); |
638 | 630 | nodeId2RoadLists.put(co.getId(), connectedRoads); |
639 | 631 | } |
642 | 634 | } |
643 | 635 | List<HousenumberRoad> addedRoads = new ArrayList<>(); |
644 | 636 | Iterator<HousenumberRoad> iter = hnrList.iterator(); |
645 | while (iter.hasNext()){ | |
637 | while (iter.hasNext()) { | |
646 | 638 | HousenumberRoad hnr = iter.next(); |
647 | 639 | |
648 | 640 | List<HousenumberMatch> lostHouses = hnr.checkStreetName(road2HousenumberRoadMap, nodeId2RoadLists); |
649 | for (HousenumberMatch house : lostHouses){ | |
641 | for (HousenumberMatch house : lostHouses) { | |
650 | 642 | MapRoad r = house.getRoad(); |
651 | if (r != null){ | |
643 | if (r != null) { | |
652 | 644 | HousenumberRoad hnr2 = road2HousenumberRoadMap.get(r); |
653 | if (hnr2 == null){ | |
645 | if (hnr2 == null) { | |
654 | 646 | CityInfo ci = getCityInfos(r.getCity(), r.getRegion(), r.getCountry()); |
655 | 647 | hnr2 = new HousenumberRoad(r, ci, Arrays.asList(house)); |
656 | 648 | if (r.getZip() != null) |
662 | 654 | } |
663 | 655 | } |
664 | 656 | } |
665 | if (hnr.getName() == null){ | |
657 | if (hnr.getName() == null) { | |
666 | 658 | iter.remove(); |
667 | for (HousenumberMatch house : hnr.getHouses()){ | |
659 | for (HousenumberMatch house : hnr.getHouses()) { | |
668 | 660 | log.warn("found no plausible road name for address",house.toBrowseURL(),", closest road id:",house.getRoad()); |
669 | 661 | } |
670 | 662 | } |
671 | 663 | } |
672 | 664 | hnrList.addAll(addedRoads); |
673 | 665 | // TODO: interpolate addr:interpolation houses |
674 | removeDupsGroupedByCityAndName(hnrList); | |
666 | removeDupsGroupedByCityAndName(); | |
675 | 667 | |
676 | 668 | // group by street name and city |
677 | 669 | TreeMap<String, TreeMap<CityInfo, List<HousenumberRoad>>> streetnameCityRoadMap = new TreeMap<>(); |
678 | for (HousenumberRoad hnr : hnrList){ | |
679 | TreeMap<CityInfo, List<HousenumberRoad>> cluster = streetnameCityRoadMap.get(hnr.getName()); | |
680 | if (cluster == null){ | |
681 | cluster = new TreeMap<>(); | |
682 | streetnameCityRoadMap.put(hnr.getName(), cluster); | |
683 | } | |
684 | List<HousenumberRoad> roadsInCluster = cluster.get(hnr.getRoadCityInfo()); | |
685 | if (roadsInCluster == null){ | |
686 | roadsInCluster = new ArrayList<>(); | |
687 | cluster.put(hnr.getRoadCityInfo(), roadsInCluster); | |
688 | } | |
689 | roadsInCluster.add(hnr); | |
690 | } | |
691 | ||
692 | for (Entry<String, TreeMap<CityInfo, List<HousenumberRoad>>> streetNameEntry : streetnameCityRoadMap.entrySet()){ | |
670 | for (HousenumberRoad hnr : hnrList) { | |
671 | TreeMap<CityInfo, List<HousenumberRoad>> cluster = streetnameCityRoadMap.computeIfAbsent(hnr.getName(), | |
672 | k -> new TreeMap<>()); | |
673 | cluster.computeIfAbsent(hnr.getRoadCityInfo(), k -> new ArrayList<>()).add(hnr); | |
674 | } | |
675 | ||
676 | for (Entry<String, TreeMap<CityInfo, List<HousenumberRoad>>> streetNameEntry : streetnameCityRoadMap.entrySet()) { | |
693 | 677 | String streetName = streetNameEntry.getKey(); |
694 | 678 | |
695 | for (Entry<CityInfo, List<HousenumberRoad>> clusterEntry : streetNameEntry.getValue().entrySet()){ | |
679 | for (Entry<CityInfo, List<HousenumberRoad>> clusterEntry : streetNameEntry.getValue().entrySet()) { | |
696 | 680 | useInterpolationInfo(streetName, clusterEntry.getValue(), road2HousenumberRoadMap); |
697 | 681 | } |
698 | 682 | |
699 | for (Entry<CityInfo, List<HousenumberRoad>> clusterEntry : streetNameEntry.getValue().entrySet()){ | |
683 | for (Entry<CityInfo, List<HousenumberRoad>> clusterEntry : streetNameEntry.getValue().entrySet()) { | |
700 | 684 | List<HousenumberRoad> roadsInCluster = clusterEntry.getValue(); |
701 | if (log.isDebugEnabled()){ | |
685 | if (log.isDebugEnabled()) { | |
702 | 686 | log.debug("processing road(s) with name",streetName,"in",clusterEntry.getKey() ); |
703 | 687 | } |
704 | for (HousenumberRoad hnr : roadsInCluster){ | |
688 | for (HousenumberRoad hnr : roadsInCluster) { | |
705 | 689 | hnr.buildIntervals(); |
706 | 690 | } |
707 | 691 | boolean optimized = false; |
708 | for (int loop = 0; loop < 10; loop++){ | |
709 | for (HousenumberRoad hnr : roadsInCluster){ | |
692 | for (int loop = 0; loop < 10; loop++) { | |
693 | for (HousenumberRoad hnr : roadsInCluster) { | |
710 | 694 | hnr.checkIntervals(); |
711 | 695 | } |
712 | 696 | checkWrongRoadAssignmments(roadsInCluster); |
713 | 697 | boolean changed = hasChanges(roadsInCluster); |
714 | if (!optimized && !changed){ | |
715 | for (HousenumberRoad hnr : roadsInCluster){ | |
698 | if (!optimized && !changed) { | |
699 | for (HousenumberRoad hnr : roadsInCluster) { | |
716 | 700 | hnr.improveSearchResults(); |
717 | 701 | } |
718 | 702 | changed = hasChanges(roadsInCluster); |
721 | 705 | if (!changed) |
722 | 706 | break; |
723 | 707 | } |
724 | for (HousenumberRoad hnr : roadsInCluster){ | |
708 | for (HousenumberRoad hnr : roadsInCluster) { | |
725 | 709 | hnr.setNumbers(); |
726 | 710 | } |
727 | 711 | } |
728 | 712 | } |
729 | 713 | } |
730 | 714 | |
731 | if (log.isInfoEnabled()){ | |
732 | for (HousenumberElem house : houseElems){ | |
733 | if (house.getRoad() == null){ | |
715 | if (log.isInfoEnabled()) { | |
716 | for (HousenumberElem house : houseElems) { | |
717 | if (house.getRoad() == null) { | |
734 | 718 | if (house.getStreet() != null) |
735 | 719 | log.info("found no plausible road for house number element",house.toBrowseURL(),house.getStreet(),house.getSign()); |
736 | 720 | else |
739 | 723 | } |
740 | 724 | } |
741 | 725 | for (MapRoad r : allRoads) { |
742 | if (log.isDebugEnabled()){ | |
726 | if (log.isDebugEnabled()) { | |
743 | 727 | List<Numbers> finalNumbers = r.getRoadDef().getNumbersList(); |
744 | if (finalNumbers != null){ | |
728 | if (finalNumbers != null) { | |
745 | 729 | log.info("id:"+r.getRoadDef().getId(),", final numbers,",r,"in",r.getCity()); |
746 | for (Numbers cn : finalNumbers){ | |
730 | for (Numbers cn : finalNumbers) { | |
747 | 731 | if (cn.isEmpty()) |
748 | 732 | continue; |
749 | 733 | log.info("id:"+r.getRoadDef().getId(),", Left: ",cn.getLeftNumberStyle(),cn.getIndex(),"Start:",cn.getLeftStart(),"End:",cn.getLeftEnd()); |
758 | 742 | private List<HousenumberRoad> createHousenumberRoads( |
759 | 743 | MultiHashMap<MapRoad, HousenumberMatch> initialHousesForRoads) { |
760 | 744 | List<HousenumberRoad> hnrList = new ArrayList<>(); |
761 | for (MapRoad road : allRoads){ | |
745 | for (MapRoad road : allRoads) { | |
762 | 746 | if (road.isSkipHousenumberProcessing()) |
763 | 747 | continue; |
764 | 748 | List<HousenumberMatch> houses = initialHousesForRoads.get(road); |
783 | 767 | |
784 | 768 | long t3 = System.currentTimeMillis(); |
785 | 769 | MultiHashMap<MapRoad,HousenumberMatch> initialHousesForRoads = new MultiHashMap<>(); |
786 | for (int i = 0; i < houseElems.size(); i++){ | |
770 | for (int i = 0; i < houseElems.size(); i++) { | |
787 | 771 | HousenumberElem house = houseElems.get(i); |
788 | 772 | HousenumberMatch bestMatch = roadSegmentIndex.createHousenumberMatch(house); |
789 | 773 | houseElems.set(i, bestMatch); |
790 | if (bestMatch.getRoad() == null){ | |
774 | if (bestMatch.getRoad() == null) { | |
791 | 775 | bestMatch.setIgnored(true); // XXX maybe create a pseudo road with zero length? |
792 | 776 | // log.warn("found no plausible road for house number element",house.getElement().toBrowseURL()); |
793 | 777 | continue; |
800 | 784 | return initialHousesForRoads; |
801 | 785 | } |
802 | 786 | |
803 | private void useAddrPlaceTag(List<HousenumberRoad> hnrList){ | |
787 | private void useAddrPlaceTag() { | |
804 | 788 | HashMap<CityInfo,MultiHashMap<String,HousenumberMatch>> cityPlaceHouseMap = new LinkedHashMap<>(); |
805 | for (int i = 0; i < houseElems.size(); i++){ | |
789 | for (int i = 0; i < houseElems.size(); i++) { | |
806 | 790 | HousenumberElem house = houseElems.get(i); |
807 | if (house.getRoad() == null) | |
791 | if (house.getRoad() == null || house.getPlace() == null) | |
808 | 792 | continue; |
809 | if (house.getPlace() == null) | |
810 | continue; | |
811 | if (house instanceof HousenumberMatch){ | |
793 | if (house instanceof HousenumberMatch) { | |
812 | 794 | HousenumberMatch hm = (HousenumberMatch) house; |
813 | if (hm.getHousenumberRoad() == null) | |
814 | continue; | |
815 | } else | |
816 | continue; | |
817 | MultiHashMap<String, HousenumberMatch> subMap = cityPlaceHouseMap.get(house.getCityInfo()); | |
818 | if (subMap == null){ | |
819 | subMap = new MultiHashMap<>(); | |
820 | cityPlaceHouseMap.put(house.getCityInfo(), subMap); | |
821 | } | |
822 | subMap.add(house.getPlace(), (HousenumberMatch) house); | |
795 | if (hm.getHousenumberRoad() != null) { | |
796 | cityPlaceHouseMap.computeIfAbsent(house.getCityInfo(), k -> new MultiHashMap<>()) | |
797 | .add(house.getPlace(), (HousenumberMatch) house); | |
798 | } | |
799 | } | |
823 | 800 | } |
824 | 801 | log.info("analysing",cityPlaceHouseMap.size(),"cities with addr:place=* houses" ); |
825 | for (Entry<CityInfo, MultiHashMap<String, HousenumberMatch>> topEntry : cityPlaceHouseMap.entrySet()){ | |
802 | for (Entry<CityInfo, MultiHashMap<String, HousenumberMatch>> topEntry : cityPlaceHouseMap.entrySet()) { | |
826 | 803 | CityInfo cityInfo = topEntry.getKey(); |
827 | 804 | List<String> placeNames = new ArrayList<>(topEntry.getValue().keySet()); |
828 | 805 | placeNames.sort(null); |
829 | for (String placeName : placeNames){ | |
806 | for (String placeName : placeNames) { | |
830 | 807 | List<HousenumberMatch> placeHouses = topEntry.getValue().get(placeName); |
831 | 808 | HashSet<HousenumberRoad> roads = new LinkedHashSet<>(); |
832 | 809 | Int2IntOpenHashMap usedNumbers = new Int2IntOpenHashMap(); |
838 | 815 | int roadsWithNames = 0; |
839 | 816 | int unnamedCloseRoads = 0; |
840 | 817 | |
841 | for (HousenumberMatch house : placeHouses){ | |
842 | if (house.getStreet() != null ){ | |
818 | for (HousenumberMatch house : placeHouses) { | |
819 | if (house.getStreet() != null ) { | |
843 | 820 | ++housesWithStreet; |
844 | if (house.getStreet().equalsIgnoreCase(house.getRoad().getStreet())){ | |
821 | if (house.getStreet().equalsIgnoreCase(house.getRoad().getStreet())) { | |
845 | 822 | ++housesWithMatchingStreet; |
846 | 823 | } |
847 | 824 | } else { |
852 | 829 | if (added && house.getRoad().getStreet() != null) |
853 | 830 | ++roadsWithNames; |
854 | 831 | int oldCount = usedNumbers.put(house.getHousenumber(),1); |
855 | if (oldCount != 0){ | |
832 | if (oldCount != 0) { | |
856 | 833 | usedNumbers.put(house.getHousenumber(), oldCount + 1); |
857 | 834 | ++dupNumbers; |
858 | 835 | } |
859 | 836 | Integer oldSignCount = usedSigns.put(house.getSign(), 1); |
860 | if (oldSignCount != null){ | |
837 | if (oldSignCount != null) { | |
861 | 838 | usedSigns.put(house.getSign(), oldSignCount + 1); |
862 | 839 | ++dupSigns; |
863 | 840 | } |
864 | 841 | } |
865 | 842 | |
866 | if (log.isDebugEnabled()){ | |
843 | if (log.isDebugEnabled()) { | |
867 | 844 | log.debug("place",placeName,"in city",cityInfo, ":", "houses:", placeHouses.size(), |
868 | 845 | ",duplicate numbers/signs:", dupNumbers+"/"+dupSigns, |
869 | 846 | ",roads (named/unnamed):", roads.size(),"("+roadsWithNames+"/"+(roads.size()- roadsWithNames)+")", |
871 | 848 | ",street = name of closest road:", housesWithMatchingStreet, |
872 | 849 | ",houses without addr:street near named road:", unnamedCloseRoads); |
873 | 850 | } |
874 | if ((float) dupSigns / placeHouses.size() < 0.25 ){ | |
851 | if ((float) dupSigns / placeHouses.size() < 0.25 ) { | |
875 | 852 | if (log.isDebugEnabled()) |
876 | 853 | log.debug("will not use gaps in intervals for roads in",placeName ); |
877 | for (HousenumberRoad hnr : roads){ | |
854 | for (HousenumberRoad hnr : roads) { | |
878 | 855 | hnr.setRemoveGaps(true); |
879 | 856 | } |
880 | 857 | } |
881 | if (placeHouses.size() > housesWithStreet){ // XXX: threshold value? | |
858 | if (placeHouses.size() > housesWithStreet) { // XXX: threshold value? | |
882 | 859 | LongArrayList ids = new LongArrayList(); |
883 | for (HousenumberRoad hnr : roads){ | |
860 | for (HousenumberRoad hnr : roads) { | |
884 | 861 | ids.add(hnr.getRoad().getRoadDef().getId()); |
885 | 862 | hnr.addPlaceName(placeName); |
886 | 863 | } |
900 | 877 | * @param initialHousesForRoads map that is updated when wrong road assignments were found |
901 | 878 | */ |
902 | 879 | private void handleInterpolationWays(MultiHashMap<MapRoad, HousenumberMatch> initialHousesForRoads) { |
903 | for (Entry<String, List<HousenumberIvl>> entry : interpolationWays.entrySet()){ | |
880 | for (Entry<String, List<HousenumberIvl>> entry : interpolationWays.entrySet()) { | |
904 | 881 | List<HousenumberIvl> infos = entry.getValue(); |
905 | for (HousenumberIvl info : infos){ | |
906 | if (info.isBad()){ | |
882 | for (HousenumberIvl info : infos) { | |
883 | if (info.isBad()) { | |
907 | 884 | continue; |
908 | 885 | } |
909 | 886 | boolean isOK = info.setNodeRefs(interpolationNodes, houseElems); |
910 | 887 | if (!isOK) |
911 | 888 | continue; |
912 | 889 | HousenumberMatch[] houses = info.getHouseNodes(); |
913 | MapRoad uncheckedRoads[] = new MapRoad[houses.length]; | |
890 | MapRoad[] uncheckedRoads = new MapRoad[houses.length]; | |
914 | 891 | for (int i = 0 ; i < houses.length; i++) |
915 | 892 | uncheckedRoads[i] = houses[i].getRoad(); |
916 | 893 | isOK = info.checkRoads(); |
917 | 894 | // check if houses are assigned to different roads now |
918 | 895 | houses = info.getHouseNodes(); |
919 | for (int i = 0 ; i < houses.length; i++){ | |
920 | if (houses[i].getRoad() != uncheckedRoads[i]){ | |
896 | for (int i = 0 ; i < houses.length; i++) { | |
897 | if (houses[i].getRoad() != uncheckedRoads[i]) { | |
921 | 898 | initialHousesForRoads.removeMapping(uncheckedRoads[i], houses[i]); |
922 | if (houses[i].isIgnored() == false) | |
899 | if (!houses[i].isIgnored()) | |
923 | 900 | initialHousesForRoads.add(houses[i].getRoad(), houses[i]); |
924 | 901 | else { |
925 | 902 | if (!isOK) |
931 | 908 | } |
932 | 909 | } |
933 | 910 | |
934 | private void removeDupsGroupedByCityAndName(List<HousenumberRoad> hnrList){ | |
911 | private void removeDupsGroupedByCityAndName() { | |
935 | 912 | HashMap<CityInfo,MultiHashMap<String,HousenumberMatch>> cityNameHouseMap = new LinkedHashMap<>(); |
936 | for (int i = 0; i < houseElems.size(); i++){ | |
937 | HousenumberElem house = houseElems.get(i); | |
913 | for (HousenumberElem house : houseElems) { | |
938 | 914 | if (house.getRoad() == null) |
939 | 915 | continue; |
940 | if (house instanceof HousenumberMatch){ | |
916 | if (house instanceof HousenumberMatch) { | |
941 | 917 | HousenumberMatch hm = (HousenumberMatch) house; |
942 | 918 | if (hm.isIgnored()) |
943 | 919 | continue; |
944 | 920 | HousenumberRoad hnr = hm.getHousenumberRoad(); |
945 | 921 | if (hnr == null || hnr.getName() == null) |
946 | 922 | continue; |
947 | MultiHashMap<String, HousenumberMatch> subMap = cityNameHouseMap.get(hm.getCityInfo()); | |
948 | if (subMap == null){ | |
949 | subMap = new MultiHashMap<>(); | |
950 | cityNameHouseMap.put(hm.getCityInfo(), subMap); | |
951 | } | |
952 | subMap.add(hnr.getName(), hm); | |
953 | } | |
954 | } | |
955 | ||
956 | for (Entry<CityInfo, MultiHashMap<String, HousenumberMatch>> topEntry : cityNameHouseMap.entrySet()){ | |
957 | for (Entry<String, List<HousenumberMatch>> entry : topEntry.getValue().entrySet()){ | |
923 | cityNameHouseMap.computeIfAbsent(hm.getCityInfo(), k -> new MultiHashMap<>()).add(hnr.getName(), hm); | |
924 | } | |
925 | } | |
926 | ||
927 | for (Entry<CityInfo, MultiHashMap<String, HousenumberMatch>> topEntry : cityNameHouseMap.entrySet()) { | |
928 | for (Entry<String, List<HousenumberMatch>> entry : topEntry.getValue().entrySet()) { | |
958 | 929 | markSimpleDuplicates(entry.getKey(), entry.getValue()); |
959 | 930 | } |
960 | ||
961 | 931 | } |
962 | 932 | } |
963 | 933 | |
982 | 952 | long t1 = System.currentTimeMillis(); |
983 | 953 | |
984 | 954 | List<MapRoad> unnamedRoads = new ArrayList<>(); |
985 | for (MapRoad road : allRoads){ | |
955 | for (MapRoad road : allRoads) { | |
986 | 956 | if (road.isSkipHousenumberProcessing()) |
987 | 957 | continue; |
988 | 958 | |
989 | if (road.getStreet() == null){ | |
959 | if (road.getStreet() == null) { | |
990 | 960 | // if a road has a label but getStreet() returns null, |
991 | 961 | // the road probably has a ref. We assume these are not service roads. |
992 | if (road.getName() == null){ | |
962 | if (road.getName() == null) { | |
993 | 963 | unnamedRoads.add(road); |
994 | 964 | List<Coord> nodes = new ArrayList<>(); |
995 | for (Coord co : road.getPoints()){ | |
965 | for (Coord co : road.getPoints()) { | |
996 | 966 | if (co.getId() != 0) |
997 | 967 | nodes.add(co); |
998 | 968 | } |
1008 | 978 | log.debug("identifyServiceRoad step 1 took",(t2-t1),"ms, found",roadNamesByNodeIds.size(),"nodes to check and",numUnnamedRoads,"unnamed roads" ); |
1009 | 979 | long t3 = System.currentTimeMillis(); |
1010 | 980 | int named = 0; |
1011 | for (int pass = 1; pass <= nameSearchDepth; pass ++){ | |
981 | for (int pass = 1; pass <= nameSearchDepth; pass ++) { | |
1012 | 982 | int unnamed = 0; |
1013 | 983 | List<MapRoad> namedRoads = new ArrayList<>(); |
1014 | for (int j = 0; j < unnamedRoads.size(); j++){ | |
984 | for (int j = 0; j < unnamedRoads.size(); j++) { | |
1015 | 985 | MapRoad road = unnamedRoads.get(j); |
1016 | 986 | if (road == null) |
1017 | 987 | continue; |
1018 | 988 | unnamed++; |
1019 | 989 | List<Coord> coordNodes = coordNodesUnnamedRoads.get(road); |
1020 | 990 | String name = null; |
1021 | for (Coord co : coordNodes){ | |
1022 | if (unclearNodeIds.contains(co.getId())){ | |
991 | for (Coord co : coordNodes) { | |
992 | if (unclearNodeIds.contains(co.getId())) { | |
1023 | 993 | name = null; |
1024 | 994 | unnamedRoads.set(j, null); // don't process again |
1025 | 995 | break; |
1029 | 999 | continue; |
1030 | 1000 | if (name == null) |
1031 | 1001 | name = possibleName; |
1032 | else if (name.equals(possibleName) == false){ | |
1002 | else if (!name.equals(possibleName)) { | |
1033 | 1003 | name = null; |
1034 | 1004 | unnamedRoads.set(j, null); // don't process again |
1035 | 1005 | break; |
1036 | 1006 | } |
1037 | 1007 | } |
1038 | if (name != null){ | |
1008 | if (name != null) { | |
1039 | 1009 | named++; |
1040 | 1010 | road.setStreet(name); |
1041 | 1011 | namedRoads.add(road); |
1042 | 1012 | unnamedRoads.set(j, null); // don't process again |
1043 | 1013 | } |
1044 | 1014 | } |
1045 | for (MapRoad road : namedRoads){ | |
1015 | for (MapRoad road : namedRoads) { | |
1046 | 1016 | road.setNamedByHousenumberProcessing(true); |
1047 | 1017 | String name = road.getStreet(); |
1048 | 1018 | if (log.isDebugEnabled()) |
1057 | 1027 | log.debug("pass",pass,unnamed,named); |
1058 | 1028 | } |
1059 | 1029 | long t4 = System.currentTimeMillis(); |
1060 | if (log.isDebugEnabled()){ | |
1030 | if (log.isDebugEnabled()) { | |
1061 | 1031 | log.debug("indentifyServiceRoad step 2 took",(t4-t3),"ms, found a name for",named,"of",numUnnamedRoads,"roads" ); |
1062 | 1032 | } |
1063 | return; | |
1064 | 1033 | } |
1065 | 1034 | |
1066 | 1035 | private void identifyNodes(List<Coord> roadPoints, |
1067 | 1036 | String streetName, Int2ObjectOpenHashMap<String> roadNamesByNodeIds, HashSet<Integer> unclearNodes) { |
1068 | for (Coord co : roadPoints){ | |
1069 | if (co.getId() != 0){ | |
1037 | for (Coord co : roadPoints) { | |
1038 | if (co.getId() != 0) { | |
1070 | 1039 | String prevName = roadNamesByNodeIds.put(co.getId(), streetName); |
1071 | if (prevName != null){ | |
1072 | if (prevName.equals(streetName) == false) | |
1073 | unclearNodes.add(co.getId()); | |
1040 | if (prevName != null && !prevName.equals(streetName)) { | |
1041 | unclearNodes.add(co.getId()); | |
1074 | 1042 | } |
1075 | 1043 | } |
1076 | 1044 | } |
1096 | 1064 | if (interpolationInfos.isEmpty()) |
1097 | 1065 | return; |
1098 | 1066 | List<HousenumberMatch> housesWithIvlInfo = new ArrayList<>(); |
1099 | for (HousenumberRoad hnr : roadsInCluster){ | |
1100 | for (HousenumberMatch house : hnr.getHouses()){ | |
1067 | for (HousenumberRoad hnr : roadsInCluster) { | |
1068 | for (HousenumberMatch house : hnr.getHouses()) { | |
1101 | 1069 | if (house.getIntervalInfoRefs() > 0) |
1102 | 1070 | housesWithIvlInfo.add(house); |
1103 | 1071 | } |
1112 | 1080 | Int2ObjectOpenHashMap<HousenumberMatch> existingNumbers = new Int2ObjectOpenHashMap<>(); |
1113 | 1081 | HashMap<HousenumberIvl, List<HousenumberMatch>> housesToAdd = new LinkedHashMap<>(); |
1114 | 1082 | |
1115 | for (HousenumberRoad hnr : roadsInCluster){ | |
1083 | for (HousenumberRoad hnr : roadsInCluster) { | |
1116 | 1084 | for (HousenumberMatch house : hnr.getHouses()) |
1117 | 1085 | existingNumbers.put(house.getHousenumber(), house); |
1118 | 1086 | } |
1119 | 1087 | int inCluster = 0; |
1120 | 1088 | boolean allOK = true; |
1121 | 1089 | // for loop may change the list |
1122 | for (int i = 0; i < interpolationInfos.size(); i++){ | |
1090 | for (int i = 0; i < interpolationInfos.size(); i++) { | |
1123 | 1091 | HousenumberIvl hivl = interpolationInfos.get(i); |
1124 | if (hivl.inCluster(housesWithIvlInfo) == false || hivl.ignoreForInterpolation()) | |
1092 | if (!hivl.inCluster(housesWithIvlInfo) || hivl.ignoreForInterpolation()) | |
1125 | 1093 | continue; |
1126 | 1094 | ++inCluster; |
1127 | 1095 | String hivlDesc = hivl.getDesc(); |
1128 | 1096 | HousenumberIvl hivlTest = simpleDupCheckSet.get(hivlDesc); |
1129 | if (hivlTest != null){ | |
1097 | if (hivlTest != null) { | |
1130 | 1098 | // happens often in Canada (CanVec imports): two or more addr:interpolation ways with similar meaning |
1131 | 1099 | // sometimes at completely different road parts, sometimes at exactly the same |
1132 | 1100 | log.warn("found additional addr:interpolation way with same meaning, is ignored:",streetName, hivl, hivlTest); |
1138 | 1106 | |
1139 | 1107 | id2IvlMap.put(hivl.getId(), hivl); |
1140 | 1108 | List<HousenumberMatch> interpolatedHouses = hivl.getInterpolatedHouses(); |
1141 | if (interpolatedHouses.isEmpty() == false){ | |
1142 | if (interpolatedHouses.get(0).getRoad() == null){ | |
1109 | if (!interpolatedHouses.isEmpty()) { | |
1110 | if (interpolatedHouses.get(0).getRoad() == null) { | |
1143 | 1111 | // the interpolated houses are not all along one road |
1144 | 1112 | findRoadForInterpolatedHouses(streetName, interpolatedHouses, roadsInCluster); |
1145 | 1113 | } |
1146 | 1114 | |
1147 | 1115 | int dupCount = 0; |
1148 | for (HousenumberMatch house : interpolatedHouses){ | |
1116 | for (HousenumberMatch house : interpolatedHouses) { | |
1149 | 1117 | if (house.getRoad() == null || house.getDistance() > HousenumberIvl.MAX_INTERPOLATION_DISTANCE_TO_ROAD) |
1150 | 1118 | continue; |
1151 | 1119 | boolean ignoreGenOnly = false; |
1152 | 1120 | HousenumberMatch old = interpolatedNumbers.get(house.getHousenumber()); |
1153 | if (old == null){ | |
1121 | if (old == null) { | |
1154 | 1122 | ignoreGenOnly = true; |
1155 | 1123 | old = existingNumbers.get(house.getHousenumber()); |
1156 | 1124 | } |
1157 | if (old != null){ | |
1125 | if (old != null) { | |
1158 | 1126 | // try to build new intervals using existing node |
1159 | 1127 | HousenumberIvl[] splitIvls = hivl.trySplitAt(old); |
1160 | if (splitIvls != null){ | |
1128 | if (splitIvls != null) { | |
1161 | 1129 | log.info("adding address",streetName,old,old.toBrowseURL(),"to addr:interpolation way, replacing", hivl,"by",Arrays.deepToString(splitIvls)); |
1162 | 1130 | interpolationInfos.add(splitIvls[0]); |
1163 | 1131 | interpolationInfos.add(splitIvls[1]); |
1167 | 1135 | // forget both or only one ? Which one? |
1168 | 1136 | house.setIgnored(true); |
1169 | 1137 | double distToOld = old.getLocation().distance(house.getLocation()); |
1170 | if (distToOld > MAX_DISTANCE_SAME_NUM){ | |
1138 | if (distToOld > MAX_DISTANCE_SAME_NUM) { | |
1171 | 1139 | if (old.isInterpolated()) |
1172 | 1140 | log.info("conflict caused by addr:interpolation way",streetName,hivl,"and interpolated address",old,"at",old.getLocation().toDegreeString()); |
1173 | 1141 | else |
1174 | 1142 | log.info("conflict caused by addr:interpolation way",streetName,hivl,"and address element",old,"at",old.getLocation().toDegreeString()); |
1175 | 1143 | dupCount++; |
1176 | if (!ignoreGenOnly){ | |
1144 | if (!ignoreGenOnly) { | |
1177 | 1145 | old.setIgnored(true); |
1178 | 1146 | long ivlId = old.getElement().getOriginalId(); |
1179 | 1147 | HousenumberIvl bad = id2IvlMap.get(ivlId); |
1185 | 1153 | } |
1186 | 1154 | if (hivl.ignoreForInterpolation()) |
1187 | 1155 | continue; |
1188 | if (dupCount > 0){ | |
1156 | if (dupCount > 0) { | |
1189 | 1157 | log.warn("addr:interpolation way",streetName,hivl,"is ignored, it produces",dupCount,"duplicate number(s) too far from existing nodes"); |
1190 | 1158 | badIvls.add(hivl); |
1191 | 1159 | } |
1198 | 1166 | } |
1199 | 1167 | if (inCluster == 0) |
1200 | 1168 | return; |
1201 | for (HousenumberIvl badIvl: badIvls){ | |
1169 | for (HousenumberIvl badIvl: badIvls) { | |
1202 | 1170 | allOK = false; |
1203 | 1171 | badIvl.ignoreNodes(); |
1204 | 1172 | housesToAdd.remove(badIvl); |
1205 | 1173 | } |
1206 | 1174 | Iterator<Entry<HousenumberIvl, List<HousenumberMatch>>> iter = housesToAdd.entrySet().iterator(); |
1207 | while (iter.hasNext()){ | |
1175 | while (iter.hasNext()) { | |
1208 | 1176 | Entry<HousenumberIvl, List<HousenumberMatch>> entry = iter.next(); |
1209 | 1177 | if (log.isInfoEnabled()) |
1210 | 1178 | log.info("using generated house numbers from addr:interpolation way",entry.getKey()); |
1211 | for (HousenumberMatch house : entry.getValue()){ | |
1212 | if (house.getRoad() != null && house.isIgnored() == false){ | |
1179 | for (HousenumberMatch house : entry.getValue()) { | |
1180 | if (house.getRoad() != null && !house.isIgnored()) { | |
1213 | 1181 | HousenumberRoad hnr = road2HousenumberRoadMap.get(house.getRoad()); |
1214 | if (hnr == null){ | |
1182 | if (hnr == null) { | |
1215 | 1183 | log.error("internal error: found no housenumber road for interpolated house",house.toBrowseURL()); |
1216 | 1184 | continue; |
1217 | 1185 | } |
1219 | 1187 | } |
1220 | 1188 | } |
1221 | 1189 | } |
1222 | if (log.isDebugEnabled()){ | |
1190 | if (log.isDebugEnabled()) { | |
1223 | 1191 | if (allOK) |
1224 | 1192 | log.debug("found no problems with interpolated numbers from addr:interpolations ways for roads with name",streetName); |
1225 | 1193 | else |
1241 | 1209 | house.setDistance(Double.POSITIVE_INFINITY); // make sure that we don't use an old match |
1242 | 1210 | house.setRoad(null); |
1243 | 1211 | List<HousenumberMatch> matches = new ArrayList<>(); |
1244 | for (HousenumberRoad hnr : roadsInCluster){ | |
1212 | for (HousenumberRoad hnr : roadsInCluster) { | |
1245 | 1213 | MapRoad r = hnr.getRoad(); |
1246 | 1214 | // make sure that we use the street info if available |
1247 | if (house.getPlace() != null){ | |
1248 | if (house.getStreet() != null && r.getStreet() != null && house.getStreet().equals(r.getStreet()) == false) | |
1249 | continue; | |
1215 | if (house.getPlace() != null && house.getStreet() != null && r.getStreet() != null | |
1216 | && house.getStreet().equals(r.getStreet())) { | |
1217 | continue; | |
1250 | 1218 | } |
1251 | 1219 | HousenumberMatch test = new HousenumberMatch(house); |
1252 | 1220 | findClosestRoadSegment(test, r); |
1253 | if (test.getRoad() != null && test.getGroup() != null || test.getDistance() < MAX_DISTANCE_TO_ROAD){ | |
1221 | if (test.getRoad() != null && test.getGroup() != null || test.getDistance() < MAX_DISTANCE_TO_ROAD) { | |
1254 | 1222 | matches.add(test); |
1255 | 1223 | } |
1256 | 1224 | } |
1257 | if (matches.isEmpty()){ | |
1225 | if (matches.isEmpty()) { | |
1258 | 1226 | house.setIgnored(true); |
1259 | 1227 | continue; |
1260 | } | |
1261 | ||
1262 | ||
1263 | HousenumberMatch closest, best; | |
1264 | best = closest = matches.get(0); | |
1265 | ||
1266 | if (matches.size() > 1){ | |
1228 | } | |
1229 | ||
1230 | HousenumberMatch closest = matches.get(0); | |
1231 | HousenumberMatch best = closest; | |
1232 | ||
1233 | if (matches.size() > 1) { | |
1267 | 1234 | // multiple roads, we assume that the closest is the best |
1268 | 1235 | // but we may have to check the alternatives as well |
1269 | 1236 | |
1275 | 1242 | house.setSegmentFrac(best.getSegmentFrac()); |
1276 | 1243 | house.setRoad(best.getRoad()); |
1277 | 1244 | house.setSegment(best.getSegment()); |
1278 | for (HousenumberMatch altHouse : matches){ | |
1245 | for (HousenumberMatch altHouse : matches) { | |
1279 | 1246 | if (altHouse.getRoad() != best.getRoad() && altHouse.getDistance() < MAX_DISTANCE_TO_ROAD) |
1280 | 1247 | house.addAlternativeRoad(altHouse.getRoad()); |
1281 | 1248 | } |
1286 | 1253 | house.calcRoadSide(); |
1287 | 1254 | } |
1288 | 1255 | // plausibility check for duplicate house numbers |
1289 | if (prev != null && prev.getHousenumber() == house.getHousenumber()){ | |
1256 | if (prev != null && prev.getHousenumber() == house.getHousenumber() | |
1257 | && prev.getSign().equals(house.getSign())) { | |
1290 | 1258 | // duplicate number (e.g. 10 and 10 or 10 and 10A or 10A and 10B) |
1291 | if (prev.getSign().equals(house.getSign())){ | |
1292 | prev.setDuplicate(true); | |
1293 | house.setDuplicate(true); | |
1294 | } | |
1295 | } | |
1296 | ||
1297 | if (house.getRoad() == null) { | |
1298 | if (house.isIgnored() == false) | |
1299 | log.warn("found no plausible road for house number element",house.toBrowseURL(),"(",streetName,house.getSign(),")"); | |
1300 | } | |
1301 | if (!house.isIgnored()) | |
1259 | prev.setDuplicate(true); | |
1260 | house.setDuplicate(true); | |
1261 | } | |
1262 | ||
1263 | if (house.getRoad() == null && !house.isIgnored()) { | |
1264 | log.warn("found no plausible road for house number element",house.toBrowseURL(),"(",streetName,house.getSign(),")"); | |
1265 | } | |
1266 | if (!house.isIgnored()) { | |
1302 | 1267 | prev = house; |
1303 | } | |
1304 | } | |
1305 | ||
1268 | } | |
1269 | } | |
1270 | } | |
1306 | 1271 | |
1307 | 1272 | private static void markSimpleDuplicates(String streetName, List<HousenumberMatch> housesNearCluster) { |
1308 | 1273 | List<HousenumberMatch> sortedHouses = new ArrayList<>(housesNearCluster); |
1309 | 1274 | sortedHouses.sort(new HousenumberMatchByNumComparator()); |
1310 | 1275 | int n = sortedHouses.size(); |
1311 | for (int i = 1; i < n; i++){ | |
1276 | for (int i = 1; i < n; i++) { | |
1312 | 1277 | HousenumberMatch house1 = sortedHouses.get(i-1); |
1313 | 1278 | if (house1.isIgnored()) |
1314 | 1279 | continue; |
1317 | 1282 | continue; |
1318 | 1283 | if (house1.getHousenumber() != house2.getHousenumber()) |
1319 | 1284 | continue; |
1320 | if (house1.getRoad() == house2.getRoad()){ | |
1285 | if (house1.getRoad() == house2.getRoad()) { | |
1321 | 1286 | if (house1.isFarDuplicate()) |
1322 | 1287 | house2.setFarDuplicate(true); |
1323 | 1288 | continue; // handled later |
1331 | 1296 | else { |
1332 | 1297 | CityInfo city1 = house1.getCityInfo(); |
1333 | 1298 | CityInfo city2 = house2.getCityInfo(); |
1334 | if (city1 != null && city1.equals(city2) == false){ | |
1299 | if (city1 != null && !city1.equals(city2)) { | |
1335 | 1300 | markFarDup = true; |
1336 | 1301 | } |
1337 | 1302 | } |
1338 | if (markFarDup){ | |
1303 | if (markFarDup) { | |
1339 | 1304 | if (log.isDebugEnabled()) |
1340 | 1305 | log.debug("keeping duplicate numbers assigned to different roads in cluster ", streetName, house1,house2); |
1341 | 1306 | house1.setFarDuplicate(true); |
1343 | 1308 | continue; |
1344 | 1309 | } |
1345 | 1310 | boolean ignore2nd = false; |
1346 | if (dist < 30){ | |
1311 | if (dist < 30) { | |
1347 | 1312 | ignore2nd = true; |
1348 | 1313 | } else { |
1349 | 1314 | Coord c1s = house1.getRoad().getPoints().get(house1.getSegment()); |
1350 | 1315 | Coord c1e = house1.getRoad().getPoints().get(house1.getSegment() + 1); |
1351 | 1316 | Coord c2s = house2.getRoad().getPoints().get(house2.getSegment()); |
1352 | 1317 | Coord c2e = house2.getRoad().getPoints().get(house2.getSegment() + 1); |
1353 | if (c1s == c2s || c1s == c2e || c1e == c2s || c1e == c2e){ | |
1318 | if (c1s == c2s || c1s == c2e || c1e == c2s || c1e == c2e) { | |
1354 | 1319 | // roads are directly connected |
1355 | 1320 | ignore2nd = true; |
1356 | 1321 | } |
1357 | 1322 | } |
1358 | if (ignore2nd){ | |
1323 | if (ignore2nd) { | |
1359 | 1324 | house2.setIgnored(true); |
1360 | if (log.isDebugEnabled()){ | |
1325 | if (log.isDebugEnabled()) { | |
1361 | 1326 | if (house1.getSign().equals(house2.getSign())) |
1362 | 1327 | log.debug("duplicate number is ignored",streetName,house2.getSign(),house2.toBrowseURL() ); |
1363 | 1328 | else |
1388 | 1353 | house.setDistance(Double.POSITIVE_INFINITY); |
1389 | 1354 | boolean foundGroupLink = false; |
1390 | 1355 | int end = Math.min(r.getPoints().size(), stopSeg+1); |
1391 | for (int node = firstSeg; node + 1 < end; node++){ | |
1356 | for (int node = firstSeg; node + 1 < end; node++) { | |
1392 | 1357 | Coord c1 = r.getPoints().get(node); |
1393 | 1358 | Coord c2 = r.getPoints().get(node + 1); |
1394 | 1359 | double frac = getFrac(c1, c2, cx); |
1395 | 1360 | double dist = distanceToSegment(c1,c2,cx,frac); |
1396 | if (house.getGroup() != null && house.getGroup().linkNode == c1){ | |
1397 | if (c1.highPrecEquals(c2) == false){ | |
1361 | if (house.getGroup() != null && house.getGroup().linkNode == c1) { | |
1362 | if (!c1.highPrecEquals(c2)) { | |
1398 | 1363 | log.debug("block doesn't have zero length segment! Road:",r,house); |
1399 | 1364 | } |
1400 | 1365 | foundGroupLink = true; |
1411 | 1376 | } |
1412 | 1377 | } |
1413 | 1378 | |
1414 | if (house.getGroup() != null && house.getGroup().linkNode != null && foundGroupLink == false){ | |
1379 | if (house.getGroup() != null && house.getGroup().linkNode != null && !foundGroupLink) { | |
1415 | 1380 | log.debug(r,house,"has a group but the link was not found, should only happen after split of zero-length-segment"); |
1416 | 1381 | } |
1417 | if (oldRoad == r){ | |
1418 | if (house.getDistance() > MAX_DISTANCE_TO_ROAD + 2.5 && oldDist <= MAX_DISTANCE_TO_ROAD ){ | |
1419 | log.warn("line distorted? Road segment was moved by more than", | |
1420 | String.format("%.2f m", 2.5), ", from address", r, house.getSign()); | |
1421 | } | |
1422 | } | |
1423 | } | |
1424 | ||
1425 | private static boolean hasChanges( | |
1426 | List<HousenumberRoad> housenumberRoads) { | |
1427 | for (HousenumberRoad hnr : housenumberRoads){ | |
1428 | if (hnr.isChanged()) | |
1429 | return true; | |
1430 | } | |
1431 | return false; | |
1382 | if (oldRoad == r && house.getDistance() > MAX_DISTANCE_TO_ROAD + 2.5 && oldDist <= MAX_DISTANCE_TO_ROAD) { | |
1383 | log.warn("line distorted? Road segment was moved by more than", String.format("%.2f m", 2.5), | |
1384 | ", from address", r, house.getSign()); | |
1385 | } | |
1386 | } | |
1387 | ||
1388 | private static boolean hasChanges(List<HousenumberRoad> housenumberRoads) { | |
1389 | return housenumberRoads.stream().anyMatch(HousenumberRoad::isChanged); | |
1432 | 1390 | } |
1433 | 1391 | |
1434 | 1392 | /** |
1438 | 1396 | private static void checkWrongRoadAssignmments(List<HousenumberRoad> housenumberRoads) { |
1439 | 1397 | if (housenumberRoads.size() < 2) |
1440 | 1398 | return; |
1441 | for (int loop = 0; loop < 10; loop++){ | |
1399 | for (int loop = 0; loop < 10; loop++) { | |
1442 | 1400 | boolean changed = false; |
1443 | for (int i = 0; i+1 < housenumberRoads.size(); i++){ | |
1401 | for (int i = 0; i+1 < housenumberRoads.size(); i++) { | |
1444 | 1402 | HousenumberRoad hnr1 = housenumberRoads.get(i); |
1445 | 1403 | hnr1.setChanged(false); |
1446 | for (int j = i+1; j < housenumberRoads.size(); j++){ | |
1404 | for (int j = i+1; j < housenumberRoads.size(); j++) { | |
1447 | 1405 | HousenumberRoad hnr2 = housenumberRoads.get(j); |
1448 | 1406 | hnr2.setChanged(false); |
1449 | 1407 | hnr1.checkWrongRoadAssignmments(hnr2); |
1450 | if (hnr1.isChanged()){ | |
1408 | if (hnr1.isChanged()) { | |
1451 | 1409 | changed = true; |
1452 | 1410 | hnr1.checkIntervals(); |
1453 | 1411 | } |
1454 | if (hnr2.isChanged()){ | |
1412 | if (hnr2.isChanged()) { | |
1455 | 1413 | changed = true; |
1456 | 1414 | hnr2.checkIntervals(); |
1457 | 1415 | } |
1561 | 1519 | if (otherMatches.isEmpty()) |
1562 | 1520 | return closestMatch; |
1563 | 1521 | HousenumberMatch bestMatch = closestMatch; |
1564 | for (HousenumberMatch alternative : otherMatches){ | |
1522 | for (HousenumberMatch alternative : otherMatches) { | |
1565 | 1523 | if (alternative == closestMatch) |
1566 | 1524 | continue; |
1567 | 1525 | if (closestMatch.getDistance() < alternative.getDistance()) |
1573 | 1531 | Coord cx = closestMatch.getLocation(); |
1574 | 1532 | double dist = closestMatch.getDistance(); |
1575 | 1533 | double dist1 = cx.distance(c1); |
1576 | double angle, altAngle; | |
1534 | double angle; | |
1577 | 1535 | if (dist1 == dist) |
1578 | 1536 | angle = Utils.getAngle(c2, c1, cx); |
1579 | 1537 | else |
1582 | 1540 | Coord c4 = alternative.getRoad().getPoints().get(alternative.getSegment()+1); |
1583 | 1541 | |
1584 | 1542 | double dist3 = cx.distance(c3); |
1543 | double altAngle; | |
1585 | 1544 | if (dist3 == dist) |
1586 | 1545 | altAngle = Utils.getAngle(c4, c3, cx); |
1587 | 1546 | else |
1588 | 1547 | altAngle = Utils.getAngle(c3, c4, cx); |
1589 | 1548 | double delta = 90 - Math.abs(angle); |
1590 | 1549 | double deltaAlt = 90 - Math.abs(altAngle); |
1591 | if (delta > deltaAlt){ | |
1550 | if (delta > deltaAlt) { | |
1592 | 1551 | bestMatch = alternative; |
1593 | c1 = c3; | |
1594 | c2 = c4; | |
1595 | } | |
1596 | } | |
1597 | if (log.isDebugEnabled()){ | |
1598 | if (closestMatch.getRoad() != bestMatch.getRoad()){ | |
1552 | } | |
1553 | } | |
1554 | if (log.isDebugEnabled()) { | |
1555 | if (closestMatch.getRoad() != bestMatch.getRoad()) { | |
1599 | 1556 | log.debug("check angle: using road",bestMatch.getRoad().getRoadDef().getId(),"instead of",closestMatch.getRoad().getRoadDef().getId(),"for house number",bestMatch.getSign(),bestMatch.toBrowseURL()); |
1600 | } else if (closestMatch != bestMatch){ | |
1557 | } else if (closestMatch != bestMatch) { | |
1601 | 1558 | log.debug("check angle: using road segment",bestMatch.getSegment(),"instead of",closestMatch.getSegment(),"for house number element",bestMatch.toBrowseURL()); |
1602 | 1559 | } |
1603 | 1560 | } |
1612 | 1569 | * @return {@code true} point lies on the left side; {@code false} point lies on the right side |
1613 | 1570 | */ |
1614 | 1571 | public static boolean isLeft(Coord spoint1, Coord spoint2, Coord point) { |
1615 | if (spoint1.distance(spoint2) == 0){ | |
1572 | if (spoint1.distance(spoint2) == 0) { | |
1616 | 1573 | log.warn("road segment length is 0 in left/right evaluation"); |
1617 | 1574 | } |
1618 | 1575 | |
1678 | 1635 | * @param length |
1679 | 1636 | * @return string with length, e.g. "0.23 m" or "116.12 m" |
1680 | 1637 | */ |
1681 | public static String formatLen(double length){ | |
1638 | public static String formatLen(double length) { | |
1682 | 1639 | return String.format("%.2f m", length); |
1683 | 1640 | } |
1684 | 1641 | |
1727 | 1684 | |
1728 | 1685 | } |
1729 | 1686 | |
1730 | public void build(List<MapRoad> roads){ | |
1731 | for (MapRoad road : roads){ | |
1687 | public void build(List<MapRoad> roads) { | |
1688 | for (MapRoad road : roads) { | |
1732 | 1689 | if (road.isSkipHousenumberProcessing()) |
1733 | 1690 | continue; |
1734 | 1691 | List<Coord> points = road.getPoints(); |
1737 | 1694 | |
1738 | 1695 | List<RoadPoint> roadPoints = new ArrayList<>(); |
1739 | 1696 | RoadPoint rp; |
1740 | for (int i = 0; i + 1 < points.size(); i++){ | |
1697 | for (int i = 0; i + 1 < points.size(); i++) { | |
1741 | 1698 | Coord c1 = points.get(i); |
1742 | 1699 | Coord c2 = points.get(i + 1); |
1743 | 1700 | int part = 0; |
1744 | 1701 | rp = new RoadPoint(road, c1, i, part++); |
1745 | 1702 | roadPoints.add(rp); |
1746 | while (true){ | |
1703 | while (true) { | |
1747 | 1704 | double segLen = c1.distance(c2); |
1748 | 1705 | double frac = maxSegmentLength / segLen; |
1749 | 1706 | if (frac >= 1) |
1752 | 1709 | c1 = c1.makeBetweenPoint(c2, frac); |
1753 | 1710 | rp = new RoadPoint(road, c1, i, part++); |
1754 | 1711 | roadPoints.add(rp); |
1755 | segLen -= maxSegmentLength; | |
1756 | 1712 | } |
1757 | 1713 | } |
1758 | 1714 | int last = points.size() - 1; |
1760 | 1716 | roadPoints.add(rp); |
1761 | 1717 | |
1762 | 1718 | Collections.shuffle(roadPoints); |
1763 | for (RoadPoint toAdd : roadPoints){ | |
1719 | for (RoadPoint toAdd : roadPoints) { | |
1764 | 1720 | int id = toAdd.p.getId(); |
1765 | 1721 | if (id == 0) |
1766 | 1722 | kdTree.add(toAdd); |
1767 | 1723 | else { |
1768 | 1724 | // Coord node, add only once to KD-tree with all roads |
1769 | 1725 | Set<RoadPoint> set = nodeId2RoadPointMap.get(id); |
1770 | if (set == null){ | |
1726 | if (set == null) { | |
1771 | 1727 | set = new LinkedHashSet<>(); |
1772 | 1728 | nodeId2RoadPointMap.put(id, set); |
1773 | 1729 | kdTree.add(toAdd); |
1778 | 1734 | } |
1779 | 1735 | } |
1780 | 1736 | |
1781 | public List<RoadPoint> getCLoseRoadPoints(HousenumberElem house){ | |
1737 | public List<RoadPoint> getCLoseRoadPoints(HousenumberElem house) { | |
1782 | 1738 | Set<RoadPoint> closeRoadPoints = kdTree.findClosePoints(house, kdSearchRange); |
1783 | 1739 | List<RoadPoint> result = new ArrayList<>(); |
1784 | for (RoadPoint rp : closeRoadPoints){ | |
1740 | for (RoadPoint rp : closeRoadPoints) { | |
1785 | 1741 | int id = rp.p.getId(); |
1786 | 1742 | if (id != 0) |
1787 | 1743 | result.addAll(nodeId2RoadPointMap.get(id)); |
1796 | 1752 | * @param house |
1797 | 1753 | * @return null if no road was found, else a {@link HousenumberMatch} instance |
1798 | 1754 | */ |
1799 | public HousenumberMatch createHousenumberMatch(HousenumberElem house){ | |
1755 | public HousenumberMatch createHousenumberMatch(HousenumberElem house) { | |
1800 | 1756 | HousenumberMatch closest = new HousenumberMatch(house); |
1801 | 1757 | List<RoadPoint> closeRoadPoints = getCLoseRoadPoints(house); |
1802 | 1758 | if (closeRoadPoints.isEmpty()) |
1818 | 1774 | BitSet testedSegments = new BitSet(); |
1819 | 1775 | MapRoad lastRoad = null; |
1820 | 1776 | HousenumberMatch hnm = null; |
1821 | for (RoadPoint rp : closeRoadPoints){ | |
1822 | if (house.getStreet() != null){ | |
1777 | for (RoadPoint rp : closeRoadPoints) { | |
1778 | if (house.getStreet() != null && rp.r.getStreet() != null | |
1779 | && !house.getStreet().equalsIgnoreCase(rp.r.getStreet())) { | |
1823 | 1780 | // we have a given street name, accept only roads with similar name or no name |
1824 | if (rp.r.getStreet() != null && house.getStreet().equalsIgnoreCase(rp.r.getStreet()) == false) | |
1825 | continue; | |
1826 | } | |
1827 | if (rp.r != lastRoad){ | |
1781 | continue; | |
1782 | } | |
1783 | if (rp.r != lastRoad) { | |
1828 | 1784 | hnm = new HousenumberMatch(house); |
1829 | 1785 | testedSegments.clear(); |
1830 | 1786 | matches.add(hnm); |
1831 | 1787 | lastRoad = rp.r; |
1832 | 1788 | } |
1833 | double oldDist = hnm.getDistance(); | |
1834 | if (rp.partOfSeg >= 0){ | |
1835 | // rp.p is at start or before end of segment | |
1836 | if (testedSegments.get(rp.segment) == false){ | |
1837 | testedSegments.set(rp.segment); | |
1838 | checkSegment(hnm, rp.r, rp.segment); | |
1839 | } | |
1840 | } | |
1841 | if (rp.partOfSeg < 0){ | |
1842 | // rp is at end of road, check (also) the preceding segment | |
1843 | if (rp.segment < 1){ | |
1844 | log.error("internal error: trying to use invalid roadPoint",rp); | |
1845 | } else if (testedSegments.get(rp.segment - 1) == false){ | |
1846 | testedSegments.set(rp.segment-1); | |
1847 | checkSegment(hnm, rp.r, rp.segment-1); | |
1848 | } | |
1849 | } | |
1850 | if (oldDist == hnm.getDistance()) | |
1851 | continue; | |
1789 | if (rp.partOfSeg >= 0 && !testedSegments.get(rp.segment)) { | |
1790 | // rp.p is at start or before end of segment | |
1791 | testedSegments.set(rp.segment); | |
1792 | checkSegment(hnm, rp.r, rp.segment); | |
1793 | } | |
1794 | if (rp.partOfSeg < 0) { | |
1795 | // rp is at end of road, check (also) the preceding segment | |
1796 | if (rp.segment < 1) { | |
1797 | log.error("internal error: trying to use invalid roadPoint", rp); | |
1798 | } else if (!testedSegments.get(rp.segment - 1)) { | |
1799 | testedSegments.set(rp.segment - 1); | |
1800 | checkSegment(hnm, rp.r, rp.segment - 1); | |
1801 | } | |
1802 | } | |
1852 | 1803 | } |
1853 | 1804 | if (matches.isEmpty()) |
1854 | 1805 | return closest; // closest has not yet a road |
1861 | 1812 | if (closest.getStreet() != null && closest.getStreet().equalsIgnoreCase(closest.getRoad().getStreet())) |
1862 | 1813 | bestMatchingName = closest; |
1863 | 1814 | |
1864 | for (HousenumberMatch altHouse : matches){ | |
1815 | for (HousenumberMatch altHouse : matches) { | |
1865 | 1816 | if (altHouse.getDistance() >= MAX_DISTANCE_TO_ROAD) |
1866 | 1817 | break; |
1867 | if (altHouse.getRoad() != closest.getRoad()){ | |
1868 | if (house.getStreet() != null && altHouse.getDistance() > closest.getDistance()){ | |
1869 | if (house.getStreet().equalsIgnoreCase(altHouse.getRoad().getStreet())){ | |
1870 | if (bestMatchingName == null || bestMatchingName.getDistance() > altHouse.getDistance()){ | |
1818 | if (altHouse.getRoad() != closest.getRoad()) { | |
1819 | if (house.getStreet() != null && altHouse.getDistance() > closest.getDistance()) { | |
1820 | if (house.getStreet().equalsIgnoreCase(altHouse.getRoad().getStreet())) { | |
1821 | if (bestMatchingName == null || bestMatchingName.getDistance() > altHouse.getDistance()) { | |
1871 | 1822 | bestMatchingName = altHouse; |
1872 | 1823 | } |
1873 | 1824 | } else { |
1878 | 1829 | closest.addAlternativeRoad(altHouse.getRoad()); |
1879 | 1830 | } |
1880 | 1831 | } |
1881 | if (bestMatchingName != null){ | |
1882 | if (house.getStreet().equals(bestMatchingName.getRoad().getStreet()) == false){ | |
1883 | log.warn("accepting match in spite of different capitalisation" , house.getStreet(),house.getSign(), bestMatchingName.getRoad().getRoadDef(), "house:",house.toBrowseURL()); | |
1884 | bestMatchingName.setStreet(bestMatchingName.getRoad().getStreet()); | |
1885 | closest.setStreet(bestMatchingName.getStreet()); | |
1886 | } | |
1832 | if (bestMatchingName != null && !house.getStreet().equals(bestMatchingName.getRoad().getStreet())) { | |
1833 | log.warn("accepting match in spite of different capitalisation", house.getStreet(), house.getSign(), | |
1834 | bestMatchingName.getRoad().getRoadDef(), "house:", house.toBrowseURL()); | |
1835 | bestMatchingName.setStreet(bestMatchingName.getRoad().getStreet()); | |
1836 | closest.setStreet(bestMatchingName.getStreet()); | |
1887 | 1837 | } |
1888 | 1838 | if (closest == bestMatchingName || bestMatchingName == null || bestMatchingName.getDistance() > MAX_DISTANCE_TO_ROAD) |
1889 | 1839 | return closest; |
1892 | 1842 | if (ratio < 0.25) |
1893 | 1843 | return closest; |
1894 | 1844 | HousenumberMatch best = closest; |
1895 | if (ratio > 0.75){ | |
1845 | if (ratio > 0.75) { | |
1896 | 1846 | // prefer the road with the matching name |
1897 | for (MapRoad r : closest.getAlternativeRoads()){ | |
1847 | for (MapRoad r : closest.getAlternativeRoads()) { | |
1898 | 1848 | if (house.getStreet().equalsIgnoreCase(r.getStreet())) |
1899 | 1849 | bestMatchingName.addAlternativeRoad(r); |
1900 | 1850 | } |
1901 | 1851 | best = bestMatchingName; |
1902 | 1852 | best.calcRoadSide(); |
1903 | 1853 | } else { |
1904 | if (log.isDebugEnabled()){ | |
1854 | if (log.isDebugEnabled()) { | |
1905 | 1855 | log.debug("further checks needed for address", closest.getStreet(), closest.getSign(), closest.toBrowseURL(), |
1906 | 1856 | formatLen(closest.getDistance()), formatLen(bestMatchingName.getDistance())); |
1907 | 1857 | } |
1910 | 1860 | return best; |
1911 | 1861 | } |
1912 | 1862 | |
1913 | } | |
1914 | ||
1915 | private static void checkSegment(HousenumberMatch house, MapRoad road, int seg){ | |
1916 | Coord cx = house.getLocation(); | |
1917 | Coord c0 = road.getPoints().get(seg); | |
1918 | Coord c1 = road.getPoints().get(seg + 1); | |
1919 | double frac = getFrac(c0, c1, cx); | |
1920 | double dist = distanceToSegment(c0,c1,cx,frac); | |
1921 | if (dist < house.getDistance()){ | |
1922 | house.setDistance(dist); | |
1923 | house.setRoad(road); | |
1924 | house.setSegment(seg); | |
1925 | house.setSegmentFrac(frac); | |
1926 | } | |
1927 | } | |
1928 | ||
1863 | private static void checkSegment(HousenumberMatch house, MapRoad road, int seg) { | |
1864 | Coord cx = house.getLocation(); | |
1865 | Coord c0 = road.getPoints().get(seg); | |
1866 | Coord c1 = road.getPoints().get(seg + 1); | |
1867 | double frac = getFrac(c0, c1, cx); | |
1868 | double dist = distanceToSegment(c0,c1,cx,frac); | |
1869 | if (dist < house.getDistance()) { | |
1870 | house.setDistance(dist); | |
1871 | house.setRoad(road); | |
1872 | house.setSegment(seg); | |
1873 | house.setSegmentFrac(frac); | |
1874 | } | |
1875 | } | |
1876 | } | |
1929 | 1877 | } |
1930 | 1878 | |
1931 | 1879 |
317 | 317 | return true; |
318 | 318 | } |
319 | 319 | |
320 | if (housesFormAGroup(house, last) == false){ | |
320 | if (!housesFormAGroup(house, last)) { | |
321 | 321 | return false; |
322 | 322 | } |
323 | 323 | if (houses.size() > 1){ |
324 | 324 | HousenumberMatch first = houses.get(0); |
325 | if (housesFormAGroup(house, first) == false){ | |
325 | if (!housesFormAGroup(house, first)) { | |
326 | 326 | HousenumberMatch preLast = houses.get(houses.size()-2); |
327 | 327 | double angle = Utils.getAngle(house.getLocation(), last.getLocation(), preLast.getLocation()); |
328 | 328 | if (Math.abs(angle) > 30) |
65 | 65 | private boolean ignoreForInterpolation; |
66 | 66 | |
67 | 67 | private boolean equalEnds; |
68 | private static final short streetTagKey = TagDict.getInstance().xlate("mkgmap:street"); | |
69 | private static final short housenumberTagKey = TagDict.getInstance().xlate("mkgmap:housenumber"); | |
70 | private static final short addrInterpolationTagKey = TagDict.getInstance().xlate("addr:interpolation"); | |
68 | private static final short TKM_STREET = TagDict.getInstance().xlate("mkgmap:street"); | |
69 | private static final short TKM_HOUSENUMBER = TagDict.getInstance().xlate("mkgmap:housenumber"); | |
70 | private static final short TK_ADDR_INTERPOLATION = TagDict.getInstance().xlate("addr:interpolation"); | |
71 | 71 | |
72 | 72 | |
73 | 73 | public HousenumberIvl(String streetName, Way interpolationWay, Node n1, Node n2) { |
78 | 78 | } |
79 | 79 | |
80 | 80 | public void setPoints(List<Coord> points) { |
81 | this.points = new ArrayList<Coord>(points); | |
81 | this.points = new ArrayList<>(points); | |
82 | 82 | } |
83 | 83 | public void setStep(int step) { |
84 | 84 | this.step = step; |
167 | 167 | MapRoad bestRoad = null; |
168 | 168 | // make sure that the closest road is one with a matching name |
169 | 169 | for (int i = 0; i < 2; i++){ |
170 | while (streetName.equals(knownHouses[i].getRoad().getStreet()) == false && knownHouses[i].hasAlternativeRoad()){ | |
170 | while (!streetName.equals(knownHouses[i].getRoad().getStreet()) && knownHouses[i].hasAlternativeRoad()) { | |
171 | 171 | HousenumberMatch testx = new HousenumberMatch(knownHouses[i]); |
172 | 172 | MapRoad r = knownHouses[i].getAlternativeRoads().remove(0); |
173 | 173 | if (streetName.equals(r.getStreet())){ |
189 | 189 | HousenumberMatch[] closest = new HousenumberMatch[2]; |
190 | 190 | boolean foundSingleRoad = false; |
191 | 191 | for (MapRoad r : toTest){ |
192 | if (streetName.equals(r.getStreet()) == false) | |
192 | if (!streetName.equals(r.getStreet())) | |
193 | 193 | continue; |
194 | 194 | foundSingleRoad = true; |
195 | 195 | for (int i = 0; i < 2; i++){ |
245 | 245 | } |
246 | 246 | } |
247 | 247 | } |
248 | if (foundSingleRoad){ | |
249 | if (r.isNamedByHousenumberProcessing() == false) | |
248 | if (foundSingleRoad) { | |
249 | if (!r.isNamedByHousenumberProcessing()) | |
250 | 250 | break; |
251 | 251 | // the closest road was originally unnamed , try to find one that is named in OSM |
252 | if (bestRoad == null){ | |
252 | if (bestRoad == null) { | |
253 | 253 | bestRoad = r; |
254 | 254 | closest[0] = test[0]; |
255 | 255 | closest[1] = test[1]; |
263 | 263 | test[1] = closest[1]; |
264 | 264 | } |
265 | 265 | if (!foundSingleRoad){ |
266 | if (streetName.equals(knownHouses[0].getRoad().getStreet()) == false || streetName.equals(knownHouses[1].getRoad().getStreet()) == false){ | |
266 | if (!streetName.equals(knownHouses[0].getRoad().getStreet()) || !streetName.equals(knownHouses[1].getRoad().getStreet())) { | |
267 | 267 | log.warn("cannot find reasonable road for both nodes",streetName,this); |
268 | 268 | return false; |
269 | 269 | } |
314 | 314 | boolean distanceWarningIssued = false; |
315 | 315 | CityInfo ci = knownHouses[0].getCityInfo(); |
316 | 316 | ZipCodeInfo zip = knownHouses[0].getZipCode(); |
317 | if (ci != null && ci.equals(knownHouses[1].getCityInfo()) == false) | |
317 | if (ci != null && !ci.equals(knownHouses[1].getCityInfo())) | |
318 | 318 | log.warn("addr:interpolation way connects houses in different cities",streetName,this,"using city",ci,"for all interpolated adresses"); |
319 | if (zip != null && zip.equals(knownHouses[1].getZipCode()) == false) | |
319 | if (zip != null && !zip.equals(knownHouses[1].getZipCode())) | |
320 | 320 | log.warn("addr:interpolation way connects houses with differnt zip codes",streetName,this,"using zip code",zip,"for all interpolated adresses"); |
321 | 321 | |
322 | for (Coord co : interpolatedPoints){ | |
322 | for (Coord co : interpolatedPoints) { | |
323 | 323 | hn += usedStep; |
324 | 324 | Node generated = new Node(interpolationWay.getId(), co); |
325 | generated.setFakeId(); | |
326 | generated.addTag(streetTagKey, streetName); | |
325 | generated.markAsGeneratedFrom(interpolationWay); | |
326 | generated.addTag(TKM_STREET, streetName); | |
327 | 327 | String number = String.valueOf(hn); |
328 | generated.addTag(housenumberTagKey, number); | |
328 | generated.addTag(TKM_HOUSENUMBER, number); | |
329 | 329 | // TODO: maybe add check that city info and zip code of both houses is equal ? |
330 | 330 | // what if not ? |
331 | 331 | HousenumberElem houseElem = new HousenumberElem(generated, ci); |
337 | 337 | if (roadForInterpolatedHouses != null){ |
338 | 338 | HousenumberGenerator.findClosestRoadSegment(house, roadForInterpolatedHouses); |
339 | 339 | if (house.getRoad() == null || house.getDistance() > MAX_INTERPOLATION_DISTANCE_TO_ROAD ){ |
340 | if (distanceWarningIssued == false){ | |
340 | if (!distanceWarningIssued){ | |
341 | 341 | log.warn("interpolated house is not close to expected road",this,house); |
342 | 342 | distanceWarningIssued = true; |
343 | 343 | } |
350 | 350 | } |
351 | 351 | |
352 | 352 | if (log.isDebugEnabled()){ |
353 | String addrInterpolationMethod = interpolationWay.getTag(addrInterpolationTagKey); | |
354 | if (hasMultipleRoads == false) | |
353 | String addrInterpolationMethod = interpolationWay.getTag(TK_ADDR_INTERPOLATION); | |
354 | if (!hasMultipleRoads) | |
355 | 355 | log.debug(this,"generated",addrInterpolationMethod,"interpolated number(s) for",knownHouses[0].getRoad()); |
356 | 356 | else |
357 | 357 | log.debug(this,"generated",addrInterpolationMethod,"interpolated number(s) for",streetName); |
495 | 495 | if (elemPos == null || elemPos >= houseElems.size()) |
496 | 496 | return false; |
497 | 497 | HousenumberElem he = houseElems.get(elemPos); |
498 | if (he instanceof HousenumberMatch == false) | |
499 | return false; | |
500 | if (he.getElement().getId() != id) | |
498 | if (!(he instanceof HousenumberMatch) || he.getElement().getId() != id) | |
501 | 499 | return false; |
502 | 500 | knownHouses[i] = (HousenumberMatch) he; |
503 | 501 | knownHouses[i].incIntervalInfoRefs(); |
546 | 544 | else { |
547 | 545 | // create a Node instance |
548 | 546 | Node toAdd = new Node(houseToAdd.getElement().getId(), houseToAdd.getLocation()); |
549 | toAdd.setFakeId(); | |
547 | toAdd.markAsGeneratedFrom(houseToAdd.getElement()); | |
550 | 548 | toAdd.copyTags(houseToAdd.element); |
551 | 549 | HousenumberElem hnElem = new HousenumberElem(toAdd, houseToAdd.getCityInfo()); |
552 | 550 | hnm = new HousenumberMatch(hnElem); |
120 | 120 | } |
121 | 121 | |
122 | 122 | public boolean hasAlternativeRoad() { |
123 | return alternativeRoads != null && alternativeRoads.isEmpty() == false; | |
123 | return alternativeRoads != null && !alternativeRoads.isEmpty(); | |
124 | 124 | } |
125 | 125 | |
126 | 126 | public boolean isIgnored() { |
309 | 309 | public boolean isEqualAddress(HousenumberElem other){ |
310 | 310 | if (getRoad() != other.getRoad()) |
311 | 311 | return false; |
312 | if (getSign().equals(other.getSign()) == false) | |
312 | if (!getSign().equals(other.getSign())) | |
313 | 313 | return false; |
314 | 314 | if (getPlace() != other.getPlace()) { |
315 | 315 | if (getPlace() == null) |
316 | 316 | return false; |
317 | if (getPlace().equals(other.getPlace()) == false) | |
317 | if (!getPlace().equals(other.getPlace())) | |
318 | 318 | return false; |
319 | 319 | } |
320 | 320 | if (getZipCode() != null && other.getZipCode() != null){ |
321 | if (getZipCode().equals(other.getZipCode()) == false) | |
321 | if (!getZipCode().equals(other.getZipCode())) | |
322 | 322 | return false; |
323 | 323 | } |
324 | 324 | if (getCityInfo() != null && other.getCityInfo() != null){ |
325 | if (getCityInfo().equals(other.getCityInfo()) == false) | |
325 | if (!getCityInfo().equals(other.getCityInfo())) | |
326 | 326 | return false; |
327 | 327 | } |
328 | 328 | return true; |
83 | 83 | filterGroups(); |
84 | 84 | if (houseNumbers.isEmpty()) |
85 | 85 | return; |
86 | List<HousenumberMatch> leftNumbers = new ArrayList<HousenumberMatch>(); | |
87 | List<HousenumberMatch> rightNumbers = new ArrayList<HousenumberMatch>(); | |
86 | List<HousenumberMatch> leftNumbers = new ArrayList<>(); | |
87 | List<HousenumberMatch> rightNumbers = new ArrayList<>(); | |
88 | 88 | |
89 | 89 | for (HousenumberMatch house : houseNumbers) { |
90 | 90 | if (house.getRoad() == null || house.isIgnored()){ |
113 | 113 | assert road.getPoints().get(0).isNumberNode(); |
114 | 114 | for (Coord p : road.getPoints()) { |
115 | 115 | // An ordinary point in the road. |
116 | if (p.isNumberNode() == false) { | |
116 | if (!p.isNumberNode()) { | |
117 | 117 | currNodePos++; |
118 | 118 | continue; |
119 | 119 | } |
176 | 176 | if (HousenumberGroup.housesFormAGroup(predHouse, house)) |
177 | 177 | group = new HousenumberGroup(this, houses.subList(j-1, j+1)); |
178 | 178 | } else { |
179 | if (group.tryAddHouse(house) == false){ | |
179 | if (!group.tryAddHouse(house)) { | |
180 | 180 | if(group.verify()) |
181 | 181 | groups.add(group); |
182 | 182 | group = null; |
193 | 193 | for (HousenumberGroup group : groups){ |
194 | 194 | int oldNumPoints = getRoad().getPoints().size(); |
195 | 195 | if (nodesAdded){ |
196 | if (group.recalcPositions() == false) | |
196 | if (!group.recalcPositions()) | |
197 | 197 | continue; |
198 | 198 | } |
199 | 199 | if (group.findSegment(streetName, groups)){ |
269 | 269 | for (int i = 1; i < houseNumbers.size(); i++){ |
270 | 270 | HousenumberMatch house1 = houseNumbers.get(i - 1); |
271 | 271 | HousenumberMatch house2 = houseNumbers.get(i); |
272 | if (house1.getSign().equals(house2.getSign()) == false){ | |
272 | if (!house1.getSign().equals(house2.getSign())) { | |
273 | 273 | usedForCalc = null; |
274 | 274 | } else { |
275 | if (house1.isEqualAddress(house2) == false) | |
275 | if (!house1.isEqualAddress(house2)) | |
276 | 276 | continue; |
277 | 277 | // found a duplicate address (e.g. 2 and 2 or 1b and 1b in same road,city etc.) |
278 | 278 | double distBetweenHouses = house2.getLocation().distance(house1.getLocation()); |
439 | 439 | else { |
440 | 440 | if (used == null) |
441 | 441 | used = prev; |
442 | if (prev.getSign().equals(house.getSign()) && prev.isEqualAddress(house) == false){ | |
442 | if (prev.getSign().equals(house.getSign()) && !prev.isEqualAddress(house)) { | |
443 | 443 | // we want to keep these duplicates |
444 | 444 | } else { |
445 | 445 | house.setIgnored(true); |
461 | 461 | for (ExtNumbers en1 = head1; en1 != null; en1 = en1.next){ |
462 | 462 | if (changed) |
463 | 463 | break; |
464 | if (en1.hasNumbers() == false) | |
464 | if (!en1.hasNumbers()) | |
465 | 465 | continue; |
466 | 466 | ExtNumbers head2 = other.extNumbersHead; |
467 | 467 | for (ExtNumbers en2 = head2; en2 != null; en2 = en2.next){ |
468 | 468 | if (changed) |
469 | 469 | break; |
470 | if (en2.hasNumbers() == false) | |
470 | if (!en2.hasNumbers()) | |
471 | 471 | continue; |
472 | 472 | int res = ExtNumbers.checkIntervals(streetName, en1, en2); |
473 | 473 | switch (res) { |
603 | 603 | } |
604 | 604 | |
605 | 605 | public void setRandom(boolean isRandom) { |
606 | if (this.isRandom == false) | |
606 | if (!this.isRandom) | |
607 | 607 | if (log.isDebugEnabled()) |
608 | 608 | log.debug("detected random case",this); |
609 | 609 | this.isRandom = isRandom; |
628 | 628 | setChanged(true); |
629 | 629 | else { |
630 | 630 | ExtNumbers test = en.hasNumbers() ? en : en.next; |
631 | if (test.getNumbers().isSimilar(curr.getNumbers()) == false) | |
631 | if (!test.getNumbers().isSimilar(curr.getNumbers())) | |
632 | 632 | setChanged(true); |
633 | 633 | } |
634 | 634 | if (curr.prev == null) |
660 | 660 | List<HousenumberMatch> wrongHouses = Collections.emptyList(); |
661 | 661 | double minDist = Double.MAX_VALUE; |
662 | 662 | double maxDist = 0; |
663 | if (houseNumbers.isEmpty() == false){ | |
663 | if (!houseNumbers.isEmpty()) { | |
664 | 664 | HashMap<String, Integer>possibleStreetNamesFromHouses = new HashMap<>(); |
665 | 665 | HashMap<String, Integer>possiblePlaceNamesFromHouses = new HashMap<>(); |
666 | 666 | for (HousenumberMatch house : houseNumbers){ |
59 | 59 | |
60 | 60 | /** |
61 | 61 | * Class to extract elevation information from SRTM files in hgt format. |
62 | * @param path a comma separated list of directories which may contain *.hgt files. | |
62 | * @param paths a comma separated list of directories which may contain *.hgt files. | |
63 | 63 | * @param bbox the bounding box of the tile for which the DEM information is needed. |
64 | 64 | * @param demPolygonMapUnits optional bounding polygon which describes the area for |
65 | 65 | * which elevation should be read from hgt files. |
66 | 66 | * @param interpolationMethod |
67 | 67 | */ |
68 | public HGTConverter(String path, Area bbox, java.awt.geom.Area demPolygonMapUnits, double extra) { | |
68 | public HGTConverter(String paths, Area bbox, java.awt.geom.Area demPolygonMapUnits, double extra) { | |
69 | 69 | // make bigger box for interpolation or aligning of areas |
70 | 70 | int minLat = (int) Math.floor(Utils.toDegrees(bbox.getMinLat()) - extra); |
71 | 71 | int minLon = (int) Math.floor(Utils.toDegrees(bbox.getMinLong()) - extra); |
92 | 92 | Area rdrBbox = new Area(lat, lon, lat+1.0, lon+1.0); |
93 | 93 | int testMode = intersectsPoly(rdrBbox); |
94 | 94 | if (testMode != 0) { |
95 | HGTReader rdr = new HGTReader(lat, lon, path); | |
95 | HGTReader rdr = new HGTReader(lat, lon, paths); | |
96 | 96 | readers[row][col] = rdr; |
97 | 97 | maxRes = Math.max(maxRes, rdr.getRes()); |
98 | 98 | } |
56 | 56 | * Class to read a single HGT file. |
57 | 57 | * @param lat in degrees, -90 .. 90 |
58 | 58 | * @param lon - -180..180 |
59 | * @param dirsWithHGT comma separated list of directories to search for *.hgt files | |
59 | * @param dirsWithHGT string with comma separated list of directories to search for *.hgt files | |
60 | 60 | * Supported are also zip files containing *.hgt files and directories containing *.hgt.zip. |
61 | 61 | */ |
62 | 62 | public HGTReader(int lat, int lon, String dirsWithHGT) { |
122 | 122 | for (CoastlineWay w : coastlines) { |
123 | 123 | if (w.getBbox().intersects(bbox)) { |
124 | 124 | Way x = new Way(w.getOriginalId(), w.getPoints()); |
125 | x.setFakeId(); | |
125 | x.markAsGeneratedFrom(w); | |
126 | 126 | x.addTag("natural", "coastline"); |
127 | 127 | ways.add(x); |
128 | 128 | } |
172 | 172 | } |
173 | 173 | |
174 | 174 | @Override |
175 | protected void removeAllTags() { | |
175 | public void removeAllTags() { | |
176 | 176 | } |
177 | 177 | |
178 | 178 | @Override |
27 | 27 | public abstract class Element { |
28 | 28 | private static final Logger log = Logger.getLogger(Element.class); |
29 | 29 | |
30 | private static final byte TYPE_NODE = 1; | |
31 | private static final byte TYPE_WAY = 2; | |
32 | private static final byte TYPE_RELATION = 3; | |
33 | ||
30 | 34 | private Tags tags; |
31 | 35 | private long id; |
32 | 36 | private long originalId; |
37 | private byte origType; | |
33 | 38 | |
34 | 39 | /** |
35 | 40 | * returns a copy of the tags or a new instance if the element has no tags |
214 | 219 | originalId = id; |
215 | 220 | } |
216 | 221 | |
217 | public void setFakeId() { | |
222 | /** | |
223 | * Mark this element as generated from another element. | |
224 | * @param orig the original element (used to extract the type) | |
225 | */ | |
226 | public void markAsGeneratedFrom(Element orig) { | |
218 | 227 | id = FakeIdGenerator.makeFakeId(); |
219 | } | |
220 | ||
228 | origType = elementToType(orig); | |
229 | } | |
230 | ||
231 | private static byte elementToType(Element orig) { | |
232 | if (orig instanceof Node) | |
233 | return TYPE_NODE; | |
234 | if (orig instanceof Way) | |
235 | return TYPE_WAY; | |
236 | if (orig instanceof Relation) | |
237 | return TYPE_RELATION; | |
238 | throw new IllegalArgumentException("invalid type"); | |
239 | } | |
240 | ||
241 | public String getOrigElement() { | |
242 | switch (origType) { | |
243 | case TYPE_NODE: | |
244 | return "node"; | |
245 | case TYPE_WAY: | |
246 | return "way"; | |
247 | case TYPE_RELATION: | |
248 | return "relation"; | |
249 | default: | |
250 | throw new IllegalArgumentException("invalid type"); | |
251 | } | |
252 | } | |
253 | ||
221 | 254 | public String toTagString() { |
222 | 255 | if (tags == null) |
223 | 256 | return "[]"; |
241 | 274 | protected void copyIds(Element other) { |
242 | 275 | id = other.id; |
243 | 276 | originalId = other.originalId; |
277 | origType = other.origType; | |
244 | 278 | } |
245 | 279 | |
246 | 280 | public String getName() { |
254 | 288 | return tags.getTagsWithPrefix(prefix, removePrefix); |
255 | 289 | } |
256 | 290 | |
257 | protected void removeAllTags() { | |
291 | public void removeAllTags() { | |
258 | 292 | tags = null; |
259 | 293 | } |
260 | 294 | |
299 | 333 | name += " "; |
300 | 334 | return name + "(OSM id " + getId() + ")"; |
301 | 335 | } |
336 | ||
337 | /** | |
338 | * Calculate a short string to be used in log messages or style functions | |
339 | * echo or echotags. | |
340 | * | |
341 | * @return string containing the element type (Node/Way/Relation followed by | |
342 | * either the id or - if the id is a fake id - the string "generated | |
343 | * from " followed by the id of the source element that was used to | |
344 | * generate this element. | |
345 | */ | |
346 | public String getBasicLogInformation() { | |
347 | String className = getClass().getSimpleName(); | |
348 | return ("GeneralRelation".equals(className)) ? "Relation" : className | |
349 | + (FakeIdGenerator.isFakeId(getId()) ? " generated from " + getOrigElement(): "") + " " + originalId; | |
350 | } | |
302 | 351 | } |
68 | 68 | private final String[] deadEndArgs; |
69 | 69 | |
70 | 70 | /** name of the tag that contains a ;-separated list of tag names that should be removed after all elements have been processed */ |
71 | public static final short MKGMAP_REMOVE_TAG_KEY = TagDict.getInstance().xlate("mkgmap:removetags"); | |
71 | public static final short TKM_REMOVETAGS = TagDict.getInstance().xlate("mkgmap:removetags"); | |
72 | 72 | |
73 | 73 | public ElementSaver(EnhancedProperties args) { |
74 | 74 | if (args.getProperty("preserve-element-order", false)) { |
362 | 362 | public void deferRelation(long id, Relation parentRel, String role) { |
363 | 363 | deferredRelationMap.add(id, new AbstractMap.SimpleEntry<>(role, parentRel)); |
364 | 364 | } |
365 | ||
366 | /** | |
367 | * Return the node object associated with the given id or create one. | |
368 | * This is called for the node members of a relation. We always want a node for them, not just a coord. | |
369 | * @param id the node id | |
370 | * @return the existing node or a newly created one (without tags) or null if no coord is associated with the id | |
371 | */ | |
372 | public Node getOrCreateNode(long id) { | |
373 | Node node = nodeMap.get(id); | |
374 | if (node == null) { | |
375 | // we didn't make a node for this point earlier, | |
376 | // do it now (if it exists) | |
377 | Coord co = getCoord(id); | |
378 | if (co != null) { | |
379 | node = new Node(id, co); | |
380 | addNode(node); | |
381 | } | |
382 | } | |
383 | return node; | |
384 | } | |
365 | 385 | } |
56 | 56 | // actions will always be executed |
57 | 57 | private boolean propogateActionsOnContinue; |
58 | 58 | |
59 | @SuppressWarnings("incomplete-switch") | |
59 | 60 | public static boolean checkType(FeatureKind featureKind, int type) { |
60 | 61 | if (type >= 0x010000) { |
61 | 62 | if ((type & 0xff) > 0x1f) |
62 | 63 | return false; |
63 | 64 | } else { |
64 | if (featureKind == FeatureKind.POLYLINE && type > 0x3f | |
65 | || (featureKind == FeatureKind.POLYGON && (type > 0x7f || type == 0x4a))) { | |
66 | return false; | |
67 | } else if (featureKind == FeatureKind.POINT) { | |
65 | switch (featureKind) { | |
66 | case POLYLINE: | |
67 | if (type > 0x3f) | |
68 | return false; | |
69 | break; | |
70 | case POLYGON: | |
71 | if (type > 0x7f || type == 0x4a) | |
72 | return false; | |
73 | break; | |
74 | case POINT: | |
68 | 75 | if (type < 0x0100) |
69 | 76 | return false; |
70 | 77 | int subtype = type & 0xff; |
71 | if (subtype > 0x1f || MapPoint.isCityType(type) && subtype != 0) { | |
72 | return false; | |
78 | if (MapPoint.isCityType(type)) { | |
79 | if (subtype != 0) | |
80 | return false; | |
81 | } else if (type >= 0x1600 && type < 0x1e00 || // Andrzej Popowski says. | |
82 | type >= 0x2a00 && type < 0x3100 || // These may be indexed and this | |
83 | type >= 0x6400 && type < 0x6700) { // confines subtype to 5 bits | |
84 | if (subtype > 0x1f) | |
85 | return false; | |
86 | } else { | |
87 | if (subtype > 0x3f) | |
88 | return false; | |
73 | 89 | } |
90 | break; | |
74 | 91 | } |
75 | 92 | } |
76 | 93 | return true; |
242 | 259 | * known to cause routing errors if used for non-routable lines. |
243 | 260 | */ |
244 | 261 | public static boolean isSpecialRoutableLineType(int type){ |
245 | return type >= 0x01 && type <= 0x13 || type == 0x16 || type == 0x1b; | |
262 | return type >= 0x01 && type <= 0x13 || type == 0x16 || type == 0x1a || type == 0x1b; | |
246 | 263 | } |
247 | 264 | |
248 | 265 | /** |
14 | 14 | |
15 | 15 | import java.util.ArrayList; |
16 | 16 | import java.util.Arrays; |
17 | import java.util.Collections; | |
17 | 18 | import java.util.HashSet; |
18 | 19 | import java.util.List; |
19 | 20 | import java.util.Set; |
20 | 21 | |
22 | import uk.me.parabola.imgfmt.app.Area; | |
21 | 23 | import uk.me.parabola.imgfmt.app.Coord; |
22 | 24 | import uk.me.parabola.imgfmt.app.Exit; |
23 | 25 | import uk.me.parabola.log.Logger; |
26 | import uk.me.parabola.util.ElementQuadTree; | |
24 | 27 | import uk.me.parabola.util.EnhancedProperties; |
25 | 28 | |
26 | 29 | /** |
32 | 35 | public class HighwayHooks implements OsmReadingHooks { |
33 | 36 | private static final Logger log = Logger.getLogger(HighwayHooks.class); |
34 | 37 | |
35 | private final List<Way> motorways = new ArrayList<>(); | |
36 | private final List<Node> exits = new ArrayList<>(); | |
38 | private final List<Element> motorways = new ArrayList<>(); // will all be Ways | |
39 | private final List<Element> exits = new ArrayList<>(); | |
37 | 40 | |
38 | 41 | private boolean makeOppositeCycleways; |
39 | 42 | private ElementSaver saver; |
42 | 45 | private Node currentNodeInWay; |
43 | 46 | |
44 | 47 | @Override |
45 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
48 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
46 | 49 | this.saver = saver; |
47 | 50 | if(props.getProperty("make-all-cycleways", false)) { |
48 | 51 | log.error("option make-all-cycleways is deprecated, please use make-opposite-cycleways"); |
60 | 63 | |
61 | 64 | @Override |
62 | 65 | public Set<String> getUsedTags() { |
63 | Set<String> usedTags = new HashSet<>(Arrays.asList("highway", "access", "barrier", "FIXME", "fixme", | |
64 | "route", "oneway", "junction", "name", Exit.TAG_ROAD_REF, "ref")); | |
66 | Set<String> usedTags = new HashSet<>(Arrays.asList("highway", "access", "barrier", "oneway", "junction", "name", | |
67 | Exit.TAG_ROAD_REF, "ref", "motorroad")); | |
65 | 68 | if (makeOppositeCycleways) { |
66 | 69 | // need the additional tags |
67 | 70 | usedTags.add("cycleway"); |
76 | 79 | |
77 | 80 | @Override |
78 | 81 | public void onAddNode(Node node) { |
79 | String val = node.getTag("highway"); | |
80 | if (val != null && ("motorway_junction".equals(val) || "services".equals(val))) { | |
82 | String highway = node.getTag("highway"); | |
83 | if (highway != null && ("motorway_junction".equals(highway) || "services".equals(highway) || "rest_area".equals(highway))) { | |
81 | 84 | exits.add(node); |
82 | 85 | node.addTag("mkgmap:osmid", String.valueOf(node.getId())); |
83 | 86 | } |
84 | 87 | } |
85 | 88 | |
86 | 89 | @Override |
87 | public void onCoordAddedToWay(Way way, long id, Coord co) { | |
90 | public void onNodeAddedToWay(Way way, long id) { | |
88 | 91 | if (!linkPOIsToWays) |
89 | 92 | return; |
90 | 93 | |
91 | 94 | currentNodeInWay = saver.getNode(id); |
95 | if (currentNodeInWay == null) | |
96 | return; | |
97 | Coord co = currentNodeInWay.getLocation(); | |
98 | ||
92 | 99 | // if this Coord is also a POI, replace it with an |
93 | 100 | // equivalent CoordPOI that contains a reference to |
94 | 101 | // the POI's Node so we can access the POI's tags |
95 | if (!(co instanceof CoordPOI) && currentNodeInWay != null) { | |
102 | if (!(co instanceof CoordPOI)) { | |
96 | 103 | // for now, only do this for nodes that have |
97 | 104 | // certain tags otherwise we will end up creating |
98 | 105 | // a CoordPOI for every node in the way |
136 | 143 | @Override |
137 | 144 | public void onAddWay(Way way) { |
138 | 145 | String highway = way.getTag("highway"); |
139 | if (highway != null || "ferry".equals(way.getTag("route"))) { | |
146 | if (highway != null) { | |
140 | 147 | // if the way is a roundabout but isn't already |
141 | 148 | // flagged as "oneway", flag it here |
142 | 149 | if ("roundabout".equals(way.getTag("junction")) && way.getTag("oneway") == null) { |
165 | 172 | } |
166 | 173 | } |
167 | 174 | |
168 | if("motorway".equals(highway) || "trunk".equals(highway)) | |
175 | if ("motorway".equals(highway) || "trunk".equals(highway) || "primary".equals(highway) || way.tagIsLikeYes("motorroad")) | |
169 | 176 | motorways.add(way); |
177 | else if (linkPOIsToWays && ("services".equals(highway) || "rest_area".equals(highway))) { | |
178 | exits.add(way); | |
179 | way.addTag("mkgmap:osmid", String.valueOf(way.getId())); | |
180 | } | |
170 | 181 | } |
171 | 182 | |
172 | 183 | @Override |
176 | 187 | motorways.clear(); |
177 | 188 | } |
178 | 189 | |
190 | private static final int XTRA = 150; // very approx 300m | |
179 | 191 | private void finishExits() { |
180 | for (Node e : exits) { | |
192 | if (exits.isEmpty() || motorways.isEmpty()) | |
193 | return; | |
194 | ElementQuadTree majorRoads = new ElementQuadTree(saver.getBoundingBox(), motorways); | |
195 | for (Element e : exits) { | |
181 | 196 | String refTag = Exit.TAG_ROAD_REF; |
182 | 197 | if (e.getTag(refTag) == null) { |
183 | 198 | String exitName = e.getTag("name"); |
186 | 201 | |
187 | 202 | String ref = null; |
188 | 203 | Way motorway = null; |
189 | for (Way w : motorways) { | |
190 | // uses an implicit call of Coord.equals() | |
191 | if (w.getPoints().contains(e.getLocation())) { | |
192 | motorway = w; | |
193 | ref = w.getTag("ref"); | |
194 | if(ref != null) | |
195 | break; | |
204 | Area bBox; | |
205 | if (e instanceof Node) | |
206 | bBox = Area.getBBox(Collections.singletonList(((Node) e).getLocation())); | |
207 | else | |
208 | bBox = Area.getBBox(((Way) e).getPoints()); | |
209 | String highway = e.getTag("highway"); | |
210 | final boolean isServices = "services".equals(highway) || "rest_area".equals(highway); | |
211 | if (isServices) // services will be just off the road, so increase size | |
212 | bBox = new Area(bBox.getMinLat() - XTRA, bBox.getMinLong() - XTRA, bBox.getMaxLat() + XTRA, bBox.getMaxLong() + XTRA); | |
213 | List<Way> possibleRoads = new ArrayList<>(); | |
214 | for (Element w : majorRoads.get(bBox)) { | |
215 | motorway = (Way) w; | |
216 | ref = motorway.getTag("ref"); | |
217 | if (ref != null) { | |
218 | if (isServices) { | |
219 | possibleRoads.add(motorway); // save all possibilities | |
220 | } else { // probably on 2+ roads, save possibilities to find the more major road (doesn't have to be motorway) | |
221 | if (!(e instanceof Node)) | |
222 | log.warn("Motorway exit", exitName, "expected to be a Node", e); | |
223 | else if (motorway.getPoints().contains(((Node) e).getLocation())) | |
224 | possibleRoads.add(motorway); | |
225 | } | |
196 | 226 | } |
197 | 227 | } |
198 | 228 | |
229 | if (possibleRoads.size() > 1) { | |
230 | if (isServices) { // pick the closest road | |
231 | Coord serviceCoord; | |
232 | if (e instanceof Node) | |
233 | serviceCoord = ((Node) e).getLocation(); | |
234 | else | |
235 | // Simple-minded logic to see if a Way that probably defines [part of] a | |
236 | // services area, hence might become a POI if option --link-pois-to-ways, | |
237 | // is near the specified road. | |
238 | // Just pick an arbitary Coord on the services boundary and find the nearest | |
239 | // any Coord in the roads. | |
240 | // No need to check if it is near the line between far-apart Coords because | |
241 | // there also needs to be a junction so that can get off the road to the | |
242 | // services. | |
243 | serviceCoord = ((Way)e).getFirstPoint(); | |
244 | long closestRoad = Long.MAX_VALUE; | |
245 | for (Way road : possibleRoads) { | |
246 | long closestCoord = Long.MAX_VALUE; | |
247 | for (Coord pointOnRoad : road.getPoints()) { | |
248 | long dist = pointOnRoad.distanceInHighPrecSquared(serviceCoord); | |
249 | if (dist < closestCoord) | |
250 | closestCoord = dist; | |
251 | } | |
252 | if (closestCoord < closestRoad) { | |
253 | closestRoad = closestCoord; | |
254 | motorway = road; | |
255 | ref = motorway.getTag("ref"); | |
256 | } | |
257 | } | |
258 | } else { // pick the most major road | |
259 | int bestRoad = Integer.MAX_VALUE; | |
260 | for (Way road : possibleRoads) { | |
261 | String roadType = road.getTag("highway"); | |
262 | int thisRoad = 4; | |
263 | if ("motorway".equals(roadType)) | |
264 | thisRoad = 0; | |
265 | else if (road.tagIsLikeYes("motorroad")) | |
266 | thisRoad = 1; | |
267 | else if ("trunk".equals(roadType)) | |
268 | thisRoad = 2; | |
269 | else if ("primary".equals(roadType)) | |
270 | thisRoad = 3; | |
271 | if (thisRoad < bestRoad) { | |
272 | bestRoad = thisRoad; | |
273 | motorway = road; | |
274 | ref = motorway.getTag("ref"); | |
275 | } | |
276 | } | |
277 | } | |
278 | //log.info("Exit", exit, possibleRoads.size(), "options, chosen:", motorway, ref); | |
279 | } // else 0 or 1 road; ref/motorway null or set correctly | |
199 | 280 | if (ref != null) { |
200 | 281 | log.info("Adding", refTag + "=" + ref, "to exit", exitName); |
201 | 282 | e.addTag(refTag, ref); |
202 | 283 | } else if(motorway != null) { |
203 | log.warn("Motorway exit", exitName, "is positioned on a motorway that doesn't have a 'ref' tag (" + e.getLocation().toOSMURL() + ")"); | |
284 | log.warn("Motorway exit", exitName, "is positioned on a motorway that doesn't have a 'ref' tag", e); | |
204 | 285 | } |
205 | 286 | } |
206 | 287 | } |
13 | 13 | package uk.me.parabola.mkgmap.reader.osm; |
14 | 14 | |
15 | 15 | import java.util.ArrayList; |
16 | import java.util.Arrays; | |
16 | 17 | import java.util.HashSet; |
17 | 18 | import java.util.List; |
18 | 19 | import java.util.Set; |
20 | import java.util.stream.Collectors; | |
19 | 21 | |
20 | import uk.me.parabola.imgfmt.app.Coord; | |
21 | 22 | import uk.me.parabola.log.Logger; |
22 | 23 | import uk.me.parabola.util.EnhancedProperties; |
23 | 24 | |
31 | 32 | |
32 | 33 | private ElementSaver saver; |
33 | 34 | private final List<Node> nodes = new ArrayList<>(); |
34 | private boolean clearNodes; | |
35 | 35 | |
36 | private static final short addrHousenumberTagKey = TagDict.getInstance().xlate("addr:housenumber"); | |
37 | private static final short addrInterpolationTagKey = TagDict.getInstance().xlate("addr:interpolation"); | |
36 | private static final short TK_ADDR_HOUSENUMBER = TagDict.getInstance().xlate("addr:housenumber"); | |
37 | private static final short TK_ADDR_INTERPOLATION = TagDict.getInstance().xlate("addr:interpolation"); | |
38 | 38 | |
39 | public static final short partOfInterpolationTagKey = TagDict.getInstance().xlate("mkgmap:part-of-interpolation"); | |
40 | public static final short mkgmapNodeIdsTagKey = TagDict.getInstance().xlate("mkgmap:node-ids"); | |
39 | public static final short TKM_PART_OF_INTERPOLATION = TagDict.getInstance().xlate("mkgmap:part-of-interpolation"); | |
40 | public static final short TKM_NODE_IDS = TagDict.getInstance().xlate("mkgmap:node-ids"); | |
41 | 41 | @Override |
42 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
42 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
43 | 43 | this.saver = saver; |
44 | 44 | if (!props.getProperty("addr-interpolation", true)) |
45 | 45 | return false; |
48 | 48 | |
49 | 49 | @Override |
50 | 50 | public Set<String> getUsedTags() { |
51 | HashSet<String> usedTags = new HashSet<>(); | |
52 | usedTags.add("addr:street"); | |
53 | usedTags.add("addr:housenumber"); | |
54 | usedTags.add("addr:interpolation"); | |
55 | usedTags.add("addr:place"); | |
56 | return usedTags; | |
51 | return new HashSet<>(Arrays.asList("addr:street", "addr:housenumber", "addr:interpolation", "addr:place")); | |
57 | 52 | } |
58 | 53 | |
59 | 54 | @Override |
60 | public void onCoordAddedToWay(Way way, long id, Coord co) { | |
61 | if (clearNodes){ | |
62 | nodes.clear(); | |
63 | clearNodes = false; | |
55 | public void onNodeAddedToWay(Way way, long id) { | |
56 | Node currentNodeInWay = saver.getNode(id); | |
57 | if (currentNodeInWay != null && currentNodeInWay.getTag(TK_ADDR_HOUSENUMBER) != null) { | |
58 | // this node might be part of a way that has the addr:interpolation tag | |
59 | nodes.add(currentNodeInWay); | |
64 | 60 | } |
65 | Node currentNodeInWay = saver.getNode(id); | |
66 | if (currentNodeInWay == null || currentNodeInWay.getTag(addrHousenumberTagKey) == null) | |
67 | return; | |
68 | // this node might be part of a way that has the addr:interpolation tag | |
69 | nodes.add(currentNodeInWay); | |
70 | 61 | } |
71 | 62 | |
72 | 63 | @Override |
73 | 64 | public void onAddWay(Way way) { |
74 | clearNodes = true; // make sure that the list is cleared with the next coord | |
75 | String ai = way.getTag(addrInterpolationTagKey); | |
76 | if (ai == null) | |
77 | return; | |
78 | if (nodes.size() < 2){ | |
79 | log.warn(way.toBrowseURL(),"tag addr:interpolation="+ai, "is ignored, found less than two valid nodes."); | |
80 | return; | |
65 | try { | |
66 | String ai = way.getTag(TK_ADDR_INTERPOLATION); | |
67 | if (ai == null) | |
68 | return; | |
69 | if (nodes.size() < 2) { | |
70 | log.warn(way.toBrowseURL(), "tag addr:interpolation=" + ai, "is ignored, found less than two valid nodes."); | |
71 | return; | |
72 | } | |
73 | switch (ai) { | |
74 | case "odd": | |
75 | case "even": | |
76 | case "all": | |
77 | case "1": | |
78 | case "2": | |
79 | break; | |
80 | default: | |
81 | if (log.isInfoEnabled()) | |
82 | log.warn(way.toBrowseURL(), "tag addr:interpolation=" + ai, "is ignored"); | |
83 | return; | |
84 | } | |
85 | ||
86 | nodes.forEach(n -> n.addTag(TKM_PART_OF_INTERPOLATION, "1")); | |
87 | way.addTag(TKM_NODE_IDS, nodes.stream().map(n -> Long.toString(n.getId())).collect(Collectors.joining(","))); | |
88 | } finally { | |
89 | // always clear, else we would use nodes for the wrong way | |
90 | nodes.clear(); | |
81 | 91 | } |
82 | switch (ai) { | |
83 | case "odd": | |
84 | case "even": | |
85 | case "all": | |
86 | case "1": | |
87 | case "2": | |
88 | break; | |
89 | default: | |
90 | if (log.isInfoEnabled()) | |
91 | log.warn(way.toBrowseURL(),"tag addr:interpolation="+ai, "is ignored"); | |
92 | return; | |
93 | } | |
94 | ||
95 | StringBuilder sb = new StringBuilder(); | |
96 | int num = nodes.size(); | |
97 | for (int i = 0; i < num; i++) { | |
98 | Node n = nodes.get(i); | |
99 | String id = String.valueOf(n.getId()); | |
100 | sb.append(id); | |
101 | if (i + 1 < num) | |
102 | sb.append(","); | |
103 | n.addTag(partOfInterpolationTagKey, "1"); | |
104 | } | |
105 | way.addTag(mkgmapNodeIdsTagKey, sb.toString()); | |
106 | } | |
107 | ||
108 | @Override | |
109 | public void end() { | |
110 | nodes.clear(); | |
111 | 92 | } |
112 | 93 | } |
63 | 63 | private boolean processDestinations; |
64 | 64 | private boolean processExits; |
65 | 65 | |
66 | private static final short TAG_KEY_HIGHWAY = TagDict.getInstance().xlate("highway"); | |
67 | private static final short TAG_KEY_ONEWAY = TagDict.getInstance().xlate("oneway"); | |
68 | private static final short TAG_KEY_EXIT_TO = TagDict.getInstance().xlate("exit_to"); | |
69 | private static final short TAG_KEY_DEST_HINT_WORK = TagDict.getInstance().xlate("mkgmap:dest_hint_work"); | |
66 | private static final short TK_HIGHWAY = TagDict.getInstance().xlate("highway"); | |
67 | private static final short TK_ONEWAY = TagDict.getInstance().xlate("oneway"); | |
68 | private static final short TK_EXIT_TO = TagDict.getInstance().xlate("exit_to"); | |
69 | private static final short TKM_DEST_HINT_WORK = TagDict.getInstance().xlate("mkgmap:dest_hint_work"); | |
70 | 70 | |
71 | 71 | @Override |
72 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
72 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
73 | 73 | this.saver = saver; |
74 | 74 | nameFinder = new NameFinder(props); |
75 | 75 | processDestinations = props.containsKey("process-destination"); |
87 | 87 | // ignore one-node or zero-node ways |
88 | 88 | continue; |
89 | 89 | } |
90 | String highwayTag = w.getTag(TAG_KEY_HIGHWAY); | |
90 | String highwayTag = w.getTag(TK_HIGHWAY); | |
91 | 91 | if (highwayTag != null && highwayTypes.contains(highwayTag)) { |
92 | 92 | processHighWay(w, highwayTag); |
93 | 93 | } |
169 | 169 | } |
170 | 170 | |
171 | 171 | if (destHint != null) { |
172 | w.addTag(TAG_KEY_DEST_HINT_WORK, destHint); | |
172 | w.addTag(TKM_DEST_HINT_WORK, destHint); | |
173 | 173 | destinationLinkWays.add(w); |
174 | 174 | |
175 | 175 | if (log.isDebugEnabled() && !standardTagKey.equals(destSourceTagKey)) { |
266 | 266 | if (dist <= maxLength) { |
267 | 267 | // create a new way with the first two points and identical tags |
268 | 268 | Way precedingWay = new Way(w.getOriginalId(), w.getPoints().subList(0, 1 + 1)); |
269 | precedingWay.setFakeId(); | |
269 | precedingWay.markAsGeneratedFrom(w); | |
270 | 270 | precedingWay.copyTags(w); |
271 | 271 | |
272 | 272 | saver.addWay(precedingWay); |
304 | 304 | cConnection = alternative; |
305 | 305 | // create the new way with identical tags |
306 | 306 | w.getPoints().add(i, cConnection); |
307 | Way precedingWay = new Way(w.getOriginalId(), new ArrayList<Coord>(w.getPoints().subList(0, i + 1))); | |
308 | precedingWay.setFakeId(); | |
307 | Way precedingWay = new Way(w.getOriginalId(), new ArrayList<>(w.getPoints().subList(0, i + 1))); | |
308 | precedingWay.markAsGeneratedFrom(w); | |
309 | 309 | precedingWay.copyTags(w); |
310 | 310 | |
311 | 311 | saver.addWay(precedingWay); |
334 | 334 | * @return <code>true</code> if the node is a usable exit, else <code>false</code> |
335 | 335 | */ |
336 | 336 | private boolean isTaggedAsExit(Node node) { |
337 | return "motorway_junction".equals(node.getTag(TAG_KEY_HIGHWAY)) | |
338 | && (node.getTag("ref") != null || (nameFinder.getName(node) != null) || node.getTag(TAG_KEY_EXIT_TO) != null); | |
337 | return "motorway_junction".equals(node.getTag(TK_HIGHWAY)) | |
338 | && (node.getTag("ref") != null || (nameFinder.getName(node) != null) || node.getTag(TK_EXIT_TO) != null); | |
339 | 339 | } |
340 | 340 | |
341 | 341 | /** |
373 | 373 | log.debug(destinationLinkWays.size(),"links with destination tag"); |
374 | 374 | while (!linksWithDestination.isEmpty()) { |
375 | 375 | Way linkWay = linksWithDestination.poll(); |
376 | String destination = linkWay.getTag(TAG_KEY_DEST_HINT_WORK); | |
376 | String destination = linkWay.getTag(TKM_DEST_HINT_WORK); | |
377 | 377 | if (log.isDebugEnabled()) |
378 | 378 | log.debug("Check way", linkWay.getId(), linkWay.toTagString()); |
379 | 379 | |
383 | 383 | Set<Way> nextWays = adjacentWays.get(c); |
384 | 384 | if (nextWays != null) { |
385 | 385 | for (Way connectedWay : nextWays) { |
386 | String nextDest = connectedWay.getTag(TAG_KEY_DEST_HINT_WORK); | |
386 | String nextDest = connectedWay.getTag(TKM_DEST_HINT_WORK); | |
387 | 387 | |
388 | 388 | if (log.isDebugEnabled()) |
389 | 389 | log.debug("Followed by",connectedWay.getId(),connectedWay.toTagString()); |
395 | 395 | |
396 | 396 | boolean startEndConnection = c == c2; |
397 | 397 | if (startEndConnection && !connectedWay.equals(linkWay) |
398 | && connectedWay.getTag(TAG_KEY_HIGHWAY).endsWith("_link") | |
398 | && connectedWay.getTag(TK_HIGHWAY).endsWith("_link") | |
399 | 399 | && destination.equals(nextDest)) { |
400 | 400 | // do not use this way because there is another link before that with the same destination |
401 | 401 | boolean removed = destinationLinkWays.remove(connectedWay); |
435 | 435 | log.debug("Exit node", exitNode, "has no connected ways. Skip it."); |
436 | 436 | return; |
437 | 437 | } |
438 | String exitTo = exitNode.getTag(TAG_KEY_EXIT_TO); | |
438 | String exitTo = exitNode.getTag(TK_EXIT_TO); | |
439 | 439 | if (exitTo != null) { |
440 | 440 | int countMatches = 0; |
441 | 441 | int preferred = Integer.MAX_VALUE; |
442 | 442 | for (Way w : exitWays) { |
443 | String hw = w.getTag(TAG_KEY_HIGHWAY); | |
443 | String hw = w.getTag(TK_HIGHWAY); | |
444 | 444 | int pos = hwSorted.indexOf(hw); |
445 | 445 | if (pos < preferred) { |
446 | 446 | preferred = pos; |
463 | 463 | } |
464 | 464 | |
465 | 465 | private void processExitWay(Node exitNode, Way w, String exitTo) { |
466 | String highwayLinkTag = w.getTag(TAG_KEY_HIGHWAY); | |
466 | String highwayLinkTag = w.getTag(TK_HIGHWAY); | |
467 | 467 | if (highwayLinkTag.endsWith("_link")) { |
468 | 468 | log.debug("Try to cut", highwayLinkTag, w, "into three parts for giving hint to exit", exitNode); |
469 | 469 | Way hintWay = splitWay(w, "exit"); |
487 | 487 | |
488 | 488 | private String fixDestHint(Way hintWay) { |
489 | 489 | if (processDestinations) { |
490 | String hint = hintWay.getTag(TAG_KEY_DEST_HINT_WORK); | |
490 | String hint = hintWay.getTag(TKM_DEST_HINT_WORK); | |
491 | 491 | if (hint != null) { |
492 | hintWay.deleteTag(TAG_KEY_DEST_HINT_WORK); | |
492 | hintWay.deleteTag(TKM_DEST_HINT_WORK); | |
493 | 493 | hintWay.addTag("mkgmap:dest_hint", hint); |
494 | 494 | } |
495 | 495 | return hint; |
504 | 504 | while (!destinationLinkWays.isEmpty()) { |
505 | 505 | Way w = destinationLinkWays.iterator().next(); |
506 | 506 | destinationLinkWays.remove(w); |
507 | String highwayLinkTag = w.getTag(TAG_KEY_HIGHWAY); | |
507 | String highwayLinkTag = w.getTag(TK_HIGHWAY); | |
508 | 508 | if (!canSplit(w) || !highwayLinkTag.endsWith("_link")) |
509 | 509 | continue; |
510 | 510 | |
622 | 622 | * @return <code>true</code> way is oneway |
623 | 623 | */ |
624 | 624 | private static boolean isOnewayInDirection(Way w) { |
625 | if (w.tagIsLikeYes(TAG_KEY_ONEWAY)) { | |
625 | if (w.tagIsLikeYes(TK_ONEWAY)) { | |
626 | 626 | return true; |
627 | 627 | } |
628 | 628 | |
629 | 629 | // check if oneway is set implicitly by the highway type (motorway and motorway_link) |
630 | String onewayTag = w.getTag(TAG_KEY_ONEWAY); | |
631 | String highwayTag = w.getTag(TAG_KEY_HIGHWAY); | |
630 | String onewayTag = w.getTag(TK_ONEWAY); | |
631 | String highwayTag = w.getTag(TK_HIGHWAY); | |
632 | 632 | return onewayTag == null && highwayTag != null |
633 | 633 | && ("motorway".equals(highwayTag)|| "motorway_link".equals(highwayTag)); |
634 | 634 | } |
639 | 639 | * @return <code>true</code> way is oneway in opposite direction |
640 | 640 | */ |
641 | 641 | private static boolean isOnewayOppositeDirection(Way w) { |
642 | return "-1".equals(w.getTag(TAG_KEY_ONEWAY)); | |
642 | return "-1".equals(w.getTag(TK_ONEWAY)); | |
643 | 643 | } |
644 | 644 | |
645 | 645 | /** |
648 | 648 | * @return <code>true</code> way is not oneway |
649 | 649 | */ |
650 | 650 | private static boolean isNotOneway(Way w) { |
651 | return "no".equals(w.getTag(TAG_KEY_ONEWAY)) || (!isOnewayInDirection(w) && !isOnewayOppositeDirection(w)); | |
651 | return "no".equals(w.getTag(TK_ONEWAY)) || (!isOnewayInDirection(w) && !isOnewayOppositeDirection(w)); | |
652 | 652 | } |
653 | 653 | } |
53 | 53 | private EnhancedProperties props; |
54 | 54 | |
55 | 55 | @Override |
56 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
56 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
57 | 57 | boundaryDirName = props.getProperty("bounds"); |
58 | 58 | |
59 | 59 | if (boundaryDirName == null) { |
147 | 147 | if (mpCenter != null && saver.getBoundingBox().contains(mpCenter)){ |
148 | 148 | // create a fake node for which the bounds information is collected |
149 | 149 | Node mpNode = new Node(r.getOriginalId(), mpCenter); |
150 | mpNode.setFakeId(); | |
150 | mpNode.markAsGeneratedFrom(r); | |
151 | 151 | processElem(mpNode); |
152 | 152 | // copy the bounds tags back to the multipolygon |
153 | 153 | for (String boundsTag : BoundaryQuadTree.mkgmapTagsArray) { |
236 | 236 | for (Area area : finishedAreas) { |
237 | 237 | Way w = singularAreaToWay(area, rel.getOriginalId()); |
238 | 238 | if (w != null) { |
239 | w.setFakeId(); | |
239 | w.markAsGeneratedFrom(rel); | |
240 | 240 | // make sure that equal coords are changed to identical coord instances |
241 | 241 | // this allows merging in the ShapeMerger |
242 | 242 | int n = w.getPoints().size(); |
22 | 22 | private ElementSaver saver; |
23 | 23 | |
24 | 24 | @Override |
25 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
25 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
26 | 26 | this.saver = saver; |
27 | 27 | return true; |
28 | 28 | } |
32 | 32 | long t1 = System.currentTimeMillis(); |
33 | 33 | log.info("Finishing multipolygons"); |
34 | 34 | for (Way way : saver.getWays().values()) { |
35 | String removeTag = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY); | |
35 | String removeTag = way.getTag(ElementSaver.TKM_REMOVETAGS); | |
36 | 36 | if (removeTag == null) { |
37 | 37 | continue; |
38 | 38 | } |
40 | 40 | if (log.isDebugEnabled()) { |
41 | 41 | log.debug("Remove tags",Arrays.toString(tagsToRemove),"from way",way.getId(),way.toTagString()); |
42 | 42 | } |
43 | way.deleteTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY); | |
43 | way.deleteTag(ElementSaver.TKM_REMOVETAGS); | |
44 | 44 | for (String rTag : tagsToRemove) { |
45 | 45 | way.deleteTag(rTag); |
46 | 46 | } |
55 | 55 | public static final String STYLE_FILTER_POLYGON = "polygon"; |
56 | 56 | |
57 | 57 | /** A tag that is set with value true on each polygon that is created by the mp processing. */ |
58 | public static final short MP_CREATED_TAG_KEY = TagDict.getInstance().xlate("mkgmap:mp_created"); | |
59 | private static final short MP_ROLE_TAG_KEY = TagDict.getInstance().xlate("mkgmap:mp_role"); | |
60 | private static final short CACHE_AREA_SIZE_TAG_KEY = TagDict.getInstance().xlate("mkgmap:cache_area_size"); | |
58 | public static final short TKM_MP_CREATED = TagDict.getInstance().xlate("mkgmap:mp_created"); | |
59 | private static final short TKM_MP_ROLE = TagDict.getInstance().xlate("mkgmap:mp_role"); | |
60 | private static final short TKM_CACHE_AREA_SIZEKEY = TagDict.getInstance().xlate("mkgmap:cache_area_size"); | |
61 | 61 | private final Map<Long, Way> tileWayMap; |
62 | 62 | private final Map<Long, String> roleMap = new HashMap<>(); |
63 | 63 | |
553 | 553 | |
554 | 554 | /** |
555 | 555 | * Removes all non closed ways from the given list. |
556 | * <code>{@link Way#hasIdenticalEndPoints()} == false</code>) | |
556 | * <code>!{@link Way#hasIdenticalEndPoints()}</code>) | |
557 | 557 | * |
558 | 558 | * @param wayList |
559 | 559 | * list of ways |
1001 | 1001 | mpWay.setFullArea(fullArea); |
1002 | 1002 | // mark this polygons so that only polygon style rules are applied |
1003 | 1003 | mpWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_POLYGON); |
1004 | mpWay.addTag(MP_CREATED_TAG_KEY, "true"); | |
1004 | mpWay.addTag(TKM_MP_CREATED, "true"); | |
1005 | 1005 | |
1006 | 1006 | if (currentPolygon.outer) { |
1007 | mpWay.addTag(MP_ROLE_TAG_KEY, "outer"); | |
1007 | mpWay.addTag(TKM_MP_ROLE, "outer"); | |
1008 | 1008 | if (isAreaSizeCalculated()) |
1009 | 1009 | mpAreaSize += calcAreaSize(mpWay.getPoints()); |
1010 | 1010 | } else { |
1011 | mpWay.addTag(MP_ROLE_TAG_KEY, "inner"); | |
1011 | mpWay.addTag(TKM_MP_ROLE, "inner"); | |
1012 | 1012 | } |
1013 | 1013 | |
1014 | 1014 | getMpPolygons().put(mpWay.getId(), mpWay); |
1069 | 1069 | // the simple line information should be used. |
1070 | 1070 | for (Way orgOuterWay : outerWaysForLineTagging) { |
1071 | 1071 | Way lineTagWay = new Way(getOriginalId(), orgOuterWay.getPoints()); |
1072 | lineTagWay.setFakeId(); | |
1072 | lineTagWay.markAsGeneratedFrom(this); | |
1073 | 1073 | lineTagWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_LINE); |
1074 | lineTagWay.addTag(MP_CREATED_TAG_KEY, "true"); | |
1074 | lineTagWay.addTag(TKM_MP_CREATED, "true"); | |
1075 | 1075 | if (mpAreaSizeStr != null) { |
1076 | 1076 | // assign the area size of the whole multipolygon to all outer polygons |
1077 | lineTagWay.addTag(CACHE_AREA_SIZE_TAG_KEY, mpAreaSizeStr); | |
1077 | lineTagWay.addTag(TKM_CACHE_AREA_SIZEKEY, mpAreaSizeStr); | |
1078 | 1078 | } |
1079 | 1079 | for (Entry<String,String> tag : outerTags.entrySet()) { |
1080 | 1080 | lineTagWay.addTag(tag.getKey(), tag.getValue()); |
1104 | 1104 | if (isAreaSizeCalculated()) { |
1105 | 1105 | // assign the area size of the whole multipolygon to all outer polygons |
1106 | 1106 | String mpAreaSizeStr = String.format(Locale.US, "%.3f", mpAreaSize); |
1107 | addTag(CACHE_AREA_SIZE_TAG_KEY, mpAreaSizeStr); | |
1107 | addTag(TKM_CACHE_AREA_SIZEKEY, mpAreaSizeStr); | |
1108 | 1108 | for (Way w : mpPolygons.values()) { |
1109 | if ("outer".equals(w.getTag(MP_ROLE_TAG_KEY))) { | |
1110 | w.addTag(CACHE_AREA_SIZE_TAG_KEY, mpAreaSizeStr); | |
1109 | if ("outer".equals(w.getTag(TKM_MP_ROLE))) { | |
1110 | w.addTag(TKM_CACHE_AREA_SIZEKEY, mpAreaSizeStr); | |
1111 | 1111 | } |
1112 | 1112 | } |
1113 | 1113 | } |
1805 | 1805 | // the simple line information should be used. |
1806 | 1806 | for (Way orgOuterWay : outerWaysForLineTagging) { |
1807 | 1807 | Way lineTagWay = new Way(getOriginalId(), orgOuterWay.getPoints()); |
1808 | lineTagWay.setFakeId(); | |
1808 | lineTagWay.markAsGeneratedFrom(this); | |
1809 | 1809 | lineTagWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_LINE); |
1810 | lineTagWay.addTag(MP_CREATED_TAG_KEY, "true"); | |
1810 | lineTagWay.addTag(TKM_MP_CREATED, "true"); | |
1811 | 1811 | for (Entry<String, String> tag : tags.entrySet()) { |
1812 | 1812 | lineTagWay.addTag(tag.getKey(), tag.getValue()); |
1813 | 1813 | |
1870 | 1870 | if (tag == null || tag.isEmpty()) { |
1871 | 1871 | return; |
1872 | 1872 | } |
1873 | String tagsToRemove = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY); | |
1873 | String tagsToRemove = way.getTag(ElementSaver.TKM_REMOVETAGS); | |
1874 | 1874 | |
1875 | 1875 | if (tagsToRemove == null) { |
1876 | 1876 | tagsToRemove = tag; |
1883 | 1883 | } |
1884 | 1884 | tagsToRemove += ";" + tag; |
1885 | 1885 | } |
1886 | way.addTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY, tagsToRemove); | |
1886 | way.addTag(ElementSaver.TKM_REMOVETAGS, tagsToRemove); | |
1887 | 1887 | } |
1888 | 1888 | |
1889 | 1889 | /** |
1950 | 1950 | |
1951 | 1951 | public JoinedWay(Way originalWay) { |
1952 | 1952 | super(originalWay.getOriginalId(), originalWay.getPoints()); |
1953 | setFakeId(); | |
1953 | markAsGeneratedFrom(originalWay); | |
1954 | 1954 | originalWays = new ArrayList<>(); |
1955 | 1955 | addWay(originalWay); |
1956 | 1956 |
16 | 16 | |
17 | 17 | public class OsmCoastDataSource extends OsmMapDataSource { |
18 | 18 | |
19 | private static final Set<String> coastlineTags = Collections.singleton("natural"); | |
20 | ||
19 | @Override | |
21 | 20 | protected OsmReadingHooks[] getPossibleHooks() { |
22 | 21 | // no hooks |
23 | 22 | return new OsmReadingHooks[] {}; |
24 | 23 | } |
25 | 24 | |
25 | @Override | |
26 | 26 | protected void createElementSaver() { |
27 | 27 | elementSaver = new CoastlineElementSaver(getConfig()); |
28 | 28 | } |
29 | 29 | |
30 | @Override | |
30 | 31 | public Set<String> getUsedTags() { |
31 | return coastlineTags; | |
32 | return Collections.singleton("natural"); | |
32 | 33 | } |
33 | 34 | } |
30 | 30 | * |
31 | 31 | * @param way The OSM way. |
32 | 32 | */ |
33 | public void convertWay(Way way); | |
33 | default void convertWay(Way way) {} | |
34 | 34 | |
35 | 35 | /** |
36 | 36 | * Takes a node (that has its own identity) and converts it from the OSM |
38 | 38 | * |
39 | 39 | * @param node The node to convert. |
40 | 40 | */ |
41 | public void convertNode(Node node); | |
41 | default void convertNode(Node node) {} | |
42 | 42 | |
43 | 43 | /** |
44 | 44 | * Takes a relation and applies rules that affect the garmin types |
51 | 51 | * |
52 | 52 | * @param relation The relation to convert. |
53 | 53 | */ |
54 | public void convertRelation(Relation relation); | |
54 | default void convertRelation(Relation relation) {} | |
55 | 55 | |
56 | public default void augmentWith(uk.me.parabola.mkgmap.reader.osm.ElementSaver elementSaver) { | |
57 | } | |
56 | default void augmentWith(uk.me.parabola.mkgmap.reader.osm.ElementSaver elementSaver) {} | |
58 | 57 | |
59 | 58 | /** |
60 | 59 | * Set the bounding box for this map. This should be set before any other |
68 | 67 | * |
69 | 68 | * @param bbox The bounding area. |
70 | 69 | */ |
71 | public void setBoundingBox(Area bbox); | |
70 | default void setBoundingBox(Area bbox) {} | |
72 | 71 | |
73 | 72 | /** |
74 | 73 | * Called when all conversion has been done. |
75 | 74 | */ |
76 | public void end(); | |
75 | default void end() {} | |
77 | 76 | |
78 | 77 | /** |
79 | 78 | * @return true/false if source contains info about driving side, else null |
80 | 79 | */ |
81 | public Boolean getDriveOnLeft(); | |
80 | default Boolean getDriveOnLeft() { | |
81 | return null; // unknown | |
82 | } | |
82 | 83 | } |
18 | 18 | import java.util.Set; |
19 | 19 | import java.util.regex.Pattern; |
20 | 20 | |
21 | import uk.me.parabola.imgfmt.ExitException; | |
21 | 22 | import uk.me.parabola.imgfmt.FormatException; |
22 | 23 | import uk.me.parabola.imgfmt.app.Area; |
23 | 24 | import uk.me.parabola.imgfmt.app.Coord; |
77 | 78 | usedTags = null; |
78 | 79 | return; |
79 | 80 | } |
80 | usedTags = new HashMap<String, String>(); | |
81 | usedTags = new HashMap<>(); | |
81 | 82 | for (String s : used) { |
82 | 83 | if (s == null) { |
83 | 84 | continue; |
161 | 162 | * It is saved |
162 | 163 | * @param way The way that was read. |
163 | 164 | */ |
164 | protected void endWay(Way way) { | |
165 | protected final void endWay(Way way) { | |
165 | 166 | way.setClosedInOSM(firstNodeRef == lastNodeRef); |
166 | 167 | way.setComplete(!missingNodeRef); |
167 | 168 | |
174 | 175 | * @param way The Way. |
175 | 176 | * @param id The coordinate id. |
176 | 177 | */ |
177 | protected void addCoordToWay(Way way, long id) { | |
178 | protected final void addCoordToWay(Way way, long id) { | |
178 | 179 | lastNodeRef = id; |
179 | 180 | if (firstNodeRef == 0) firstNodeRef = id; |
180 | 181 | |
181 | 182 | Coord co = saver.getCoord(id); |
182 | 183 | |
183 | 184 | if (co != null) { |
184 | hooks.onCoordAddedToWay(way, id, co); | |
185 | co = saver.getCoord(id); | |
185 | Node node = saver.getNode(id); | |
186 | if (node != null && node.getTagCount() > 0) { | |
187 | hooks.onNodeAddedToWay(way, id); | |
188 | // hooks can change the node and the coord object associated with the id | |
189 | co = saver.getCoord(id); | |
190 | if (co == null) { | |
191 | throw new ExitException("Internal error: hooks removed coord with id " + id); | |
192 | } | |
193 | } | |
186 | 194 | way.addPoint(co); |
187 | 195 | } else { |
188 | 196 | missingNodeRef = true; |
31 | 31 | |
32 | 32 | import uk.me.parabola.imgfmt.Utils; |
33 | 33 | import uk.me.parabola.log.Logger; |
34 | import uk.me.parabola.mkgmap.CommandArgs; | |
34 | 35 | import uk.me.parabola.mkgmap.general.LevelInfo; |
35 | 36 | import uk.me.parabola.mkgmap.general.LoadableMapDataSource; |
36 | import uk.me.parabola.mkgmap.osmstyle.NameFinder; | |
37 | 37 | import uk.me.parabola.mkgmap.osmstyle.StyleImpl; |
38 | 38 | import uk.me.parabola.mkgmap.osmstyle.StyledConverter; |
39 | 39 | import uk.me.parabola.mkgmap.reader.MapperBasedMapDataSource; |
238 | 238 | for (OsmReadingHooks p : getPossibleHooks()) { |
239 | 239 | if (p instanceof ResidentialHook && style != null && !style.getUsedTags().contains("mkgmap:residential")) |
240 | 240 | continue; |
241 | if (p.init(saver, props)){ | |
241 | if (p.init(saver, props, style)){ | |
242 | 242 | plugins.add(p); |
243 | if (p instanceof RelationStyleHook) | |
244 | ((RelationStyleHook) p).setStyle(style); | |
245 | 243 | } |
246 | 244 | } |
247 | 245 | |
288 | 286 | parts[0] = parts[0].trim(); |
289 | 287 | parts[1] = parts[1].trim(); |
290 | 288 | if ("*".equals(parts[1])) { |
291 | deletedTags.put(parts[0], new HashSet<String>()); | |
289 | deletedTags.put(parts[0], new HashSet<>()); | |
292 | 290 | } else { |
293 | Set<String> vals = deletedTags.get(parts[0]); | |
294 | if (vals == null) | |
295 | vals = new HashSet<>(); | |
296 | vals.add(parts[1]); | |
297 | deletedTags.put(parts[0], vals); | |
291 | deletedTags.computeIfAbsent(parts[0], k-> new HashSet<>()).add(parts[1]); | |
298 | 292 | } |
299 | 293 | } else { |
300 | 294 | log.error("Ignoring bad line in deleted tags file: " + line); |
324 | 318 | setStyle(StyleImpl.readStyle(props)); |
325 | 319 | |
326 | 320 | usedTags.addAll(style.getUsedTags()); |
327 | usedTags.addAll(NameFinder.getNameTags(props)); | |
321 | // make sure that we don't remove tags which are only used with the mkgmap:from-node: prefix | |
322 | style.getUsedTags().stream().filter(s -> s.startsWith(POIGeneratorHook.FROM_NODE_PREFIX)) | |
323 | .map(s -> s.substring(POIGeneratorHook.FROM_NODE_PREFIX.length())).forEach(usedTags::add); | |
324 | usedTags.addAll(CommandArgs.getNameTags(props)); | |
328 | 325 | converter = new StyledConverter(style, mapper, props); |
329 | 326 | } |
330 | 327 |
16 | 16 | |
17 | 17 | public class OsmPrecompSeaDataSource extends OsmMapDataSource { |
18 | 18 | |
19 | private static final Set<String> coastlineTags = Collections.singleton("natural"); | |
20 | ||
19 | @Override | |
21 | 20 | protected OsmReadingHooks[] getPossibleHooks() { |
22 | 21 | // no hooks |
23 | 22 | return new OsmReadingHooks[] {}; |
24 | 23 | } |
25 | 24 | |
25 | @Override | |
26 | 26 | public Set<String> getUsedTags() { |
27 | return coastlineTags; | |
27 | return Collections.singleton("natural"); | |
28 | 28 | } |
29 | 29 | } |
14 | 14 | import java.util.Collections; |
15 | 15 | import java.util.Set; |
16 | 16 | |
17 | import uk.me.parabola.imgfmt.app.Coord; | |
18 | 17 | import uk.me.parabola.util.EnhancedProperties; |
19 | 18 | |
20 | 19 | /** |
48 | 47 | * element that is being passed in as it will be added automatically. |
49 | 48 | * |
50 | 49 | * @param props The command line options. |
50 | * @param style The style used for this input file | |
51 | 51 | * |
52 | 52 | * @return If you return false then this set of hooks will not be used. So if they |
53 | 53 | * are not needed based on the options supplied you can disable it. |
54 | 54 | */ |
55 | public default boolean init(ElementSaver saver, EnhancedProperties props) { | |
55 | default boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
56 | 56 | return true; |
57 | 57 | } |
58 | 58 | |
62 | 62 | * |
63 | 63 | * @return the tag names used by this hook |
64 | 64 | */ |
65 | public default Set<String> getUsedTags() { | |
65 | default Set<String> getUsedTags() { | |
66 | 66 | return Collections.emptySet(); |
67 | 67 | } |
68 | 68 | |
80 | 80 | * |
81 | 81 | * @param way The osm way. |
82 | 82 | */ |
83 | public default void onAddWay(Way way) {} | |
83 | default void onAddWay(Way way) {} | |
84 | 84 | |
85 | 85 | /** |
86 | * This is called whenever a node is added to a way. A node is something with tags, not just a Coord. | |
86 | * This is called whenever a tagged node is added to a way. | |
87 | 87 | * |
88 | * The way will not have been added via addWay() yet. The node is the node that | |
89 | * | |
88 | * The way will not have been added via addWay() yet. | |
89 | * Hooks can change the node and the coord object associated with the id, but they must not remove them. | |
90 | * | |
90 | 91 | * @param way The incomplete way. |
91 | 92 | * @param coordId The coordinate id of the node that is being added. |
92 | * @param co The coordinate. | |
93 | 93 | */ |
94 | public default void onCoordAddedToWay(Way way, long coordId, Coord co) {} | |
94 | default void onNodeAddedToWay(Way way, long coordId) {} | |
95 | 95 | |
96 | 96 | /** |
97 | 97 | * Called after the file has been read. Can be used to add more elements to the saver |
98 | 98 | * based on information stored up. |
99 | 99 | */ |
100 | public default void end() {} | |
100 | default void end() {} | |
101 | 101 | |
102 | 102 | } |
15 | 15 | import java.util.HashSet; |
16 | 16 | import java.util.Set; |
17 | 17 | |
18 | import uk.me.parabola.imgfmt.app.Coord; | |
19 | 18 | import uk.me.parabola.util.EnhancedProperties; |
20 | 19 | |
21 | 20 | /** |
48 | 47 | } |
49 | 48 | |
50 | 49 | @Override |
51 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
50 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
52 | 51 | for (int i = 0; i < readingHooks.length; i++) |
53 | readingHooks[i].init(saver, props); | |
52 | readingHooks[i].init(saver, props, style); | |
54 | 53 | return true; |
55 | 54 | } |
56 | 55 | |
61 | 60 | } |
62 | 61 | |
63 | 62 | @Override |
64 | public void onCoordAddedToWay(Way way, long coordId, Coord co) { | |
63 | public void onNodeAddedToWay(Way way, long coordId) { | |
65 | 64 | for (int i = 0; i < readingHooks.length; i++) |
66 | readingHooks[i].onCoordAddedToWay(way, coordId, co); | |
65 | readingHooks[i].onNodeAddedToWay(way, coordId); | |
67 | 66 | } |
68 | 67 | |
69 | 68 | @Override |
15 | 15 | import java.util.AbstractMap; |
16 | 16 | import java.util.ArrayList; |
17 | 17 | import java.util.Collections; |
18 | import java.util.HashSet; | |
19 | 18 | import java.util.IdentityHashMap; |
20 | 19 | import java.util.List; |
21 | 20 | import java.util.Map; |
22 | 21 | import java.util.Map.Entry; |
23 | 22 | import java.util.Set; |
23 | import java.util.stream.Collectors; | |
24 | 24 | |
25 | 25 | import uk.me.parabola.imgfmt.app.Coord; |
26 | 26 | import uk.me.parabola.log.Logger; |
63 | 63 | private static final Logger log = Logger.getLogger(POIGeneratorHook.class); |
64 | 64 | |
65 | 65 | private List<Entry<String,String>> poiPlacementTags; |
66 | /** | |
67 | * maps only those locations which are used in nodes with tags which are used in | |
68 | * the points rules with the {@code FROM_NODE_PREFIX} and which are not already {@link CoordPOI} instances. | |
69 | * The mapping is only needed to create the POIs, thus we don't create {@link CoordPOI} instances for them. | |
70 | */ | |
71 | private IdentityHashMap<Coord, Node> coordToNodeMap; | |
66 | 72 | |
67 | 73 | private ElementSaver saver; |
68 | 74 | |
69 | 75 | private boolean poisToAreas = false; |
70 | 76 | private boolean poisToLines = false; |
77 | private boolean poisToLinesStart = false; | |
78 | private boolean poisToLinesEnd = false; | |
79 | private boolean poisToLinesMid = false; | |
80 | private boolean poisToLinesOther = false; | |
71 | 81 | private NameFinder nameFinder; |
72 | 82 | private AreaSizeFunction areaSizeFunction = new AreaSizeFunction(); |
83 | ||
84 | private Set<String> usedTagsPOI; | |
73 | 85 | |
74 | 86 | /** Name of the bool tag that is set to true if a POI is created from an area */ |
75 | public static final short AREA2POI_TAG = TagDict.getInstance().xlate("mkgmap:area2poi"); | |
76 | public static final short LINE2POI_TAG = TagDict.getInstance().xlate("mkgmap:line2poi"); | |
77 | public static final short LINE2POI_TYPE_TAG = TagDict.getInstance().xlate("mkgmap:line2poitype"); | |
78 | public static final short WAY_LENGTH_TAG = TagDict.getInstance().xlate("mkgmap:way-length"); | |
87 | public static final short TKM_AREA2POI = TagDict.getInstance().xlate("mkgmap:area2poi"); | |
88 | public static final short TKM_LINE2POI = TagDict.getInstance().xlate("mkgmap:line2poi"); | |
89 | public static final short TKM_LINE2POI_TYPE = TagDict.getInstance().xlate("mkgmap:line2poitype"); | |
90 | public static final short TKM_WAY_LENGTH = TagDict.getInstance().xlate("mkgmap:way-length"); | |
79 | 91 | |
80 | 92 | @Override |
81 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
93 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
82 | 94 | poisToAreas = props.containsKey("add-pois-to-areas"); |
83 | 95 | poisToLines = props.containsKey("add-pois-to-lines"); |
96 | if (poisToLines) { | |
97 | String[] opts = {"all"}; | |
98 | if (!props.getProperty("add-pois-to-lines").isEmpty()) { | |
99 | opts = props.getProperty("add-pois-to-lines").split(","); | |
100 | } | |
101 | ||
102 | for (String opt : opts) { | |
103 | switch (opt.trim()) { | |
104 | case "start": | |
105 | poisToLinesStart = true; | |
106 | break; | |
107 | case "end": | |
108 | poisToLinesEnd = true; | |
109 | break; | |
110 | case "mid": | |
111 | poisToLinesMid = true; | |
112 | break; | |
113 | case "other": | |
114 | poisToLinesOther= true; | |
115 | break; | |
116 | case "all": | |
117 | poisToLinesStart= true; | |
118 | poisToLinesEnd= true; | |
119 | poisToLinesMid = true; | |
120 | poisToLinesOther= true; | |
121 | break; | |
122 | ||
123 | default: | |
124 | throw new IllegalArgumentException("Invalied argument '"+opt+"' for add-pois-to-lines"); | |
125 | } | |
126 | } | |
127 | ||
128 | } | |
84 | 129 | |
85 | 130 | if (!(poisToAreas || poisToLines)) { |
86 | 131 | log.info("Disable Areas2POIHook because add-pois-to-areas and add-pois-to-lines option is not set."); |
91 | 136 | this.poiPlacementTags = getPoiPlacementTags(props); |
92 | 137 | |
93 | 138 | this.saver = saver; |
94 | ||
139 | if (style != null && style.getUsedTagsPOI() != null) { | |
140 | // extract special tags used in the points file | |
141 | usedTagsPOI = style.getUsedTagsPOI().stream() | |
142 | .filter(s -> s.startsWith(FROM_NODE_PREFIX)) | |
143 | .map(s -> s.substring(POIGeneratorHook.FROM_NODE_PREFIX.length())) | |
144 | .collect(Collectors.toSet()); | |
145 | } else { | |
146 | usedTagsPOI = Collections.emptySet(); | |
147 | } | |
95 | 148 | return true; |
96 | 149 | } |
97 | 150 | |
149 | 202 | return tagList; |
150 | 203 | } |
151 | 204 | |
152 | ||
153 | 205 | @Override |
154 | 206 | public Set<String> getUsedTags() { |
155 | // return all tags defined in the poiPlacementTags | |
156 | Set<String> tags = new HashSet<>(); | |
157 | for (Entry<String,String> poiTag : poiPlacementTags) { | |
158 | tags.add(poiTag.getKey()); | |
159 | } | |
160 | return tags; | |
207 | return poiPlacementTags.stream().map(Map.Entry::getKey).collect(Collectors.toSet()); | |
161 | 208 | } |
162 | 209 | |
163 | 210 | @Override |
164 | 211 | public void end() { |
165 | 212 | log.info(getClass().getSimpleName(), "started"); |
213 | coordToNodeMap = new IdentityHashMap<>(); | |
214 | if (!usedTagsPOI.isEmpty()) { | |
215 | for (Node n : saver.getNodes().values()) { | |
216 | if (n.getLocation() instanceof CoordPOI) | |
217 | continue; | |
218 | for (String key : usedTagsPOI) { | |
219 | if (n.getTag(key) != null) { | |
220 | coordToNodeMap.put(n.getLocation(), n); | |
221 | break; | |
222 | } | |
223 | } | |
224 | } | |
225 | } | |
166 | 226 | addPOIsForWays(); |
167 | 227 | addPOIsForMPs(); |
228 | coordToNodeMap.clear(); | |
168 | 229 | log.info(getClass().getSimpleName(), "finished"); |
169 | 230 | } |
170 | 231 | |
208 | 269 | } |
209 | 270 | |
210 | 271 | // do not add POIs for polygons created by multipolygon processing |
211 | if (w.tagIsLikeYes(MultiPolygonRelation.MP_CREATED_TAG_KEY)) { | |
272 | if (w.tagIsLikeYes(MultiPolygonRelation.TKM_MP_CREATED)) { | |
212 | 273 | if (log.isDebugEnabled()) |
213 | 274 | log.debug("MP processed: Do not create POI for", w.toTagString()); |
214 | 275 | continue; |
242 | 303 | // get the coord where the poi is placed |
243 | 304 | Coord poiCoord = null; |
244 | 305 | // do we have some labeling coords? |
245 | if (labelCoords.size() > 0) { | |
306 | if (!labelCoords.isEmpty()) { | |
246 | 307 | int poiOrder = Integer.MAX_VALUE; |
247 | 308 | // go through all points of the way and check if one of the coords |
248 | 309 | // is a labeling coord |
249 | 310 | for (Coord c : polygon.getPoints()) { |
250 | 311 | Integer cOrder = labelCoords.get(c); |
251 | 312 | if (cOrder != null && cOrder.intValue() < poiOrder) { |
252 | // this coord is a labeling coord | |
313 | // this coord is a labelling coord | |
253 | 314 | // use it for the current way |
254 | 315 | poiCoord = c; |
255 | 316 | poiOrder = cOrder; |
267 | 328 | } |
268 | 329 | // add tag mkgmap:cache_area_size to the original polygon so that it is copied to the POI |
269 | 330 | areaSizeFunction.value(polygon); |
270 | Node poi = createPOI(polygon, poiCoord, AREA2POI_TAG, 0); | |
271 | saver.addNode(poi); | |
331 | addPOI(polygon, poiCoord, TKM_AREA2POI, 0); | |
272 | 332 | } |
273 | 333 | |
274 | 334 | |
286 | 346 | prevC = c; |
287 | 347 | } |
288 | 348 | |
289 | Node startNode = createPOI(line, line.getFirstPoint(), LINE2POI_TAG, sumDist); | |
290 | startNode.addTag(LINE2POI_TYPE_TAG,"start"); | |
291 | saver.addNode(startNode); | |
292 | ||
293 | Node endNode = createPOI(line, line.getLastPoint(), LINE2POI_TAG, sumDist); | |
294 | endNode.addTag(LINE2POI_TYPE_TAG,"end"); | |
295 | saver.addNode(endNode); | |
296 | ||
297 | int noPOIs = 2; | |
298 | Coord lastPoint = line.getFirstPoint(); | |
299 | if (line.getPoints().size() > 2) { | |
300 | for (Coord inPoint : line.getPoints().subList(1, line.getPoints().size()-1)) { | |
301 | if (inPoint.equals(lastPoint)){ | |
349 | int countPOIs = 0; | |
350 | if (poisToLinesStart) { | |
351 | Node startNode = addPOI(line, line.getFirstPoint(), TKM_LINE2POI, sumDist); | |
352 | startNode.addTag(TKM_LINE2POI_TYPE, "start"); | |
353 | countPOIs++; | |
354 | } | |
355 | ||
356 | if (poisToLinesEnd) { | |
357 | Node endNode = addPOI(line, line.getLastPoint(), TKM_LINE2POI, sumDist); | |
358 | endNode.addTag(TKM_LINE2POI_TYPE, "end"); | |
359 | countPOIs++; | |
360 | } | |
361 | ||
362 | if (poisToLinesOther && line.getPoints().size() > 2) { | |
363 | Coord lastPoint = line.getFirstPoint(); | |
364 | for (Coord inPoint : line.getPoints().subList(1, line.getPoints().size() - 1)) { | |
365 | if (inPoint.equals(lastPoint)) { | |
302 | 366 | continue; |
303 | 367 | } |
304 | 368 | lastPoint = inPoint; |
305 | Node innerNode = createPOI(line, inPoint, LINE2POI_TAG, sumDist); | |
306 | innerNode.addTag(LINE2POI_TYPE_TAG,"inner"); | |
307 | saver.addNode(innerNode); | |
308 | noPOIs++; | |
309 | } | |
310 | } | |
311 | ||
312 | Coord midPoint = null; | |
313 | double remMidDist = sumDist/2; | |
314 | for (int midPos =0; midPos < dists.size(); midPos++) { | |
315 | double nextDist = dists.get(midPos); | |
316 | if (remMidDist <= nextDist) { | |
317 | double frac = remMidDist/nextDist; | |
318 | midPoint = line.getPoints().get(midPos).makeBetweenPoint(line.getPoints().get(midPos+1), frac); | |
319 | break; | |
320 | } | |
321 | remMidDist -= nextDist; | |
322 | } | |
323 | ||
324 | if (midPoint != null) { | |
325 | Node midNode = createPOI(line, midPoint, LINE2POI_TAG, sumDist); | |
326 | midNode.addTag(LINE2POI_TYPE_TAG,"mid"); | |
327 | saver.addNode(midNode); | |
328 | noPOIs++; | |
329 | } | |
330 | return noPOIs; | |
331 | ||
332 | } | |
333 | ||
334 | private static Node createPOI(Element source, Coord poiCoord, short poiTypeTagKey, double wayLength) { | |
369 | Node innerNode = addPOI(line, inPoint, TKM_LINE2POI, sumDist); | |
370 | innerNode.addTag(TKM_LINE2POI_TYPE, "inner"); | |
371 | countPOIs++; | |
372 | } | |
373 | } | |
374 | if (poisToLinesMid) { | |
375 | Coord midPoint = null; | |
376 | double remMidDist = sumDist / 2; | |
377 | for (int midPos = 0; midPos < dists.size(); midPos++) { | |
378 | double nextDist = dists.get(midPos); | |
379 | if (remMidDist <= nextDist) { | |
380 | double frac = remMidDist / nextDist; | |
381 | midPoint = line.getPoints().get(midPos).makeBetweenPoint(line.getPoints().get(midPos + 1), frac); | |
382 | break; | |
383 | } | |
384 | remMidDist -= nextDist; | |
385 | } | |
386 | ||
387 | if (midPoint != null) { | |
388 | Node midNode = addPOI(line, midPoint, TKM_LINE2POI, sumDist); | |
389 | midNode.addTag(TKM_LINE2POI_TYPE, "mid"); | |
390 | countPOIs++; | |
391 | } | |
392 | } | |
393 | return countPOIs; | |
394 | ||
395 | } | |
396 | ||
397 | /** Prefix that is added to tags which are copied from the original node. */ | |
398 | public static final String FROM_NODE_PREFIX = "mkgmap:from-node:"; | |
399 | ||
400 | private Node addPOI(Element source, Coord poiCoord, short poiTypeTagKey, double wayLength) { | |
335 | 401 | Node poi = new Node(source.getOriginalId(), poiCoord); |
336 | poi.setFakeId(); | |
402 | poi.markAsGeneratedFrom(source); | |
337 | 403 | poi.copyTags(source); |
338 | 404 | poi.deleteTag(MultiPolygonRelation.STYLE_FILTER_TAG); |
339 | 405 | poi.addTag(poiTypeTagKey, "true"); |
340 | if (poiTypeTagKey == LINE2POI_TAG) { | |
341 | poi.addTag(WAY_LENGTH_TAG, String.valueOf(Math.round(wayLength))); | |
342 | } | |
406 | if (poiTypeTagKey == TKM_LINE2POI) { | |
407 | poi.addTag(TKM_WAY_LENGTH, String.valueOf(Math.round(wayLength))); | |
408 | } | |
409 | ||
410 | Node node = null; | |
411 | if (poiCoord instanceof CoordPOI) { | |
412 | node = ((CoordPOI) poiCoord).getNode(); | |
413 | } else { | |
414 | node = coordToNodeMap.get(poiCoord); | |
415 | } | |
416 | if (node != null) { | |
417 | // add the original tags of the node with the prefix mkgmap:from-node: | |
418 | for (Entry<String, String> entry : node.getTagEntryIterator()) { | |
419 | if (!entry.getKey().startsWith("mkgmap:")) { | |
420 | poi.addTag(FROM_NODE_PREFIX + entry.getKey(), entry.getValue()); | |
421 | } | |
422 | } | |
423 | } | |
424 | ||
343 | 425 | if (log.isDebugEnabled()) { |
344 | 426 | log.debug("Create POI",poi.toTagString(),"from",source.getId(),source.toTagString()); |
345 | 427 | } |
428 | saver.addNode(poi); | |
346 | 429 | return poi; |
347 | 430 | |
348 | 431 | } |
396 | 479 | else |
397 | 480 | point = adminCentre.getLocation(); |
398 | 481 | } |
399 | if (point == null) { | |
400 | continue; | |
401 | } | |
402 | ||
403 | Node poi = createPOI(r, point, AREA2POI_TAG, 0); | |
404 | // remove the type tag which makes only sense for relations | |
405 | poi.deleteTag("type"); | |
406 | saver.addNode(poi); | |
407 | mps2POI++; | |
482 | ||
483 | if (point != null) { | |
484 | Node poi = addPOI(r, point, TKM_AREA2POI, 0); | |
485 | // remove the type tag which makes only sense for relations | |
486 | poi.deleteTag("type"); | |
487 | mps2POI++; | |
488 | } | |
408 | 489 | } |
409 | 490 | log.info(mps2POI,"POIs from multipolygons created"); |
410 | 491 | } |
25 | 25 | private ElementSaver saver; |
26 | 26 | private NameFinder nameFinder; |
27 | 27 | |
28 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
28 | @Override | |
29 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
29 | 30 | this.saver = saver; |
31 | this.style = style; | |
30 | 32 | nameFinder = new NameFinder(props); |
31 | 33 | return true; |
32 | 34 | } |
33 | 35 | |
34 | public void setStyle(Style style){ | |
35 | this.style = style; | |
36 | } | |
37 | ||
36 | @Override | |
38 | 37 | public void end() { |
39 | 38 | Rule relationRules = style.getRelationRules(); |
40 | 39 | for (Relation rel : saver.getRelations().values()) { |
13 | 13 | package uk.me.parabola.mkgmap.reader.osm; |
14 | 14 | |
15 | 15 | import java.util.ArrayList; |
16 | import java.util.Arrays; | |
16 | 17 | import java.util.HashSet; |
17 | 18 | import java.util.List; |
18 | 19 | import java.util.Set; |
40 | 41 | private NameFinder nameFinder; |
41 | 42 | |
42 | 43 | @Override |
43 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
44 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
44 | 45 | if (!props.getProperty("residential-hook", true)) |
45 | 46 | return false; |
46 | 47 | this.nameFinder = new NameFinder(props); |
76 | 77 | residentialBoundaries = null; |
77 | 78 | } |
78 | 79 | |
79 | private static final short landuseTagKey = TagDict.getInstance().xlate("landuse"); | |
80 | private static final short nameTagKey = TagDict.getInstance().xlate("name"); | |
81 | private static final short styleFilterTagKey = TagDict.getInstance().xlate("mkgmap:stylefilter"); | |
82 | private static final short otherKey = TagDict.getInstance().xlate("mkgmap:other"); | |
80 | private static final short TK_LANDUSE = TagDict.getInstance().xlate("landuse"); | |
81 | private static final short TK_NAME = TagDict.getInstance().xlate("name"); | |
82 | private static final short TKM_STYLEFILTER = TagDict.getInstance().xlate("mkgmap:stylefilter"); | |
83 | private static final short TKM_OTHER = TagDict.getInstance().xlate("mkgmap:other"); | |
83 | 84 | |
84 | 85 | private BoundaryQuadTree buildResidentialBoundaryTree() { |
85 | 86 | List<Boundary> residentials = new ArrayList<>(); |
86 | 87 | Tags tags = new Tags(); |
87 | 88 | |
88 | 89 | for (Way way : saver.getWays().values()) { |
89 | if (way.hasIdenticalEndPoints() && "residential".equals(way.getTag(landuseTagKey))) { | |
90 | if ("polyline".equals(way.getTag(styleFilterTagKey))) | |
90 | if (way.hasIdenticalEndPoints() && "residential".equals(way.getTag(TK_LANDUSE))) { | |
91 | if ("polyline".equals(way.getTag(TKM_STYLEFILTER))) | |
91 | 92 | continue; |
92 | 93 | String name = nameFinder.getName(way); |
93 | tags.put(nameTagKey, name == null ? "yes": name); | |
94 | tags.put(TK_NAME, name == null ? "yes": name); | |
94 | 95 | Boundary b = new Boundary(Java2DConverter.createArea(way.getPoints()), tags, "w"+way.getId()); |
95 | 96 | residentials.add(b); |
96 | 97 | } |
115 | 116 | // try the mid point of the way first |
116 | 117 | int middle = way.getPoints().size() / 2; |
117 | 118 | Coord loc = way.hasIdenticalEndPoints() ? way.getCofG() : way.getPoints().get(middle); |
118 | if (! "residential".equals(way.getTag(landuseTagKey))) | |
119 | if (! "residential".equals(way.getTag(TK_LANDUSE))) | |
119 | 120 | residentialTags = residentialBoundaries.get(loc); |
120 | 121 | } |
121 | 122 | |
122 | 123 | if (residentialTags != null) { |
123 | elem.addTag("mkgmap:residential", residentialTags.get(otherKey)); | |
124 | elem.addTag("mkgmap:residential", residentialTags.get(TKM_OTHER)); | |
124 | 125 | } |
125 | 126 | } |
126 | 127 | |
127 | 128 | @Override |
128 | 129 | public Set<String> getUsedTags() { |
129 | Set<String> used = new HashSet<>(); | |
130 | used.add("landuse"); | |
131 | used.add("name"); | |
132 | return used; | |
130 | return new HashSet<>(Arrays.asList("landuse", "name")); | |
133 | 131 | } |
134 | 132 | } |
135 | 133 |
27 | 27 | */ |
28 | 28 | public class RoutingHook implements OsmReadingHooks { |
29 | 29 | |
30 | private final Set<String> usedTags; | |
30 | private final Set<String> usedTags = new HashSet<>(); | |
31 | 31 | |
32 | public RoutingHook() { | |
33 | usedTags = new HashSet<>(); | |
34 | usedTags.add("except"); | |
35 | usedTags.add("restriction"); | |
36 | usedTags.add("restriction:foot"); | |
37 | usedTags.add("restriction:hgv"); | |
38 | usedTags.add("restriction:motorcar"); | |
39 | usedTags.add("restriction:vehicle"); | |
40 | usedTags.add("restriction:motor_vehicle"); | |
41 | usedTags.add("restriction:bicycle"); | |
42 | usedTags.add("restriction:bus"); | |
43 | } | |
32 | @Override | |
33 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
34 | // only enabled if the route option is set | |
35 | if (props.containsKey("route")) { | |
44 | 36 | |
45 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
46 | ||
47 | if (props.getProperty("old-style", false)) { | |
48 | // the access tags need to be loaded if the old style handling | |
49 | // is active and access restrictions are handled by the java | |
50 | // source code and not by the style | |
51 | usedTags.add("access"); | |
52 | usedTags.add("bicycle"); | |
53 | usedTags.add("carpool"); | |
54 | usedTags.add("delivery"); | |
55 | usedTags.add("emergency"); | |
56 | usedTags.add("foot"); | |
57 | usedTags.add("goods"); | |
58 | usedTags.add("hgv"); | |
59 | usedTags.add("motorcar"); | |
60 | usedTags.add("motorcycle"); | |
61 | usedTags.add("psv"); | |
62 | usedTags.add("route"); | |
63 | usedTags.add("taxi"); | |
64 | } | |
65 | int admLevelNod3 = props.getProperty("add-boundary-nodes-at-admin-boundaries", 2); | |
66 | if (admLevelNod3 > 0) { | |
67 | usedTags.add("boundary"); | |
68 | usedTags.add("admin_level"); | |
37 | usedTags.add("except"); | |
38 | usedTags.add("restriction"); | |
39 | usedTags.add("restriction:foot"); | |
40 | usedTags.add("restriction:hgv"); | |
41 | usedTags.add("restriction:motorcar"); | |
42 | usedTags.add("restriction:vehicle"); | |
43 | usedTags.add("restriction:motor_vehicle"); | |
44 | usedTags.add("restriction:bicycle"); | |
45 | usedTags.add("restriction:bus"); | |
46 | ||
47 | if (props.getProperty("old-style", false)) { | |
48 | // the access tags need to be loaded if the old style handling | |
49 | // is active and access restrictions are handled by the java | |
50 | // source code and not by the style | |
51 | usedTags.add("access"); | |
52 | usedTags.add("bicycle"); | |
53 | usedTags.add("carpool"); | |
54 | usedTags.add("delivery"); | |
55 | usedTags.add("emergency"); | |
56 | usedTags.add("foot"); | |
57 | usedTags.add("goods"); | |
58 | usedTags.add("hgv"); | |
59 | usedTags.add("motorcar"); | |
60 | usedTags.add("motorcycle"); | |
61 | usedTags.add("psv"); | |
62 | usedTags.add("route"); | |
63 | usedTags.add("taxi"); | |
64 | } | |
65 | int admLevelNod3 = props.getProperty("add-boundary-nodes-at-admin-boundaries", 2); | |
66 | if (admLevelNod3 > 0) { | |
67 | usedTags.add("boundary"); | |
68 | usedTags.add("admin_level"); | |
69 | } | |
70 | return true; | |
69 | 71 | } |
70 | 72 | |
71 | ||
72 | // only enabled if the route option is set | |
73 | return props.containsKey("route"); | |
73 | return false; | |
74 | 74 | } |
75 | 75 | |
76 | 76 | @Override |
117 | 117 | * the whole thing is omitted if not used. |
118 | 118 | */ |
119 | 119 | @Override |
120 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
120 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
121 | 121 | this.saver = saver; |
122 | 122 | |
123 | 123 | precompSea = props.getProperty("precomp-sea", null); |
442 | 442 | */ |
443 | 443 | // create copy of way that has only the natural=coastline tag |
444 | 444 | Way shore = new Way(way.getOriginalId(), way.getPoints()); |
445 | shore.setFakeId(); | |
445 | shore.markAsGeneratedFrom(way); | |
446 | 446 | shore.addTag("natural", "coastline"); |
447 | 447 | saver.addWay(shore); |
448 | 448 | |
467 | 467 | if (way.hasIdenticalEndPoints()){ |
468 | 468 | // add a copy of this way to be able to draw it as a shape |
469 | 469 | Way shapeWay = new Way(way.getOriginalId(), way.getPoints()); |
470 | shapeWay.setFakeId(); | |
470 | shapeWay.markAsGeneratedFrom(way); | |
471 | 471 | shapeWay.copyTags(way); |
472 | 472 | // change the tag so that only special rules looking for it are firing |
473 | 473 | shapeWay.deleteTag("natural"); |
672 | 672 | for (Way w : seaPrecompWays) { |
673 | 673 | // set a new id to be sure that the precompiled ids do not |
674 | 674 | // interfere with the ids of this run |
675 | w.setFakeId(); | |
675 | w.markAsGeneratedFrom(w); | |
676 | 676 | |
677 | 677 | if ("land".equals(w.getTag("natural"))) { |
678 | 678 | landWays.add(w); |
790 | 790 | wm = w1; |
791 | 791 | } else { |
792 | 792 | wm = new Way(w1.getOriginalId(), w1.getPoints()); |
793 | wm.setFakeId(); | |
793 | wm.markAsGeneratedFrom(w1); | |
794 | 794 | beginMap.put(wm.getFirstPoint(), wm); |
795 | 795 | } |
796 | 796 | beginMap.remove(w2.getFirstPoint()); |
1000 | 1000 | toBeRemoved.add(segment); |
1001 | 1001 | for (List<Coord> pts : clipped) { |
1002 | 1002 | Way shore = new Way(segment.getOriginalId(), pts); |
1003 | shore.setFakeId(); | |
1003 | shore.markAsGeneratedFrom(segment); | |
1004 | 1004 | toBeAdded.add(shore); |
1005 | 1005 | } |
1006 | 1006 | } |
1089 | 1089 | wm = w1; |
1090 | 1090 | } else { |
1091 | 1091 | wm = new Way(w1.getOriginalId()); |
1092 | wm.setFakeId(); | |
1092 | wm.markAsGeneratedFrom(w1); | |
1093 | 1093 | shoreline.remove(w1); |
1094 | 1094 | shoreline.add(wm); |
1095 | 1095 | wm.getPoints().addAll(w1.getPoints()); |
33 | 33 | * @author Steve Ratcliffe |
34 | 34 | */ |
35 | 35 | public interface Style { |
36 | public String getOption(String name); | |
36 | String getOption(String name); | |
37 | 37 | |
38 | public StyleInfo getInfo(); | |
38 | StyleInfo getInfo(); | |
39 | 39 | |
40 | 40 | /** |
41 | 41 | * Get the rules that apply to ways. This includes lines and polygons |
42 | 42 | * as they are not separate primitives in osm. It is a merge of the line |
43 | 43 | * rules and the polygon rules. |
44 | 44 | */ |
45 | public Rule getWayRules(); | |
45 | Rule getWayRules(); | |
46 | 46 | |
47 | 47 | /** |
48 | 48 | * Get the rules that apply to nodes. |
49 | 49 | */ |
50 | public Rule getNodeRules(); | |
50 | Rule getNodeRules(); | |
51 | 51 | |
52 | 52 | /** |
53 | 53 | * Get the rules that apply to lines. |
54 | 54 | */ |
55 | public Rule getLineRules(); | |
55 | Rule getLineRules(); | |
56 | 56 | |
57 | 57 | /** |
58 | 58 | * Get the rules that apply to polygons. |
59 | 59 | */ |
60 | public Rule getPolygonRules(); | |
60 | Rule getPolygonRules(); | |
61 | 61 | |
62 | 62 | /** |
63 | 63 | * Get the relation rules. |
64 | 64 | */ |
65 | public Rule getRelationRules(); | |
65 | Rule getRelationRules(); | |
66 | 66 | |
67 | 67 | /** |
68 | 68 | * Get the overlay definitions. Most styles will not use this. |
69 | 69 | */ |
70 | public LineAdder getOverlays(LineAdder lineAdder); | |
70 | LineAdder getOverlays(LineAdder lineAdder); | |
71 | 71 | |
72 | 72 | /** |
73 | 73 | * Get the tags that are used by this style. |
74 | 74 | */ |
75 | public Set<String> getUsedTags(); | |
75 | Set<String> getUsedTags(); | |
76 | ||
77 | /** | |
78 | * Get the tags that are used in the points file of this style | |
79 | */ | |
80 | Set<String> getUsedTagsPOI(); | |
76 | 81 | |
77 | 82 | /** |
78 | 83 | * Report statistics for rule expressions. |
79 | 84 | */ |
80 | public void reportStats(); | |
85 | void reportStats(); | |
81 | 86 | } |
29 | 29 | private String version; |
30 | 30 | private String summary; |
31 | 31 | private String longDescription; |
32 | private final List<String> baseStyleNames = new ArrayList<String>(); | |
32 | private final List<String> baseStyleNames = new ArrayList<>(); | |
33 | 33 | |
34 | 34 | |
35 | 35 | public String getSummary() { |
38 | 38 | private List<Entry<String,String>> areasToPoiNodeTags; |
39 | 39 | |
40 | 40 | @Override |
41 | public boolean init(ElementSaver saver, EnhancedProperties props) { | |
41 | public boolean init(ElementSaver saver, EnhancedProperties props, Style style) { | |
42 | 42 | this.saver = saver; |
43 | 43 | |
44 | 44 | // Get the tags from the POIGeneratorHook which are used to define the point |
62 | 62 | long nodes = saver.getNodes().size(); |
63 | 63 | |
64 | 64 | // go through all nodes |
65 | for (Node node : new ArrayList<Node>(saver.getNodes().values())) { | |
65 | for (Node node : new ArrayList<>(saver.getNodes().values())) { | |
66 | 66 | |
67 | 67 | // nodes without tags can be removed |
68 | 68 | if (node.getTagCount() == 0) { |
95 | 95 | |
96 | 96 | Rectangle bboxRect = new Rectangle(bbox.getMinLong(), bbox.getMinLat(), bbox.getWidth(), bbox.getHeight()); |
97 | 97 | long ways = saver.getWays().size(); |
98 | for (Way way : new ArrayList<Way>(saver.getWays().values())) { | |
98 | for (Way way : new ArrayList<>(saver.getWays().values())) { | |
99 | 99 | if (way.isViaWay()) |
100 | 100 | continue; |
101 | 101 | if (way.getPoints().isEmpty()) { |
35 | 35 | * @author Steve Ratcliffe |
36 | 36 | */ |
37 | 37 | public class OsmBinHandler extends OsmHandler { |
38 | ||
39 | public OsmBinHandler() { | |
40 | } | |
41 | 38 | |
42 | 39 | @Override |
43 | 40 | public boolean isFileSupported(String name) { |
202 | 199 | Element el = null; |
203 | 200 | |
204 | 201 | if (binRel.getTypes(j) == Osmformat.Relation.MemberType.NODE) { |
205 | el = saver.getNode(mid); | |
206 | if(el == null) { | |
207 | // we didn't make a node for this point earlier, | |
208 | // do it now (if it exists) | |
209 | Coord co = saver.getCoord(mid); | |
210 | if(co != null) { | |
211 | el = new Node(mid, co); | |
212 | saver.addNode((Node)el); | |
213 | } | |
214 | } | |
202 | el = saver.getOrCreateNode(mid); | |
215 | 203 | } else if (binRel.getTypes(j) == Osmformat.Relation.MemberType.WAY) { |
216 | 204 | el = saver.getWay(mid); |
217 | 205 | } else if (binRel.getTypes(j) == Osmformat.Relation.MemberType.RELATION) { |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.mkgmap.reader.osm.boundary; |
13 | 13 | |
14 | import uk.me.parabola.imgfmt.app.Area; | |
15 | import uk.me.parabola.mkgmap.reader.osm.Node; | |
16 | 14 | import uk.me.parabola.mkgmap.reader.osm.OsmConverter; |
17 | 15 | import uk.me.parabola.mkgmap.reader.osm.Relation; |
18 | 16 | import uk.me.parabola.mkgmap.reader.osm.Way; |
21 | 19 | public class BoundaryConverter implements OsmConverter { |
22 | 20 | |
23 | 21 | private final BoundarySaver saver; |
22 | ||
24 | 23 | public BoundaryConverter(BoundarySaver saver) { |
25 | 24 | this.saver= saver; |
26 | 25 | } |
33 | 32 | } |
34 | 33 | |
35 | 34 | @Override |
36 | public void convertNode(Node node) { | |
37 | } | |
38 | ||
39 | @Override | |
40 | 35 | public void convertRelation(Relation relation) { |
41 | 36 | if (relation instanceof BoundaryRelation) { |
42 | 37 | Boundary boundary = ((BoundaryRelation)relation).getBoundary(); |
45 | 40 | } |
46 | 41 | } |
47 | 42 | |
48 | @Override | |
49 | public void setBoundingBox(Area bbox) { | |
50 | ||
51 | } | |
52 | ||
53 | @Override | |
54 | public void end() { | |
55 | } | |
56 | ||
57 | @Override | |
58 | public Boolean getDriveOnLeft(){ | |
59 | return null; // unknown | |
60 | } | |
61 | ||
62 | 43 | } |
20 | 20 | import java.util.ListIterator; |
21 | 21 | import java.util.Queue; |
22 | 22 | import java.util.Set; |
23 | import java.util.concurrent.Callable; | |
24 | 23 | import java.util.concurrent.ExecutionException; |
25 | 24 | import java.util.concurrent.ExecutorCompletionService; |
26 | 25 | import java.util.concurrent.ExecutorService; |
46 | 45 | } |
47 | 46 | |
48 | 47 | private static void saveArea(String attribute, Integer level, Area covered) { |
49 | String gpxBasename = "gpx/summary/" + attribute + "/admin_level=" | |
50 | + level; | |
48 | String gpxBasename = "gpx/summary/" + attribute + "/admin_level=" + level; | |
51 | 49 | |
52 | 50 | List<List<Coord>> coveredPolys = Java2DConverter.areaToShapes(covered); |
53 | 51 | Collections.reverse(coveredPolys); |
62 | 60 | public static void main(String[] args) throws InterruptedException { |
63 | 61 | int processors = Runtime.getRuntime().availableProcessors(); |
64 | 62 | ExecutorService excSvc = Executors.newFixedThreadPool(processors); |
65 | ExecutorCompletionService<Area> executor = new ExecutorCompletionService<Area>( | |
66 | excSvc); | |
63 | ExecutorCompletionService<Area> executor = new ExecutorCompletionService<>(excSvc); | |
67 | 64 | String workDirName = args[0]; |
68 | 65 | System.out.println(workDirName); |
69 | 66 | File boundaryDir = new File(workDirName); |
70 | final Set<String> boundsFileNames = new HashSet<String>(); | |
67 | final Set<String> boundsFileNames = new HashSet<>(); | |
71 | 68 | if (boundaryDir.isFile() && boundaryDir.getName().endsWith(".bnd")) { |
72 | 69 | workDirName = boundaryDir.getParent(); |
73 | 70 | if (workDirName == null) |
85 | 82 | for (String fileName : boundsFileNames) { |
86 | 83 | String[] parts = fileName.substring("bounds_".length(), |
87 | 84 | fileName.length() - 4).split("_"); |
88 | int lat = Integer.valueOf(parts[0]); | |
89 | int lon = Integer.valueOf(parts[1]); | |
85 | int lat = Integer.parseInt(parts[0]); | |
86 | int lon = Integer.parseInt(parts[1]); | |
90 | 87 | if (lat < minLat) |
91 | 88 | minLat = lat; |
92 | 89 | if (lat > maxLat) |
96 | 93 | if (lon > maxLon) |
97 | 94 | maxLon = lon; |
98 | 95 | } |
99 | System.out.format("Covered area: (%d,%d)-(%d,%d)\n", minLat, minLon, | |
100 | maxLat, maxLon); | |
96 | System.out.format("Covered area: (%d,%d)-(%d,%d)%n", minLat, minLon, maxLat, maxLon); | |
101 | 97 | int maxSteps = 2; |
102 | 98 | |
103 | 99 | final String boundaryDirName = workDirName; |
104 | 100 | for (int adminlevel = 2; adminlevel < 12; adminlevel++) { |
105 | final Set<String> boundaryFileNames = Collections.synchronizedSet(new HashSet<String>(boundsFileNames)); | |
101 | final Set<String> boundaryFileNames = Collections.synchronizedSet(new HashSet<>(boundsFileNames)); | |
106 | 102 | final int adminLevel = adminlevel; |
107 | final Queue<Future<Area>> areas = new LinkedBlockingQueue<Future<Area>>(); | |
108 | for (int lat = minLat; lat <= maxLat; lat += maxSteps | |
109 | * BoundaryUtil.RASTER) { | |
110 | for (int lon = minLon; lon <= maxLon; lon += maxSteps | |
111 | * BoundaryUtil.RASTER) { | |
103 | final Queue<Future<Area>> areas = new LinkedBlockingQueue<>(); | |
104 | for (int lat = minLat; lat <= maxLat; lat += maxSteps * BoundaryUtil.RASTER) { | |
105 | for (int lon = minLon; lon <= maxLon; lon += maxSteps * BoundaryUtil.RASTER) { | |
112 | 106 | for (int latStep = 0; latStep < maxSteps |
113 | 107 | && lat + latStep * BoundaryUtil.RASTER <= maxLat; latStep++) { |
114 | 108 | for (int lonStep = 0; lonStep < maxSteps |
115 | 109 | && lon + lonStep * BoundaryUtil.RASTER <= maxLon; lonStep++) { |
116 | final int fLat = lat + latStep | |
117 | * BoundaryUtil.RASTER; | |
118 | final int fLon = lon + lonStep | |
119 | * BoundaryUtil.RASTER; | |
110 | final int fLat = lat + latStep * BoundaryUtil.RASTER; | |
111 | final int fLon = lon + lonStep * BoundaryUtil.RASTER; | |
120 | 112 | |
121 | areas.add(executor.submit(new Callable<Area>() { | |
122 | public Area call() { | |
123 | String filename = "bounds_" + fLat + "_" | |
124 | + fLon + ".bnd"; | |
125 | if (boundaryFileNames.contains(filename) == false) { | |
126 | return new Area(); | |
127 | } | |
128 | BoundaryCoverageUtil converter = new BoundaryCoverageUtil( | |
129 | boundaryDirName, filename); | |
130 | boundaryFileNames.remove(filename); | |
131 | System.out.format("%5d bounds files remaining\n", boundaryFileNames.size()); | |
132 | return converter.getCoveredArea(adminLevel); | |
113 | areas.add(executor.submit(() -> { | |
114 | String filename = "bounds_" + fLat + "_" + fLon + ".bnd"; | |
115 | if (!boundaryFileNames.contains(filename)) { | |
116 | return new Area(); | |
133 | 117 | } |
118 | BoundaryCoverageUtil converter = new BoundaryCoverageUtil(boundaryDirName, filename); | |
119 | boundaryFileNames.remove(filename); | |
120 | System.out.format("%5d bounds files remaining%n", boundaryFileNames.size()); | |
121 | return converter.getCoveredArea(adminLevel); | |
134 | 122 | })); |
135 | 123 | } |
136 | 124 | } |
139 | 127 | |
140 | 128 | final AtomicInteger mergeSteps = new AtomicInteger(); |
141 | 129 | while (areas.size() > 1) { |
142 | final List<Future<Area>> toMerge = new ArrayList<Future<Area>>(); | |
143 | for (int i = 0; i < maxSteps * 2 && areas.isEmpty() == false; i++) { | |
130 | final List<Future<Area>> toMerge = new ArrayList<>(); | |
131 | for (int i = 0; i < maxSteps * 2 && !areas.isEmpty(); i++) { | |
144 | 132 | toMerge.add(areas.poll()); |
145 | 133 | } |
146 | 134 | mergeSteps.incrementAndGet(); |
147 | areas.add(executor.submit(new Callable<Area>() { | |
148 | public Area call() { | |
149 | Area a = new Area(); | |
150 | ListIterator<Future<Area>> mergeAreas = toMerge | |
151 | .listIterator(); | |
152 | while (mergeAreas.hasNext()) { | |
153 | try { | |
154 | a.add(mergeAreas.next().get()); | |
155 | } catch (InterruptedException exp) { | |
156 | System.err.println(exp); | |
157 | } catch (ExecutionException exp) { | |
158 | System.err.println(exp); | |
159 | } | |
160 | mergeAreas.remove(); | |
135 | areas.add(executor.submit(() -> { | |
136 | Area a = new Area(); | |
137 | ListIterator<Future<Area>> mergeAreas = toMerge.listIterator(); | |
138 | while (mergeAreas.hasNext()) { | |
139 | try { | |
140 | a.add(mergeAreas.next().get()); | |
141 | } catch (InterruptedException exp1) { | |
142 | System.err.println(exp1); | |
143 | } catch (ExecutionException exp2) { | |
144 | System.err.println(exp2); | |
161 | 145 | } |
162 | System.out.format("%5d merges remaining\n",mergeSteps.decrementAndGet()); | |
163 | return a; | |
146 | mergeAreas.remove(); | |
164 | 147 | } |
148 | System.out.format("%5d merges remaining%n",mergeSteps.decrementAndGet()); | |
149 | return a; | |
165 | 150 | })); |
166 | 151 | } |
167 | 152 | try { |
171 | 156 | } catch (Exception exp) { |
172 | 157 | System.err.println(exp); |
173 | 158 | } |
174 | // } | |
175 | 159 | } |
176 | 160 | excSvc.shutdown(); |
177 | 161 | } |
94 | 94 | int bAll = bounds1.size() + bounds2.size(); |
95 | 95 | long tProgress = System.currentTimeMillis(); |
96 | 96 | |
97 | while (bounds1.isEmpty() == false || bounds2.isEmpty() == false) { | |
97 | while (!bounds1.isEmpty() || !bounds2.isEmpty()) { | |
98 | 98 | String f1 = bounds1.peek(); |
99 | 99 | String f2 = bounds2.peek(); |
100 | 100 | |
109 | 109 | if (cmp == 0) { |
110 | 110 | Area a1 = loadArea(inputName1, f1, tag, value); |
111 | 111 | Area a2 = loadArea(inputName2, f2, tag, value); |
112 | if (a1.isEmpty() == false|| a2.isEmpty() == false){ | |
112 | if (!a1.isEmpty() || !a2.isEmpty()){ | |
113 | 113 | Area o1 = new Area(a1); |
114 | 114 | o1.subtract(a2); |
115 | if (o1.isEmpty() == false) | |
115 | if (!o1.isEmpty()) | |
116 | 116 | only1.add(o1); |
117 | 117 | Area o2 = new Area(a2); |
118 | 118 | o2.subtract(a1); |
119 | if (o2.isEmpty() == false) | |
119 | if (!o2.isEmpty()) | |
120 | 120 | only2.add(o2); |
121 | 121 | } |
122 | 122 | bounds1.poll(); |
164 | 164 | dir = "."; // the local directory |
165 | 165 | } |
166 | 166 | BoundaryQuadTree bqt = BoundaryUtil.loadQuadTree(dir, bndFileName); |
167 | if (tag.equals("admin_level")) | |
167 | if ("admin_level".equals(tag)) | |
168 | 168 | return (bqt.getCoveredArea(Integer.valueOf(value))); |
169 | 169 | Map<String, Tags> bTags = bqt.getTagsMap(); |
170 | 170 | Map<String, List<Area>> areas = bqt.getAreas(); |
228 | 228 | if (args.length < 2) |
229 | 229 | printUsage(); |
230 | 230 | File f1 = new File(args[0]); |
231 | if (f1.exists() == false){ | |
231 | if (!f1.exists()) { | |
232 | 232 | System.err.println(args[0] + " does not exist"); |
233 | 233 | printUsage(); |
234 | 234 | } |
235 | 235 | File f2 = new File(args[1]); |
236 | if (f2.exists() == false){ | |
236 | if (!f2.exists()) { | |
237 | 237 | System.err.println(args[1] + " does not exist"); |
238 | 238 | printUsage(); |
239 | 239 | } |
25 | 25 | |
26 | 26 | public BoundaryElement(boolean outer, List<Coord> points) { |
27 | 27 | this.outer = outer; |
28 | this.points = new ArrayList<Coord>(points); | |
28 | this.points = new ArrayList<>(points); | |
29 | 29 | } |
30 | 30 | |
31 | 31 | public Area getArea() { |
55 | 55 | } else if (element instanceof Way) { |
56 | 56 | Way w = (Way) element; |
57 | 57 | // a single way must be closed |
58 | if (w.isClosedInOSM() == false) { | |
58 | if (!w.isClosedInOSM()) { | |
59 | 59 | return false; |
60 | 60 | } |
61 | 61 | } else { |
101 | 101 | break; |
102 | 102 | } |
103 | 103 | } |
104 | if (found == false){ | |
104 | if (!found) { | |
105 | 105 | System.out.println("No boundary with admin_level=" + adminlevel |
106 | 106 | + " found."); |
107 | 107 | continue; |
156 | 156 | boundaryDirName = boundaryDir.getParent(); |
157 | 157 | if (boundaryDirName == null) |
158 | 158 | boundaryDirName = "."; |
159 | boundaryFileNames = new ArrayList<String>(); | |
159 | boundaryFileNames = new ArrayList<>(); | |
160 | 160 | boundaryFileNames.add(boundaryDir.getName()); |
161 | 161 | } else { |
162 | 162 | boundaryFileNames = BoundaryUtil.getBoundaryDirContent(boundaryDirName); |
77 | 77 | int gridLat = (co.getLatitude() - minLat) / BoundaryUtil.RASTER; |
78 | 78 | int gridLon = (co.getLongitude() - minLon) / BoundaryUtil.RASTER; |
79 | 79 | if (grid[gridLat][gridLon] == null){ |
80 | if (emptyMessagePrinted[gridLat][gridLon] == false){ | |
80 | if (!emptyMessagePrinted[gridLat][gridLon]) { | |
81 | 81 | emptyMessagePrinted[gridLat][gridLon] = true; |
82 | 82 | int keyLat = BoundaryUtil.getSplitBegin(co.getLatitude()); |
83 | 83 | int keyLon = BoundaryUtil.getSplitBegin(co.getLongitude()); |
24 | 24 | File outDir = new File (outDirName); |
25 | 25 | |
26 | 26 | if (outDir.exists() ){ |
27 | if (outDir.isDirectory() == false){ | |
27 | if (!outDir.isDirectory()) { | |
28 | 28 | System.err.println("target is not a directory, output is written to bounds.txt"); |
29 | 29 | outDir = new File("."); |
30 | 30 | } |
41 | 41 | break; |
42 | 42 | Map<String, Tags> map = bqt.getTagsMap(); |
43 | 43 | for ( Entry<String, Tags> entry: map.entrySet()) { |
44 | TreeMap<String,String> btree = new TreeMap<String, String>(); | |
44 | TreeMap<String,String> btree = new TreeMap<>(); | |
45 | 45 | String line = bndFile+ ":" + entry.getKey(); |
46 | 46 | Iterator<Entry<String,String>> tagIter = entry.getValue().entryIterator(); |
47 | 47 | while (tagIter.hasNext()) { |
13 | 13 | |
14 | 14 | import java.util.HashMap; |
15 | 15 | import java.util.List; |
16 | import java.util.Map; | |
16 | 17 | import java.util.regex.Pattern; |
17 | 18 | |
18 | 19 | import uk.me.parabola.log.Logger; |
21 | 22 | import uk.me.parabola.mkgmap.reader.osm.Element; |
22 | 23 | import uk.me.parabola.mkgmap.reader.osm.TagDict; |
23 | 24 | import uk.me.parabola.mkgmap.reader.osm.Tags; |
24 | import uk.me.parabola.mkgmap.reader.osm.boundary.Boundary; | |
25 | 25 | import uk.me.parabola.util.EnhancedProperties; |
26 | 26 | |
27 | 27 | /** |
83 | 83 | * @param boundaries list of boundaries |
84 | 84 | * @return A Map that maps boundary Ids to the location relevant tags |
85 | 85 | */ |
86 | public HashMap<String, BoundaryLocationInfo> getPreparedLocationInfo( | |
86 | public Map<String, BoundaryLocationInfo> getPreparedLocationInfo( | |
87 | 87 | List<Boundary> boundaries) { |
88 | HashMap<String, BoundaryLocationInfo> preparedLocationInfo = new HashMap<String, BoundaryLocationInfo> (); | |
88 | HashMap<String, BoundaryLocationInfo> preparedLocationInfo = new HashMap<> (); | |
89 | 89 | if (boundaries != null){ |
90 | 90 | for (Boundary b :boundaries){ |
91 | 91 | preparedLocationInfo.put(b.getId(), parseTags(b.getTags())); |
100 | 100 | * @return a name or null if no usable name tag was found |
101 | 101 | */ |
102 | 102 | private String getName(Tags tags) { |
103 | if ("2".equals(tags.get(admin_levelTagKey))) { | |
103 | if ("2".equals(tags.get(TK_ADMIN_LEVEL))) { | |
104 | 104 | // admin_level=2 boundaries. They need to be handled special because their name is changed |
105 | 105 | // to the 3 letter ISO code using the Locator class and the LocatorConfig.xml file. |
106 | 106 | for (short nameTagKey : Locator.PREFERRED_NAME_TAG_KEYS) { |
123 | 123 | return null; |
124 | 124 | } |
125 | 125 | |
126 | private static final short postal_codeTagKey = TagDict.getInstance().xlate("postal_code"); | |
127 | private static final short boundaryTagKey = TagDict.getInstance().xlate("boundary"); | |
126 | private static final short TK_POSTAL_CODE = TagDict.getInstance().xlate("postal_code"); | |
127 | private static final short TK_BOUNDARY = TagDict.getInstance().xlate("boundary"); | |
128 | 128 | /** |
129 | 129 | * Try to extract a zip code from the the tags of a boundary. |
130 | 130 | * @param tags the boundary tags |
131 | 131 | * @return null if no zip code was found, else a String that should be a zip code. |
132 | 132 | */ |
133 | 133 | private String getZip(Tags tags) { |
134 | String zip = tags.get(postal_codeTagKey); | |
135 | if (zip == null) { | |
136 | if ("postal_code".equals(tags.get(boundaryTagKey))){ | |
137 | // unlikely | |
138 | String name = tags.get("name"); | |
139 | if (name == null) { | |
140 | name = getName(tags); | |
141 | } | |
142 | if (name != null) { | |
143 | String[] nameParts = name.split(Pattern.quote(" ")); | |
144 | if (nameParts.length > 0) { | |
145 | zip = nameParts[0].trim(); | |
146 | } | |
134 | String zip = tags.get(TK_POSTAL_CODE); | |
135 | if (zip == null && "postal_code".equals(tags.get(TK_BOUNDARY))){ | |
136 | // unlikely | |
137 | String name = tags.get("name"); | |
138 | if (name == null) { | |
139 | name = getName(tags); | |
140 | } | |
141 | if (name != null) { | |
142 | String[] nameParts = name.split(Pattern.quote(" ")); | |
143 | if (nameParts.length > 0) { | |
144 | zip = nameParts[0].trim(); | |
147 | 145 | } |
148 | 146 | } |
149 | 147 | } |
151 | 149 | } |
152 | 150 | |
153 | 151 | public static final int UNSET_ADMIN_LEVEL = 100; // must be higher than real levels |
154 | private static final short admin_levelTagKey = TagDict.getInstance().xlate("admin_level"); | |
152 | private static final short TK_ADMIN_LEVEL = TagDict.getInstance().xlate("admin_level"); | |
155 | 153 | /** |
156 | 154 | * translate the admin_level tag to an integer. |
157 | 155 | * @param tags the boundary tags |
159 | 157 | * the conversion failed. |
160 | 158 | */ |
161 | 159 | private static int getAdminLevel(Tags tags) { |
162 | if ("administrative".equals(tags.get(boundaryTagKey))) { | |
163 | String level = tags.get(admin_levelTagKey); | |
160 | if ("administrative".equals(tags.get(TK_BOUNDARY))) { | |
161 | String level = tags.get(TK_ADMIN_LEVEL); | |
164 | 162 | if (level != null) { |
165 | 163 | try { |
166 | 164 | int res = Integer.parseInt(level); |
41 | 41 | try (FileChannel in = (new FileInputStream(file)).getChannel(); |
42 | 42 | FileChannel out = (new FileOutputStream(new File(to, filename))).getChannel()) { |
43 | 43 | in.transferTo(0, file.length(), out); |
44 | in.close(); | |
45 | out.close(); | |
46 | 44 | } catch (IOException exp) { |
47 | 45 | System.err.println(exp); |
48 | 46 | } |
61 | 59 | BoundarySaver bSave = new BoundarySaver(targetDir, BoundarySaver.QUADTREE_DATA_FORMAT); |
62 | 60 | bSave.setCreateEmptyFiles(false); |
63 | 61 | |
64 | List<File> copy = new ArrayList<File>(); | |
62 | List<File> copy = new ArrayList<>(); | |
65 | 63 | |
66 | 64 | int processed = 0; |
67 | 65 | int all = fl1.size()+fl2.size(); |
129 | 127 | File merge = new File(args[2]); |
130 | 128 | |
131 | 129 | // TODO: maybe allow zip as input |
132 | if (b1.exists() == false || b1.isDirectory() == false) { | |
130 | if (!b1.exists() || !b1.isDirectory()) { | |
133 | 131 | System.err.println(b1 + " does not exist or is not a directory"); |
134 | 132 | return; |
135 | 133 | } |
136 | 134 | |
137 | if (b2.exists() == false || b2.isDirectory() == false) { | |
135 | if (!b2.exists() || !b2.isDirectory()) { | |
138 | 136 | System.err.println(b2 + " does not exist or is not a directory"); |
139 | 137 | return; |
140 | 138 | } |
141 | 139 | |
142 | if (merge.exists() && merge.isDirectory() == false) { | |
140 | if (merge.exists() && !merge.isDirectory()) { | |
143 | 141 | System.err.println(merge + " is not a directory"); |
144 | 142 | } |
145 | 143 | |
146 | if (merge.exists() == false) { | |
144 | if (!merge.exists()) { | |
147 | 145 | merge.mkdirs(); |
148 | 146 | } |
149 | 147 |
109 | 109 | |
110 | 110 | |
111 | 111 | public static void main(String[] args) { |
112 | if (args[0].equals("--help") || args.length != 2) { | |
112 | if ("--help".equals(args[0]) || args.length != 2) { | |
113 | 113 | System.err.println("Usage:"); |
114 | 114 | System.err.println("java -cp mkgmap.jar uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryPreprocessor <inputfile> <boundsdir>"); |
115 | 115 | System.err.println(" <inputfile>: File containing boundary data (OSM, PBF or O5M format)"); |
62 | 62 | |
63 | 63 | // maps the "normal" tags of the boundaries that are saved in this tree to |
64 | 64 | // the boundaryId |
65 | private final HashMap<String, Tags> boundaryTags = new LinkedHashMap<>(); | |
65 | private final Map<String, Tags> boundaryTags = new LinkedHashMap<>(); | |
66 | 66 | // maps the location relevant info to the boundaryId |
67 | private final HashMap<String, BoundaryLocationInfo> preparedLocationInfo; | |
67 | private final Map<String, BoundaryLocationInfo> preparedLocationInfo; | |
68 | 68 | // property controlled preparer |
69 | 69 | private final BoundaryLocationPreparer preparer; |
70 | 70 |
141 | 141 | return; |
142 | 142 | } |
143 | 143 | |
144 | Queue<PolygonStatus> polygonWorkingQueue = new LinkedBlockingQueue<PolygonStatus>(); | |
144 | Queue<PolygonStatus> polygonWorkingQueue = new LinkedBlockingQueue<>(); | |
145 | 145 | BitSet nestedOuterPolygons = new BitSet(); |
146 | 146 | BitSet nestedInnerPolygons = new BitSet(); |
147 | 147 | |
255 | 255 | } |
256 | 256 | outmostPolygonProcessing = false; |
257 | 257 | } else { |
258 | for (String tag : new ArrayList<String>(outerTags.keySet())) { | |
259 | if (outerTags.get(tag).equals(outerWay.getTag(tag)) == false) { | |
258 | for (String tag : new ArrayList<>(outerTags.keySet())) { | |
259 | if (!outerTags.get(tag).equals(outerWay.getTag(tag))) { | |
260 | 260 | outerTags.remove(tag); |
261 | 261 | } |
262 | 262 | } |
311 | 311 | List<JoinedWay> unclosed = new ArrayList<>(); |
312 | 312 | |
313 | 313 | for (JoinedWay w : allWays) { |
314 | if (w.hasIdenticalEndPoints() == false) { | |
314 | if (!w.hasIdenticalEndPoints()) { | |
315 | 315 | unclosed.add(w); |
316 | 316 | } |
317 | 317 | } |
89 | 89 | |
90 | 90 | public BoundarySaver(File boundaryDir, String mode) { |
91 | 91 | this.boundaryDir = boundaryDir; |
92 | if (boundaryDir.exists() && boundaryDir.isDirectory() == false){ | |
92 | if (boundaryDir.exists() && !boundaryDir.isDirectory()) { | |
93 | 93 | log.error("output target exists and is not a directory"); |
94 | 94 | System.exit(-1); |
95 | 95 | } |
189 | 189 | } |
190 | 190 | |
191 | 191 | private void openStream(StreamInfo streamInfo, boolean newFile) { |
192 | if (streamInfo.file.getParentFile().exists() == false | |
192 | if (!streamInfo.file.getParentFile().exists() | |
193 | 193 | && streamInfo.file.getParentFile() != null) |
194 | 194 | streamInfo.file.getParentFile().mkdirs(); |
195 | 195 | FileOutputStream fileStream = null; |
248 | 248 | + ".bnd"); |
249 | 249 | streams.put(filekey, stream); |
250 | 250 | openStream(stream, true); |
251 | } else if (stream.isOpen() == false) { | |
251 | } else if (!stream.isOpen()) { | |
252 | 252 | openStream(stream, false); |
253 | 253 | } |
254 | 254 | } |
377 | 377 | * @param area the area (can be non-singular) |
378 | 378 | * @throws IOException |
379 | 379 | */ |
380 | @SuppressWarnings("fallthrough") | |
380 | 381 | public static void writeArea(DataOutputStream dos, Shape area) throws IOException{ |
381 | 382 | double[] res = new double[6]; |
382 | 383 | double[] lastRes = new double[2]; |
106 | 106 | return bElements; |
107 | 107 | |
108 | 108 | // result is not usable if first element is not outer |
109 | if (tryAgain == false){ | |
109 | if (!tryAgain){ | |
110 | 110 | // cannot convert this area |
111 | 111 | log.error(" first element is not outer. "+ bElements.get(0)); |
112 | 112 | |
458 | 458 | // 1st read the mkgmap release the boundary file is created by |
459 | 459 | String mkgmapRel = "?"; |
460 | 460 | String firstId = inpStream.readUTF(); |
461 | if ("BND".equals(firstId) == false){ | |
461 | if (!"BND".equals(firstId)) { | |
462 | 462 | throw new FormatException("Unsupported boundary data type "+firstId); |
463 | 463 | } |
464 | 464 | |
589 | 589 | |
590 | 590 | if ("boundary".equals(type) || "multipolygon".equals(type)) { |
591 | 591 | String boundaryVal = b.getTags().get("boundary"); |
592 | if ("administrative".equals(boundaryVal) == false) | |
592 | if (!"administrative".equals(boundaryVal)) | |
593 | 593 | return false; |
594 | 594 | // for boundary=administrative the admin_level must be set |
595 | 595 | if (b.getTags().get("admin_level") == null) { |
710 | 710 | * @return a map with the divided shapes |
711 | 711 | */ |
712 | 712 | public static Map<String, Shape> rasterArea(Area areaToSplit) { |
713 | return rasterShape(areaToSplit, new HashMap<String, Shape>()); | |
713 | return rasterShape(areaToSplit, new HashMap<>()); | |
714 | 714 | } |
715 | 715 | |
716 | 716 | /** |
274 | 274 | lastRef[refType] += deltaRef; |
275 | 275 | long memId = lastRef[refType]; |
276 | 276 | if (refType == 0) { |
277 | el = saver.getNode(memId); | |
278 | if (el == null) { | |
279 | // we didn't make a node for this point earlier, | |
280 | // do it now (if it exists) | |
281 | Coord co = saver.getCoord(memId); | |
282 | if (co != null) { | |
283 | el = new Node(memId, co); | |
284 | saver.addNode((Node) el); | |
285 | } | |
286 | } | |
277 | el = saver.getOrCreateNode(memId); | |
287 | 278 | } else if (refType == 1) { |
288 | 279 | el = saver.getWay(memId); |
289 | 280 | } else if (refType == 2) { |
281 | 281 | if ("way".equals(type)){ |
282 | 282 | el = saver.getWay(id); |
283 | 283 | } else if ("node".equals(type)) { |
284 | el = saver.getNode(id); | |
285 | if(el == null) { | |
286 | // we didn't make a node for this point earlier, | |
287 | // do it now (if it exists) | |
288 | Coord co = saver.getCoord(id); | |
289 | if(co != null) { | |
290 | el = new Node(id, co); | |
291 | saver.addNode((Node)el); | |
292 | } | |
293 | } | |
284 | el = saver.getOrCreateNode(id); | |
294 | 285 | } else if ("relation".equals(type)) { |
295 | 286 | el = saver.getRelation(id); |
296 | 287 | if (el == null) { |
45 | 45 | { |
46 | 46 | private static final Logger log = Logger.getLogger(OverviewMapDataSource.class); |
47 | 47 | |
48 | private final List<String> copyrights = new ArrayList<String>(); | |
48 | private final List<String> copyrights = new ArrayList<>(); | |
49 | 49 | LevelInfo[] levels = null; |
50 | 50 | private Path2D tileAreaPath = new Path2D.Double(); |
51 | 51 |
614 | 614 | } |
615 | 615 | |
616 | 616 | private void addLineString (String value, boolean close) { |
617 | List<List<Coord>> lists = lineStringMap.get(currentLevel); | |
618 | if (lists == null) { | |
619 | lists = new ArrayList<>(); | |
620 | lineStringMap.put(currentLevel, lists); | |
621 | } | |
622 | lists.add(coordsFromString(value, close)); | |
617 | lineStringMap.computeIfAbsent(currentLevel, k -> new ArrayList<>()).add(coordsFromString(value, close)); | |
623 | 618 | } |
624 | 619 | |
625 | 620 | private boolean isCommonValue(MapElement elem, String name, String value) { |
115 | 115 | shape.setMinResolution(10); |
116 | 116 | shape.setName("background"); |
117 | 117 | |
118 | List<Coord> coords = new ArrayList<Coord>(); | |
118 | List<Coord> coords = new ArrayList<>(); | |
119 | 119 | |
120 | 120 | Coord co = new Coord(startLat, startLong); |
121 | 121 | coords.add(co); |
209 | 209 | |
210 | 210 | double baseLat = lat + y * ELEMENT_SPACING; |
211 | 211 | double baseLong = lon + x * ELEMENT_SPACING; |
212 | List<Coord> coords = new ArrayList<Coord>(); | |
212 | List<Coord> coords = new ArrayList<>(); | |
213 | 213 | |
214 | 214 | Coord co = new Coord(baseLat, baseLong); |
215 | 215 | coords.add(co); |
253 | 253 | double baseLat = lat + y * ELEMENT_SPACING; |
254 | 254 | double baseLong = lon + x * ELEMENT_SPACING; |
255 | 255 | |
256 | List<Coord> coords = new ArrayList<Coord>(); | |
256 | List<Coord> coords = new ArrayList<>(); | |
257 | 257 | |
258 | 258 | Coord co = new Coord(baseLat, baseLong); |
259 | 259 | //pg.addCoord(co); |
111 | 111 | double baseLat = startLat + y * ELEMENT_SPACING; |
112 | 112 | double baseLong = startLong + x * ELEMENT_SPACING; |
113 | 113 | |
114 | List<Coord> coords = new ArrayList<Coord>(); | |
114 | List<Coord> coords = new ArrayList<>(); | |
115 | 115 | |
116 | 116 | for (int i = 0; i < 5; i++) { |
117 | 117 | Coord co = new Coord(baseLat + i * ELEMENT_SIZE, baseLong + i * ELEMENT_SIZE); |
207 | 207 | continue; |
208 | 208 | } |
209 | 209 | w.deleteTag(MultiPolygonRelation.STYLE_FILTER_TAG); |
210 | w.deleteTag(MultiPolygonRelation.MP_CREATED_TAG_KEY); | |
210 | w.deleteTag(MultiPolygonRelation.TKM_MP_CREATED); | |
211 | 211 | ways.add(w); |
212 | 212 | } |
213 | 213 | } |
215 | 215 | |
216 | 216 | try { |
217 | 217 | // forward the ways to the queue of the saver thread |
218 | saveQueue.put(new SimpleEntry<String, List<Way>>(mergeData.getKey(), ways)); | |
218 | saveQueue.put(new SimpleEntry<>(mergeData.getKey(), ways)); | |
219 | 219 | } catch (InterruptedException exp) { |
220 | 220 | exp.printStackTrace(); |
221 | 221 | } |
35 | 35 | * @author Steve Ratcliffe |
36 | 36 | */ |
37 | 37 | public class CommonSection { |
38 | private static final Set<String> seen = new HashSet<String>(); | |
38 | private static final Set<String> seen = new HashSet<>(); | |
39 | 39 | protected final TypData data; |
40 | 40 | private boolean hasXpm; |
41 | 41 | |
50 | 50 | * @return True if this routine has processed the tag. |
51 | 51 | */ |
52 | 52 | protected boolean commonKey(TokenScanner scanner, TypElement current, String name, String value) { |
53 | if (name.equalsIgnoreCase("Type")) { | |
53 | if ("Type".equalsIgnoreCase(name)) { | |
54 | 54 | try { |
55 | 55 | int ival = Integer.decode(value); |
56 | 56 | if (ival >= 0x100) { |
63 | 63 | throw new SyntaxException(scanner, "Bad number " + value); |
64 | 64 | } |
65 | 65 | |
66 | } else if (name.equalsIgnoreCase("SubType")) { | |
66 | } else if ("SubType".equalsIgnoreCase(name)) { | |
67 | 67 | try { |
68 | 68 | int ival = Integer.decode(value); |
69 | 69 | current.setSubType(ival); |
78 | 78 | throw new SyntaxException(scanner, "Bad number in " + value); |
79 | 79 | } |
80 | 80 | |
81 | } else if (name.equalsIgnoreCase("Xpm")) { | |
81 | } else if ("Xpm".equalsIgnoreCase(name)) { | |
82 | 82 | Xpm xpm = readXpm(scanner, value, current.simpleBitmap()); |
83 | 83 | current.setXpm(xpm); |
84 | 84 | |
85 | } else if (name.equalsIgnoreCase("FontStyle")) { | |
85 | } else if ("FontStyle".equalsIgnoreCase(name)) { | |
86 | 86 | int font = decodeFontStyle(value); |
87 | 87 | current.setFontStyle(font); |
88 | 88 | |
89 | } else if (name.equalsIgnoreCase("CustomColor") || name.equals("ExtendedLabels")) { | |
89 | } else if ("CustomColor".equalsIgnoreCase(name) || "ExtendedLabels".equals(name)) { | |
90 | 90 | // These are just noise, the appropriate flag is set if any feature is used. |
91 | 91 | |
92 | } else if (name.equalsIgnoreCase("DaycustomColor")) { | |
92 | } else if ("DaycustomColor".equalsIgnoreCase(name)) { | |
93 | 93 | current.setDayFontColor(value); |
94 | 94 | |
95 | } else if (name.equalsIgnoreCase("NightcustomColor")) { | |
95 | } else if ("NightcustomColor".equalsIgnoreCase(name)) { | |
96 | 96 | current.setNightCustomColor(value); |
97 | 97 | |
98 | } else if (name.equalsIgnoreCase("Comment")) { | |
98 | } else if ("Comment".equalsIgnoreCase(name)) { | |
99 | 99 | // a comment that is ignored. |
100 | 100 | } else { |
101 | 101 | return false; |
105 | 105 | } |
106 | 106 | |
107 | 107 | protected int decodeFontStyle(String value) { |
108 | if (value.startsWith("NoLabel") || value.equalsIgnoreCase("nolabel")) { | |
108 | if (value.startsWith("NoLabel") || "nolabel".equalsIgnoreCase(value)) { | |
109 | 109 | return 1; |
110 | } else if (value.equalsIgnoreCase("SmallFont") || value.equalsIgnoreCase("Small")) { | |
110 | } else if ("SmallFont".equalsIgnoreCase(value) || "Small".equalsIgnoreCase(value)) { | |
111 | 111 | return 2; |
112 | } else if (value.equalsIgnoreCase("NormalFont") || value.equalsIgnoreCase("Normal")) { | |
112 | } else if ("NormalFont".equalsIgnoreCase(value) || "Normal".equalsIgnoreCase(value)) { | |
113 | 113 | return 3; |
114 | } else if (value.equalsIgnoreCase("LargeFont") || value.equalsIgnoreCase("Large")) { | |
114 | } else if ("LargeFont".equalsIgnoreCase(value) || "Large".equalsIgnoreCase(value)) { | |
115 | 115 | return 4; |
116 | } else if (value.equalsIgnoreCase("Default")) { | |
116 | } else if ("Default".equalsIgnoreCase(value)) { | |
117 | 117 | return 0; |
118 | 118 | } else { |
119 | 119 | warnUnknown("font value " + value); |
131 | 131 | * @param header The string containing the xpm header and other extended data provided on the |
132 | 132 | * same line. |
133 | 133 | */ |
134 | private void parseXpmHeader(TokenScanner scanner, ColourInfo info, String header) { | |
134 | private static void parseXpmHeader(TokenScanner scanner, ColourInfo info, String header) { | |
135 | 135 | TokenScanner s2 = new TokenScanner("string", new StringReader(header)); |
136 | 136 | |
137 | 137 | if (s2.checkToken("\"")) |
172 | 172 | if (colour.charAt(0) == '#') { |
173 | 173 | colour = scanner.nextValue(); |
174 | 174 | colourInfo.addColour(colourTag, new Rgb(colour)); |
175 | } else if (colour.equalsIgnoreCase("none")) { | |
175 | } else if ("none".equalsIgnoreCase(colour)) { | |
176 | 176 | colourInfo.addTransparent(colourTag); |
177 | 177 | } else { |
178 | 178 | throw new SyntaxException(scanner, "Unrecognised colour: " + colour); |
190 | 190 | * Get any keywords that are on the end of the colour line. Must not step |
191 | 191 | * over the new line boundary. |
192 | 192 | */ |
193 | private void readExtraColourInfo(TokenScanner scanner, AlphaAdder colour) { | |
193 | private static void readExtraColourInfo(TokenScanner scanner, AlphaAdder colour) { | |
194 | 194 | while (!scanner.isEndOfFile()) { |
195 | 195 | Token tok = scanner.nextRawToken(); |
196 | 196 | if (tok.isEol()) |
328 | 328 | * by quotes. The can be trailing attribute that sets the opacity of |
329 | 329 | * the final pixel. |
330 | 330 | */ |
331 | private int readTrueImageLine(TokenScanner scanner, final int[] image, int count) { | |
331 | private static int readTrueImageLine(TokenScanner scanner, final int[] image, int count) { | |
332 | 332 | do { |
333 | 333 | scanner.validateNext("#"); |
334 | 334 | String col = scanner.nextValue(); |
31 | 31 | * There is only one tag in this section. |
32 | 32 | */ |
33 | 33 | public void processLine(TokenScanner scanner, String name, String value) { |
34 | if (!name.equalsIgnoreCase("Type")) | |
34 | if (!"Type".equalsIgnoreCase(name)) | |
35 | 35 | throw new SyntaxException(scanner, "Unrecognised keyword in draw order section: " + name); |
36 | 36 | |
37 | 37 | String[] typeDrawOrder = value.split(",",-1); |
29 | 29 | } |
30 | 30 | |
31 | 31 | public void processLine(TokenScanner scanner, String name, String value) { |
32 | if (name.equalsIgnoreCase("String")) { | |
32 | if ("String".equalsIgnoreCase(name)) { | |
33 | 33 | // There is only one string and it doesn't have a language prefix. |
34 | 34 | // But if it does we will just ignore it. |
35 | 35 | current.addLabel(value); |
39 | 39 | if (commonKey(scanner, current, name, value)) |
40 | 40 | return; |
41 | 41 | |
42 | if (name.equalsIgnoreCase("IconXpm")) { | |
42 | if ("IconXpm".equalsIgnoreCase(name)) { | |
43 | 43 | Xpm xpm = readXpm(scanner, value, current.simpleBitmap()); |
44 | 44 | current.addIcon(xpm); |
45 | 45 | } else { |
47 | 47 | ival = -1; |
48 | 48 | } |
49 | 49 | |
50 | if (name.equalsIgnoreCase("FID")) { | |
50 | if ("FID".equalsIgnoreCase(name)) { | |
51 | 51 | if (ival != -1) |
52 | 52 | data.setFamilyId(ival); |
53 | } else if (name.equalsIgnoreCase("ProductCode")) { | |
53 | } else if ("ProductCode".equalsIgnoreCase(name)) { | |
54 | 54 | if (ival != -1) |
55 | 55 | data.setProductId(ival); |
56 | } else if (name.equalsIgnoreCase("CodePage")) { | |
56 | } else if ("CodePage".equalsIgnoreCase(name)) { | |
57 | 57 | if (ival != -1 && data.getSort() == null) // ignore if --code-page |
58 | 58 | data.setSort(SrtTextReader.sortForCodepage(ival)); |
59 | 59 | } else { |
37 | 37 | if (commonKey(scanner, current, name, value)) |
38 | 38 | return; |
39 | 39 | |
40 | if (name.equalsIgnoreCase("UseOrientation")) { | |
40 | if ("UseOrientation".equalsIgnoreCase(name)) { | |
41 | 41 | current.setUseOrientation(value.charAt(0) == 'Y'); |
42 | } else if (name.equalsIgnoreCase("LineWidth")) { | |
42 | } else if ("LineWidth".equalsIgnoreCase(name)) { | |
43 | 43 | try { |
44 | 44 | int ival = Integer.decode(value); |
45 | 45 | current.setLineWidth(ival); |
46 | 46 | } catch (NumberFormatException e) { |
47 | 47 | throw new SyntaxException(scanner, "Bad number for line width: " + value); |
48 | 48 | } |
49 | } else if (name.equalsIgnoreCase("BorderWidth")) { | |
49 | } else if ("BorderWidth".equalsIgnoreCase(name)) { | |
50 | 50 | try { |
51 | 51 | int ival = Integer.decode(value); |
52 | 52 | current.setBorderWidth(ival); |
33 | 33 | if (commonKey(scanner, current, name, value)) |
34 | 34 | return; |
35 | 35 | |
36 | if (name.equalsIgnoreCase("DayXpm")) { | |
36 | if ("DayXpm".equalsIgnoreCase(name)) { | |
37 | 37 | Xpm xpm = readXpm(scanner, value, current.simpleBitmap()); |
38 | 38 | current.setXpm(xpm); |
39 | 39 | |
40 | } else if (name.equalsIgnoreCase("NightXpm")) { | |
40 | } else if ("NightXpm".equalsIgnoreCase(name)) { | |
41 | 41 | Xpm xpm = readXpm(scanner, value, current.simpleBitmap()); |
42 | 42 | current.setNightXpm(xpm); |
43 | 43 |
72 | 72 | String name = tok.getValue(); |
73 | 73 | |
74 | 74 | String sep = scanner.nextValue(); |
75 | if (!sep.equals("=") && !sep.equals(":")) | |
75 | if (!"=".equals(sep) && !":".equals(sep)) | |
76 | 76 | throw new SyntaxException(scanner, "Expecting '=' or ':' instead of " + sep); |
77 | 77 | |
78 | 78 | String value = scanner.readLine(); |
154 | 154 | * @return the resultList |
155 | 155 | */ |
156 | 156 | public void get(Area bbox, Set<Element> resultSet) { |
157 | if (isEmpty() || bbox.intersects(bounds) == false) { | |
157 | if (isEmpty() || !bbox.intersects(bounds)) { | |
158 | 158 | return; |
159 | 159 | } |
160 | 160 | if (elementList != null) { |
18 | 18 | public static String getGpxBaseName() { |
19 | 19 | String tilePath = (log.threadTag() == null ? "unknown" : log.threadTag()); |
20 | 20 | |
21 | int tilenameStart = tilePath.lastIndexOf("/"); | |
21 | int tilenameStart = tilePath.lastIndexOf('/'); | |
22 | 22 | // check the case if the tiles are defined without path |
23 | 23 | tilenameStart = (tilenameStart < 0 ? 0 : tilenameStart+1); |
24 | 24 | |
141 | 141 | } |
142 | 142 | } |
143 | 143 | |
144 | if (polygonpoints != null && polygonpoints.isEmpty() == false) { | |
144 | if (polygonpoints != null && !polygonpoints.isEmpty()) { | |
145 | 145 | pw.print("<trk><name>"); |
146 | 146 | pw.print(fname); |
147 | 147 | pw.print("</name><trkseg>"); |
12 | 12 | |
13 | 13 | package uk.me.parabola.util; |
14 | 14 | |
15 | import java.util.ArrayList; | |
16 | 15 | import java.util.Collections; |
17 | 16 | import java.util.IdentityHashMap; |
18 | 17 | import java.util.LinkedList; |
24 | 23 | /** |
25 | 24 | * the empty list to be returned when there is key without values. |
26 | 25 | */ |
27 | private final List<V> emptyList = Collections.unmodifiableList(new ArrayList<V>(0)); | |
28 | ||
26 | ||
29 | 27 | /** |
30 | 28 | * Returns the list of values associated with the given key. |
31 | 29 | * |
35 | 33 | */ |
36 | 34 | public List<V> get(Object key) { |
37 | 35 | List<V> result = super.get(key); |
38 | return result == null ? emptyList : result; | |
36 | return result == null ? Collections.emptyList() : result; | |
39 | 37 | } |
40 | 38 | |
41 | 39 | |
42 | 40 | public V add(K key, V value ) { |
43 | 41 | List<V> values = super.get(key); |
44 | 42 | if (values == null ) { |
45 | values = new LinkedList<V>(); | |
43 | values = new LinkedList<>(); | |
46 | 44 | super.put( key, values ); |
47 | 45 | } |
48 | 46 |
38 | 38 | } |
39 | 39 | |
40 | 40 | public List<Coord> get(Area bbox) { |
41 | return root.get(bbox, new ArrayList<Coord>(2000)); | |
41 | return root.get(bbox, new ArrayList<>(2000)); | |
42 | 42 | } |
43 | 43 | |
44 | 44 | public List<Coord> get(Collection<List<Coord>> polygons) { |
45 | return root.get(new QuadTreePolygon(polygons), new ArrayList<Coord>(2000)); | |
45 | return root.get(new QuadTreePolygon(polygons), new ArrayList<>(2000)); | |
46 | 46 | } |
47 | 47 | |
48 | 48 | public List<Coord> get(List<Coord> polygon) { |
56 | 56 | if (!polygon.get(0).equals(polygon.get(polygon.size() - 1))) { |
57 | 57 | throw new IllegalArgumentException("polygon is not closed"); |
58 | 58 | } |
59 | List<Coord> points = root.get(new QuadTreePolygon(polygon), new ArrayList<Coord>(2000)); | |
59 | List<Coord> points = root.get(new QuadTreePolygon(polygon), new ArrayList<>(2000)); | |
60 | 60 | if (offset > 0) { |
61 | 61 | ListIterator<Coord> pointIter = points.listIterator(); |
62 | 62 | while (pointIter.hasNext()) { |
70 | 70 | break; |
71 | 71 | case PathIterator.SEG_CLOSE: |
72 | 72 | Path2D.Double segment = null; |
73 | if (clippingRect.contains(minX, minY) == false || clippingRect.contains(maxX,maxY) == false){ | |
73 | if (!clippingRect.contains(minX, minY) || !clippingRect.contains(maxX,maxY)) { | |
74 | 74 | Rectangle2D.Double bbox = new Rectangle2D.Double(minX,minY,maxX-minX,maxY-minY); |
75 | 75 | segment = clipSinglePathWithSutherlandHodgman (points, num, clippingRect, bbox); |
76 | 76 | } else |
154 | 154 | * @return the clipped path as a Path2D.Double or null if the result is empty |
155 | 155 | */ |
156 | 156 | public static Path2D.Double clipSinglePathWithSutherlandHodgman (double[] points, int num, Rectangle2D clippingRect, Rectangle2D.Double bbox) { |
157 | if (num <= 2 || bbox.intersects(clippingRect) == false){ | |
157 | if (num <= 2 || !bbox.intersects(clippingRect)) { | |
158 | 158 | return null; |
159 | 159 | } |
160 | 160 |
94 | 94 | |
95 | 95 | getInitialBase(); |
96 | 96 | |
97 | List<Numbers> numbers = new ArrayList<Numbers>(); | |
97 | List<Numbers> numbers = new ArrayList<>(); | |
98 | 98 | |
99 | 99 | // To do this properly we need to know the number of nodes I think, this is the |
100 | 100 | // best we can do: if there are more than 8 bits left, there must be another command |
32 | 32 | * @author Steve Ratcliffe |
33 | 33 | */ |
34 | 34 | public class StringStyleFileLoader extends StyleFileLoader { |
35 | private final Map<String, String> files = new HashMap<String, String>(); | |
35 | private final Map<String, String> files = new HashMap<>(); | |
36 | 36 | |
37 | 37 | /** |
38 | 38 | * Pass filename and file contents like so: |
44 | 44 | * @author Steve Ratcliffe |
45 | 45 | */ |
46 | 46 | public class TestUtils { |
47 | private static final List<String> files = new ArrayList<String>(); | |
48 | private static final Deque<Closeable> open = new ArrayDeque<Closeable>(); | |
47 | private static final List<String> files = new ArrayList<>(); | |
48 | private static final Deque<Closeable> open = new ArrayDeque<>(); | |
49 | 49 | |
50 | 50 | static { |
51 | 51 | files.add(Args.DEF_MAP_FILENAME); |
108 | 108 | * @param in The arguments to use. |
109 | 109 | */ |
110 | 110 | public static Outputs run(String ... in) { |
111 | List<String> args = new ArrayList<String>(Arrays.asList(in)); | |
111 | List<String> args = new ArrayList<>(Arrays.asList(in)); | |
112 | 112 | args.add(0, Args.TEST_STYLE_ARG); |
113 | 113 | |
114 | 114 | OutputStream outsink = new ByteArrayOutputStream(); |
44 | 44 | Random rand = new Random(8866028); |
45 | 45 | |
46 | 46 | for (int iter = 0; iter < 1000000; iter++) { |
47 | List<String> sl = new ArrayList<String>(); | |
47 | List<String> sl = new ArrayList<>(); | |
48 | 48 | for (int i = 0; i < 20; i++) { |
49 | 49 | String n; |
50 | 50 | do { |
68 | 68 | } |
69 | 69 | |
70 | 70 | private void run(String[] strings) { |
71 | List<Numbers> numbers = new ArrayList<Numbers>(); | |
71 | List<Numbers> numbers = new ArrayList<>(); | |
72 | 72 | for (String s : strings) { |
73 | 73 | Numbers n = new Numbers(s); |
74 | 74 | n.setIndex(n.getPolishIndex()); |
283 | 283 | return list; |
284 | 284 | } |
285 | 285 | |
286 | private List<Numbers> createList(String[] specs) { | |
286 | private static List<Numbers> createList(String[] specs) { | |
287 | 287 | List<Numbers> numbers = new ArrayList<>(); |
288 | 288 | for (String s : specs) { |
289 | 289 | Numbers n = new Numbers(s); |
293 | 293 | return numbers; |
294 | 294 | } |
295 | 295 | |
296 | private Matcher<Integer> lessThanOrEqual(final int val) { | |
296 | private static Matcher<Integer> lessThanOrEqual(final int val) { | |
297 | 297 | return new BaseMatcher<Integer>() { |
298 | 298 | public boolean matches(Object o) { |
299 | 299 | return (Integer) o <= val; |
22 | 22 | |
23 | 23 | @Test |
24 | 24 | public void testGetObject() { |
25 | IntegerSortKey<String> k1 = new IntegerSortKey<String>(HELLO1, 1, 1); | |
26 | CombinedSortKey<String> ck1 = new CombinedSortKey<String>(k1, 2, 2); | |
25 | IntegerSortKey<String> k1 = new IntegerSortKey<>(HELLO1, 1, 1); | |
26 | CombinedSortKey<String> ck1 = new CombinedSortKey<>(k1, 2, 2); | |
27 | 27 | |
28 | 28 | assertEquals("retrieve original object", HELLO1, ck1.getObject()); |
29 | 29 | } |
30 | 30 | |
31 | 31 | @Test |
32 | 32 | public void testCompletelyEqual() { |
33 | IntegerSortKey<String> k1 = new IntegerSortKey<String>(HELLO1, 1, 1); | |
34 | CombinedSortKey<String> ck1 = new CombinedSortKey<String>(k1, 2, 2); | |
35 | CombinedSortKey<String> ck2 = new CombinedSortKey<String>(k1, 2, 2); | |
33 | IntegerSortKey<String> k1 = new IntegerSortKey<>(HELLO1, 1, 1); | |
34 | CombinedSortKey<String> ck1 = new CombinedSortKey<>(k1, 2, 2); | |
35 | CombinedSortKey<String> ck2 = new CombinedSortKey<>(k1, 2, 2); | |
36 | 36 | |
37 | 37 | assertEquals(0, ck1.compareTo(ck2)); |
38 | 38 | } |
39 | 39 | |
40 | 40 | @Test |
41 | 41 | public void testDifferentKey() { |
42 | IntegerSortKey<String> k1 = new IntegerSortKey<String>(HELLO1, 1, 1); | |
43 | IntegerSortKey<String> k2 = new IntegerSortKey<String>(HELLO1, 1, 2); | |
44 | CombinedSortKey<String> ck1 = new CombinedSortKey<String>(k1, 2, 2); | |
45 | CombinedSortKey<String> ck2 = new CombinedSortKey<String>(k2, 2, 2); | |
42 | IntegerSortKey<String> k1 = new IntegerSortKey<>(HELLO1, 1, 1); | |
43 | IntegerSortKey<String> k2 = new IntegerSortKey<>(HELLO1, 1, 2); | |
44 | CombinedSortKey<String> ck1 = new CombinedSortKey<>(k1, 2, 2); | |
45 | CombinedSortKey<String> ck2 = new CombinedSortKey<>(k2, 2, 2); | |
46 | 46 | |
47 | 47 | assertEquals(-1, k1.compareTo(k2)); |
48 | 48 | |
52 | 52 | |
53 | 53 | @Test |
54 | 54 | public void testDifferentFirst() { |
55 | IntegerSortKey<String> k1 = new IntegerSortKey<String>(HELLO1, 1, 1); | |
56 | IntegerSortKey<String> k2 = new IntegerSortKey<String>(HELLO1, 1, 1); | |
57 | CombinedSortKey<String> ck1 = new CombinedSortKey<String>(k1, 2, 2); | |
58 | CombinedSortKey<String> ck2 = new CombinedSortKey<String>(k2, 3, 2); | |
55 | IntegerSortKey<String> k1 = new IntegerSortKey<>(HELLO1, 1, 1); | |
56 | IntegerSortKey<String> k2 = new IntegerSortKey<>(HELLO1, 1, 1); | |
57 | CombinedSortKey<String> ck1 = new CombinedSortKey<>(k1, 2, 2); | |
58 | CombinedSortKey<String> ck2 = new CombinedSortKey<>(k2, 3, 2); | |
59 | 59 | |
60 | 60 | assertEquals(0, k1.compareTo(k2)); |
61 | 61 | |
65 | 65 | |
66 | 66 | @Test |
67 | 67 | public void testDifferentSecond() { |
68 | IntegerSortKey<String> k1 = new IntegerSortKey<String>(HELLO1, 1, 1); | |
69 | IntegerSortKey<String> k2 = new IntegerSortKey<String>(HELLO1, 1, 1); | |
70 | CombinedSortKey<String> ck1 = new CombinedSortKey<String>(k1, 2, 2); | |
71 | CombinedSortKey<String> ck2 = new CombinedSortKey<String>(k2, 2, 3); | |
68 | IntegerSortKey<String> k1 = new IntegerSortKey<>(HELLO1, 1, 1); | |
69 | IntegerSortKey<String> k2 = new IntegerSortKey<>(HELLO1, 1, 1); | |
70 | CombinedSortKey<String> ck1 = new CombinedSortKey<>(k1, 2, 2); | |
71 | CombinedSortKey<String> ck2 = new CombinedSortKey<>(k2, 2, 3); | |
72 | 72 | |
73 | 73 | assertEquals(0, k1.compareTo(k2)); |
74 | 74 | |
78 | 78 | |
79 | 79 | @Test |
80 | 80 | public void testKeyOverridesFirst() { |
81 | IntegerSortKey<String> k1 = new IntegerSortKey<String>(HELLO1, 1, 1); | |
82 | IntegerSortKey<String> k2 = new IntegerSortKey<String>(HELLO1, 2, 1); | |
83 | CombinedSortKey<String> ck1 = new CombinedSortKey<String>(k1, 3, 2); | |
84 | CombinedSortKey<String> ck2 = new CombinedSortKey<String>(k2, 2, 2); | |
81 | IntegerSortKey<String> k1 = new IntegerSortKey<>(HELLO1, 1, 1); | |
82 | IntegerSortKey<String> k2 = new IntegerSortKey<>(HELLO1, 2, 1); | |
83 | CombinedSortKey<String> ck1 = new CombinedSortKey<>(k1, 3, 2); | |
84 | CombinedSortKey<String> ck2 = new CombinedSortKey<>(k2, 2, 2); | |
85 | 85 | |
86 | 86 | assertEquals(-1, k1.compareTo(k2)); |
87 | 87 | |
91 | 91 | |
92 | 92 | @Test |
93 | 93 | public void testPrimaryOverridesSecond() { |
94 | IntegerSortKey<String> k1 = new IntegerSortKey<String>(HELLO1, 1, 1); | |
95 | IntegerSortKey<String> k2 = new IntegerSortKey<String>(HELLO1, 1, 1); | |
96 | CombinedSortKey<String> ck1 = new CombinedSortKey<String>(k1, 2, 3); | |
97 | CombinedSortKey<String> ck2 = new CombinedSortKey<String>(k2, 3, 2); | |
94 | IntegerSortKey<String> k1 = new IntegerSortKey<>(HELLO1, 1, 1); | |
95 | IntegerSortKey<String> k2 = new IntegerSortKey<>(HELLO1, 1, 1); | |
96 | CombinedSortKey<String> ck1 = new CombinedSortKey<>(k1, 2, 3); | |
97 | CombinedSortKey<String> ck2 = new CombinedSortKey<>(k2, 3, 2); | |
98 | 98 | |
99 | 99 | assertEquals(0, k1.compareTo(k2)); |
100 | 100 |
222 | 222 | } |
223 | 223 | } |
224 | 224 | |
225 | private final List<FileArgs> files = new ArrayList<FileArgs>(); | |
225 | private final List<FileArgs> files = new ArrayList<>(); | |
226 | 226 | |
227 | 227 | public void processOption(String opt, String val) { |
228 | 228 | } |
136 | 136 | @Test |
137 | 137 | public void testAbsoluteFilenamesInFile() { |
138 | 138 | String s, exp_dir; |
139 | if (PATH_SEP.equals("\\")) { | |
139 | if ("\\".equals(PATH_SEP)) { | |
140 | 140 | s = "input-file: c:\\home\\foo\n"; |
141 | 141 | exp_dir = "c:\\home"; |
142 | 142 | } |
27 | 27 | |
28 | 28 | public class ShapeMergeFilterTest { |
29 | 29 | // create one Coord instance for each point in a small test grid |
30 | private static final HashMap<Integer,Coord> map = new HashMap<Integer,Coord>(){ | |
31 | /** | |
32 | * | |
33 | */ | |
34 | private static final long serialVersionUID = 1L; | |
35 | ||
36 | { | |
37 | for (int latHp = 0; latHp < 100; latHp +=5){ | |
38 | for (int lonHp = 0; lonHp < 100; lonHp += 5){ | |
39 | Coord co = Coord.makeHighPrecCoord(latHp, lonHp); | |
40 | put(latHp*1000 + lonHp,co); | |
41 | } | |
30 | private static final HashMap<Integer, Coord> map = new HashMap<>(); | |
31 | static { | |
32 | for (int latHp = 0; latHp < 100; latHp += 5) { | |
33 | for (int lonHp = 0; lonHp < 100; lonHp += 5) { | |
34 | Coord co = Coord.makeHighPrecCoord(latHp, lonHp); | |
35 | map.put(latHp * 1000 + lonHp, co); | |
42 | 36 | } |
43 | 37 | } |
44 | }; | |
38 | ||
39 | } | |
45 | 40 | |
46 | 41 | @Test |
47 | 42 | public void testAreaTestVal(){ |
48 | List<Coord> points = new ArrayList<Coord>(){/** | |
49 | * | |
50 | */ | |
51 | private static final long serialVersionUID = 1L; | |
52 | ||
53 | { | |
54 | add(getPoint(10,10)); | |
55 | add(getPoint(30,10)); | |
56 | add(getPoint(30,30)); | |
57 | add(getPoint(10,30)); | |
58 | add(getPoint(10,10)); // close | |
59 | ||
60 | }}; | |
61 | assertEquals(2 * (20 * 20),ShapeMergeFilter.calcAreaSizeTestVal(points)); | |
43 | List<Coord> points = Arrays.asList( | |
44 | getPoint(10,10), | |
45 | getPoint(30,10), | |
46 | getPoint(30,30), | |
47 | getPoint(10,30), | |
48 | getPoint(10,10)); // close | |
49 | ||
50 | assertEquals(2L * (20 * 20), ShapeMergeFilter.calcAreaSizeTestVal(points)); | |
62 | 51 | } |
63 | 52 | /** |
64 | 53 | * two simple shapes, sharing one point |
65 | 54 | */ |
66 | 55 | @Test |
67 | 56 | public void testSimpleSharingOne(){ |
68 | List<Coord> points1 = new ArrayList<Coord>(){/** | |
69 | * | |
70 | */ | |
71 | private static final long serialVersionUID = 1L; | |
72 | ||
73 | { | |
74 | add(getPoint(15,10)); | |
75 | add(getPoint(30,25)); | |
76 | add(getPoint(25,30)); | |
77 | add(getPoint(10,30)); | |
78 | add(getPoint(5,20)); | |
79 | add(getPoint(15,10)); // close | |
80 | }}; | |
81 | ||
82 | List<Coord> points2 = new ArrayList<Coord>(){/** | |
83 | * | |
84 | */ | |
85 | private static final long serialVersionUID = 1L; | |
86 | ||
87 | { | |
88 | add(getPoint(25,30)); | |
89 | add(getPoint(30,35)); | |
90 | add(getPoint(20,40)); | |
91 | add(getPoint(15,35)); | |
92 | add(getPoint(25,30)); | |
93 | }}; | |
94 | testVariants("simple shapes sharing one point", points1, points2,1,10); | |
57 | List<Coord> points1 = Arrays.asList( | |
58 | getPoint(15,10), | |
59 | getPoint(30,25), | |
60 | getPoint(25,30), | |
61 | getPoint(10,30), | |
62 | getPoint(5,20), | |
63 | getPoint(15,10)); // close | |
64 | ||
65 | List<Coord> points2 = Arrays.asList( | |
66 | getPoint(25,30), | |
67 | getPoint(30,35), | |
68 | getPoint(20,40), | |
69 | getPoint(15,35), | |
70 | getPoint(25,30)); // close | |
71 | ||
72 | testVariants("simple shapes sharing one point", points1, points2, 1, 10); | |
95 | 73 | } |
96 | 74 | |
97 | 75 | /** |
99 | 77 | */ |
100 | 78 | @Test |
101 | 79 | public void testSimpleNonOverlapping(){ |
102 | List<Coord> points1 = new ArrayList<Coord>(){/** | |
103 | * | |
104 | */ | |
105 | private static final long serialVersionUID = 1L; | |
106 | ||
107 | { | |
108 | add(getPoint(15,10)); | |
109 | add(getPoint(30,25)); | |
110 | add(getPoint(25,30)); | |
111 | add(getPoint(15,35)); | |
112 | add(getPoint(5,20)); | |
113 | add(getPoint(15,10)); // close | |
114 | }}; | |
115 | ||
116 | List<Coord> points2 = new ArrayList<Coord>(){/** | |
117 | * | |
118 | */ | |
119 | private static final long serialVersionUID = 1L; | |
120 | ||
121 | { | |
122 | add(getPoint(25,30)); | |
123 | add(getPoint(30,35)); | |
124 | add(getPoint(20,40)); | |
125 | add(getPoint(15,35)); | |
126 | add(getPoint(25,30)); | |
127 | }}; | |
128 | testVariants("simple shapes", points1, points2,1,8); | |
80 | List<Coord> points1 = Arrays.asList( | |
81 | getPoint(15,10), | |
82 | getPoint(30,25), | |
83 | getPoint(25,30), | |
84 | getPoint(15,35), | |
85 | getPoint(5,20), | |
86 | getPoint(15,10)); // close | |
87 | ||
88 | List<Coord> points2 = Arrays.asList( | |
89 | getPoint(25,30), | |
90 | getPoint(30,35), | |
91 | getPoint(20,40), | |
92 | getPoint(15,35), | |
93 | getPoint(25,30)); // close | |
94 | ||
95 | testVariants("simple shapes", points1, points2, 1, 8); | |
129 | 96 | } |
130 | 97 | |
131 | 98 | /** |
134 | 101 | |
135 | 102 | @Test |
136 | 103 | public void test3SharedPointsNonOverlapping(){ |
137 | List<Coord> points1 = new ArrayList<Coord>(){/** | |
138 | * | |
139 | */ | |
140 | private static final long serialVersionUID = 1L; | |
141 | ||
142 | { | |
143 | add(getPoint(15,10)); | |
144 | add(getPoint(30,25)); | |
145 | add(getPoint(25,30)); | |
146 | add(getPoint(20,35)); | |
147 | add(getPoint(15,35)); | |
148 | add(getPoint(5,20)); | |
149 | add(getPoint(15,10));// close | |
150 | }}; | |
151 | ||
152 | List<Coord> points2 = new ArrayList<Coord>(){/** | |
153 | * | |
154 | */ | |
155 | private static final long serialVersionUID = 1L; | |
156 | ||
157 | { | |
158 | add(getPoint(25,30)); | |
159 | add(getPoint(30,35)); | |
160 | add(getPoint(20,40)); | |
161 | add(getPoint(15,35)); | |
162 | add(getPoint(20,35)); | |
163 | add(getPoint(25,30));// close | |
164 | }}; | |
104 | List<Coord> points1 = Arrays.asList( | |
105 | getPoint(15,10), | |
106 | getPoint(30,25), | |
107 | getPoint(25,30), | |
108 | getPoint(20,35), | |
109 | getPoint(15,35), | |
110 | getPoint(5,20), | |
111 | getPoint(15,10));// close | |
112 | ||
113 | List<Coord> points2 = Arrays.asList( | |
114 | getPoint(25,30), | |
115 | getPoint(30,35), | |
116 | getPoint(20,40), | |
117 | getPoint(15,35), | |
118 | getPoint(20,35), | |
119 | getPoint(25,30));// close | |
120 | ||
165 | 121 | testVariants("test 3 consecutive shared points", points1, points2, 1, 8); |
166 | 122 | } |
167 | 123 | |
171 | 127 | |
172 | 128 | @Test |
173 | 129 | public void test2SharedPointsNoEdge(){ |
174 | List<Coord> points1 = new ArrayList<Coord>(){/** | |
175 | * | |
176 | */ | |
177 | private static final long serialVersionUID = 1L; | |
178 | ||
179 | { | |
180 | add(getPoint(15,10)); | |
181 | add(getPoint(30,25)); | |
182 | add(getPoint(25,30)); | |
183 | add(getPoint(15,35)); | |
184 | add(getPoint(5,20)); | |
185 | add(getPoint(15,10));// close | |
186 | }}; | |
187 | ||
188 | List<Coord> points2 = new ArrayList<Coord>(){/** | |
189 | * | |
190 | */ | |
191 | private static final long serialVersionUID = 1L; | |
192 | ||
193 | { | |
194 | add(getPoint(25,30)); | |
195 | add(getPoint(30,35)); | |
196 | add(getPoint(20,40)); | |
197 | add(getPoint(15,35)); | |
198 | add(getPoint(20,35)); | |
199 | add(getPoint(25,30));// close | |
200 | }}; | |
130 | List<Coord> points1 = Arrays.asList( | |
131 | getPoint(15,10), | |
132 | getPoint(30,25), | |
133 | getPoint(25,30), | |
134 | getPoint(15,35), | |
135 | getPoint(5,20), | |
136 | getPoint(15,10));// close | |
137 | ||
138 | List<Coord> points2 = Arrays.asList( | |
139 | getPoint(25,30), | |
140 | getPoint(30,35), | |
141 | getPoint(20,40), | |
142 | getPoint(15,35), | |
143 | getPoint(20,35), | |
144 | getPoint(25,30));// close | |
145 | ||
201 | 146 | testVariants("test 2 non-consecutive shared points", points1, points2, 1, 11); |
202 | 147 | } |
203 | 148 | |
208 | 153 | |
209 | 154 | @Test |
210 | 155 | public void testCloseUFormed(){ |
211 | List<Coord> points1 = new ArrayList<Coord>(){/** | |
212 | * | |
213 | */ | |
214 | private static final long serialVersionUID = 1L; | |
215 | ||
216 | { | |
156 | List<Coord> points1 = Arrays.asList( | |
217 | 157 | // u-formed shaped (open at top) |
218 | add(getPoint(15,50)); | |
219 | add(getPoint(30,50)); | |
220 | add(getPoint(30,55)); | |
221 | add(getPoint(20,55)); | |
222 | add(getPoint(20,65)); | |
223 | add(getPoint(30,65)); | |
224 | add(getPoint(30,70)); | |
225 | add(getPoint(15,70)); | |
226 | add(getPoint(15,50));// close | |
227 | }}; | |
228 | ||
229 | ||
230 | List<Coord> points2 = new ArrayList<Coord>(){/** | |
231 | * | |
232 | */ | |
233 | private static final long serialVersionUID = 1L; | |
234 | ||
235 | { | |
236 | add(getPoint(35,50)); | |
237 | add(getPoint(35,70)); | |
238 | add(getPoint(30,70)); | |
239 | add(getPoint(30,65)); | |
240 | add(getPoint(30,55)); | |
241 | add(getPoint(30,50)); | |
242 | add(getPoint(35,50)); // close | |
243 | }}; | |
158 | getPoint(15,50), | |
159 | getPoint(30,50), | |
160 | getPoint(30,55), | |
161 | getPoint(20,55), | |
162 | getPoint(20,65), | |
163 | getPoint(30,65), | |
164 | getPoint(30,70), | |
165 | getPoint(15,70), | |
166 | getPoint(15,50));// close | |
167 | ||
168 | List<Coord> points2 = Arrays.asList( | |
169 | getPoint(35,50), | |
170 | getPoint(35,70), | |
171 | getPoint(30,70), | |
172 | getPoint(30,65), | |
173 | getPoint(30,55), | |
174 | getPoint(30,50), | |
175 | getPoint(35,50)); // close | |
244 | 176 | |
245 | 177 | testVariants("test close U formed shape", points1, points2, 1, 11); |
246 | 178 | } |
251 | 183 | |
252 | 184 | @Test |
253 | 185 | public void testFillUFormed(){ |
254 | List<Coord> points1 = new ArrayList<Coord>(){/** | |
255 | * | |
256 | */ | |
257 | private static final long serialVersionUID = 1L; | |
258 | ||
259 | { | |
186 | List<Coord> points1 = Arrays.asList( | |
260 | 187 | // u-formed shaped (open at top) |
261 | add(getPoint(15,50)); | |
262 | add(getPoint(30,50)); | |
263 | add(getPoint(30,55)); | |
264 | add(getPoint(20,55)); | |
265 | add(getPoint(20,65)); | |
266 | add(getPoint(30,65)); | |
267 | add(getPoint(30,70)); | |
268 | add(getPoint(15,70)); | |
269 | add(getPoint(15,50)); // close | |
270 | }}; | |
271 | ||
272 | ||
273 | List<Coord> points2 = new ArrayList<Coord>(){/** | |
274 | * | |
275 | */ | |
276 | private static final long serialVersionUID = 1L; | |
277 | ||
278 | { | |
279 | add(getPoint(30,55)); | |
280 | add(getPoint(30,65)); | |
281 | add(getPoint(20,65)); | |
282 | add(getPoint(20,55)); | |
283 | add(getPoint(30,55)); // close | |
284 | }}; | |
188 | getPoint(15,50), | |
189 | getPoint(30,50), | |
190 | getPoint(30,55), | |
191 | getPoint(20,55), | |
192 | getPoint(20,65), | |
193 | getPoint(30,65), | |
194 | getPoint(30,70), | |
195 | getPoint(15,70), | |
196 | getPoint(15,50)); // close | |
197 | ||
198 | List<Coord> points2 = Arrays.asList( | |
199 | getPoint(30,55), | |
200 | getPoint(30,65), | |
201 | getPoint(20,65), | |
202 | getPoint(20,55), | |
203 | getPoint(30,55)); // close | |
204 | ||
285 | 205 | testVariants("test fill U-formed shape", points1, points2, 1, 5); |
286 | 206 | } |
287 | 207 | |
291 | 211 | |
292 | 212 | @Test |
293 | 213 | public void testFillHole(){ |
294 | List<Coord> points1 = new ArrayList<Coord>(){/** | |
295 | * | |
296 | */ | |
297 | private static final long serialVersionUID = 1L; | |
298 | ||
299 | { | |
214 | List<Coord> points1 = Arrays.asList( | |
300 | 215 | // a rectangle with a hole |
301 | add(getPoint(35,50)); | |
302 | add(getPoint(35,70)); | |
303 | add(getPoint(15,70)); | |
304 | add(getPoint(15,50)); | |
305 | add(getPoint(30,50)); | |
306 | add(getPoint(30,55)); | |
307 | add(getPoint(20,55)); | |
308 | add(getPoint(20,65)); | |
309 | add(getPoint(30,65)); | |
310 | add(getPoint(30,50)); | |
311 | add(getPoint(35,50));// close | |
312 | }}; | |
313 | ||
314 | List<Coord> points2 = new ArrayList<Coord>(){/** | |
315 | * | |
316 | */ | |
317 | private static final long serialVersionUID = 1L; | |
318 | ||
319 | { | |
320 | add(getPoint(30,55)); | |
321 | add(getPoint(30,65)); | |
322 | add(getPoint(20,65)); | |
323 | add(getPoint(20,55)); | |
324 | add(getPoint(30,55)); // close | |
325 | }}; | |
216 | getPoint(35,50), | |
217 | getPoint(35,70), | |
218 | getPoint(15,70), | |
219 | getPoint(15,50), | |
220 | getPoint(30,50), | |
221 | getPoint(30,55), | |
222 | getPoint(20,55), | |
223 | getPoint(20,65), | |
224 | getPoint(30,65), | |
225 | getPoint(30,50), | |
226 | getPoint(35,50));// close | |
227 | ||
228 | List<Coord> points2 = Arrays.asList( | |
229 | getPoint(30,55), | |
230 | getPoint(30,65), | |
231 | getPoint(20,65), | |
232 | getPoint(20,55), | |
233 | getPoint(30,55)); // close | |
234 | ||
326 | 235 | testVariants("test-fill-hole", points1, points2, 1, 6); // expect 8 points if spike is not removed |
327 | 236 | } |
328 | 237 | |
329 | 238 | @Test |
330 | 239 | public void testDuplicate(){ |
331 | List<Coord> points1 = new ArrayList<Coord>(){/** | |
332 | * | |
333 | */ | |
334 | private static final long serialVersionUID = 1L; | |
335 | ||
336 | { | |
337 | add(getPoint(30,55)); | |
338 | add(getPoint(30,65)); | |
339 | add(getPoint(20,65)); | |
340 | add(getPoint(20,55)); | |
341 | add(getPoint(30,55)); // close | |
342 | }}; | |
343 | List<Coord> points2 = new ArrayList<Coord>(points1); | |
240 | List<Coord> points1 = Arrays.asList( | |
241 | getPoint(30,55), | |
242 | getPoint(30,65), | |
243 | getPoint(20,65), | |
244 | getPoint(20,55), | |
245 | getPoint(30,55)); // close | |
246 | ||
247 | List<Coord> points2 = new ArrayList<>(points1); | |
344 | 248 | |
345 | 249 | testVariants("test duplicate", points1, points2, 1, 5); |
346 | 250 | } |
347 | 251 | |
348 | 252 | @Test |
349 | 253 | public void testOverlap(){ |
350 | List<Coord> points1 = new ArrayList<Coord>(){/** | |
351 | * | |
352 | */ | |
353 | private static final long serialVersionUID = 1L; | |
354 | ||
355 | { | |
356 | add(getPoint(30,55)); | |
357 | add(getPoint(30,65)); | |
358 | add(getPoint(20,65)); | |
359 | add(getPoint(20,55)); | |
360 | add(getPoint(30,55)); // close | |
361 | }}; | |
362 | ||
363 | List<Coord> points2 = new ArrayList<Coord>(){/** | |
364 | * | |
365 | */ | |
366 | private static final long serialVersionUID = 1L; | |
367 | ||
368 | { | |
369 | add(getPoint(30,55)); | |
370 | add(getPoint(30,65)); | |
371 | add(getPoint(25,65)); | |
372 | add(getPoint(25,55)); | |
373 | add(getPoint(30,55)); // close | |
374 | }}; | |
254 | List<Coord> points1 = Arrays.asList( | |
255 | getPoint(30,55), | |
256 | getPoint(30,65), | |
257 | getPoint(20,65), | |
258 | getPoint(20,55), | |
259 | getPoint(30,55)); // close | |
260 | ||
261 | List<Coord> points2 = Arrays.asList( | |
262 | getPoint(30,55), | |
263 | getPoint(30,65), | |
264 | getPoint(25,65), | |
265 | getPoint(25,55), | |
266 | getPoint(30,55)); // close | |
267 | ||
375 | 268 | // no merge expected |
376 | 269 | testVariants("test overlap", points1, points2, 2, 5); |
377 | 270 | } |
382 | 275 | */ |
383 | 276 | @Test |
384 | 277 | public void testTwoWShaped(){ |
385 | List<Coord> points1 = new ArrayList<Coord>(){/** | |
386 | * | |
387 | */ | |
388 | private static final long serialVersionUID = 1L; | |
389 | ||
390 | { | |
391 | add(getPoint(0,5)); | |
392 | add(getPoint(35,5)); | |
393 | add(getPoint(35,20)); | |
394 | add(getPoint(30,15)); | |
395 | add(getPoint(25,20)); | |
396 | add(getPoint(25,10)); | |
397 | add(getPoint(15,10)); | |
398 | add(getPoint(15,20)); | |
399 | add(getPoint(10,15)); | |
400 | add(getPoint(5,20)); | |
401 | add(getPoint(0,20)); | |
402 | add(getPoint(0,5)); // close | |
403 | }}; | |
404 | ||
405 | List<Coord> points2 = new ArrayList<Coord>(){/** | |
406 | * | |
407 | */ | |
408 | private static final long serialVersionUID = 1L; | |
409 | ||
410 | { | |
411 | add(getPoint(35,35)); | |
412 | add(getPoint(35,20)); | |
413 | add(getPoint(30,15)); | |
414 | add(getPoint(25,20)); | |
415 | add(getPoint(25,25)); | |
416 | add(getPoint(15,25)); | |
417 | add(getPoint(15,20)); | |
418 | add(getPoint(10,15)); | |
419 | add(getPoint(5,20)); | |
420 | add(getPoint(0,20)); | |
421 | add(getPoint(5,35)); | |
422 | add(getPoint(35,35)); // close | |
423 | }}; | |
278 | List<Coord> points1 = Arrays.asList( | |
279 | getPoint(0,5), | |
280 | getPoint(35,5), | |
281 | getPoint(35,20), | |
282 | getPoint(30,15), | |
283 | getPoint(25,20), | |
284 | getPoint(25,10), | |
285 | getPoint(15,10), | |
286 | getPoint(15,20), | |
287 | getPoint(10,15), | |
288 | getPoint(5,20), | |
289 | getPoint(0,20), | |
290 | getPoint(0,5)); // close | |
291 | ||
292 | List<Coord> points2 = Arrays.asList( | |
293 | getPoint(35,35), | |
294 | getPoint(35,20), | |
295 | getPoint(30,15), | |
296 | getPoint(25,20), | |
297 | getPoint(25,25), | |
298 | getPoint(15,25), | |
299 | getPoint(15,20), | |
300 | getPoint(10,15), | |
301 | getPoint(5,20), | |
302 | getPoint(0,20), | |
303 | getPoint(5,35), | |
304 | getPoint(35,35)); // close | |
424 | 305 | |
425 | 306 | // wanted: merge that removes at least the longer shared sequence |
426 | 307 | testVariants("test two w-shaped", points1, points2, 1, 16); |
0 | /* | |
1 | * Copyright (C) 2020. | |
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.osmstyle; | |
13 | ||
14 | import static org.junit.Assert.assertEquals; | |
15 | ||
16 | import java.util.Arrays; | |
17 | ||
18 | import org.junit.Test; | |
19 | ||
20 | import uk.me.parabola.imgfmt.app.Coord; | |
21 | import uk.me.parabola.mkgmap.general.MapLine; | |
22 | import uk.me.parabola.mkgmap.general.MapRoad; | |
23 | import uk.me.parabola.util.EnhancedProperties; | |
24 | ||
25 | ||
26 | /** | |
27 | * Unit test for the code which implements the --road-name-config option | |
28 | */ | |
29 | public class PrefixSuffixFilterTest { | |
30 | ||
31 | @Test | |
32 | public void testFilter() { | |
33 | EnhancedProperties props = new EnhancedProperties(); | |
34 | props.put("road-name-config", "resources/roadNameConfig.txt"); | |
35 | PrefixSuffixFilter filter = new PrefixSuffixFilter(props); | |
36 | MapRoad road; | |
37 | road = genRoad("Rue de la Concorde", "FRA"); | |
38 | filter.filter(road); | |
39 | assertEquals("Rue de la" + (char) 0x1e + "Concorde", road.getName()); | |
40 | road = genRoad("Place de l'Etoile", "FRA"); | |
41 | filter.filter(road); | |
42 | assertEquals("Place de l'" + (char) 0x1b + "Etoile", road.getName()); | |
43 | road = genRoad("Rue de la Normandie", "DEU"); // no change in Germany | |
44 | filter.filter(road); | |
45 | assertEquals("Rue de la Normandie", road.getName()); | |
46 | road = genRoad("Karl-Mustermann-Straße", "DEU"); | |
47 | filter.filter(road); | |
48 | assertEquals("Karl-Mustermann" + (char) 0x1c + "-Straße", road.getName()); | |
49 | road = genRoad("Karl-Mustermann-Straße", "DEU"); | |
50 | filter.filter(road); | |
51 | assertEquals("Karl-Mustermann" + (char) 0x1c + "-Straße", road.getName()); | |
52 | ||
53 | } | |
54 | ||
55 | private static MapRoad genRoad(String name, String isoCountry) { | |
56 | MapLine line = new MapLine(); | |
57 | line.setPoints(Arrays.asList(new Coord(2.0,2.0), new Coord(2.01, 2.0))); | |
58 | MapRoad r = new MapRoad(1, 1, line); | |
59 | r.setName(name); | |
60 | r.setCountry(isoCountry); | |
61 | return r; | |
62 | } | |
63 | ||
64 | } |
395 | 395 | } |
396 | 396 | |
397 | 397 | private List<GType> resolveList(RuleSet rs, Way el) { |
398 | final List<GType> list = new ArrayList<GType>(); | |
398 | final List<GType> list = new ArrayList<>(); | |
399 | 399 | rs.resolveType(el, new TypeResult() { |
400 | 400 | public void add(Element el, GType type) { |
401 | 401 | list.add(type); |
405 | 405 | } |
406 | 406 | |
407 | 407 | private GType getFirstType(Rule rs, Element el) { |
408 | final List<GType> types = new ArrayList<GType>(); | |
408 | final List<GType> types = new ArrayList<>(); | |
409 | 409 | rs.resolveType(el, new TypeResult() { |
410 | 410 | public void add(Element el, GType type) { |
411 | 411 | types.add(type); |
44 | 44 | public class StyledConverterTest { |
45 | 45 | private static final String LOC = "classpath:teststyles"; |
46 | 46 | private OsmConverter converter; |
47 | private final List<MapLine> lines = new ArrayList<MapLine>(); | |
47 | private final List<MapLine> lines = new ArrayList<>(); | |
48 | 48 | |
49 | 49 | @Test |
50 | 50 | public void testConvertWay() throws FileNotFoundException { |
34 | 34 | el.addTag("b", "2"); |
35 | 35 | el.addTag("c", "3"); |
36 | 36 | |
37 | List<String> keys = new ArrayList<String>(); | |
38 | List<String> values = new ArrayList<String>(); | |
37 | List<String> keys = new ArrayList<>(); | |
38 | List<String> values = new ArrayList<>(); | |
39 | 39 | |
40 | 40 | for (Map.Entry<String, String> ent : el.getTagEntryIterator()) { |
41 | 41 | keys.add(ent.getKey()); |
12 | 12 | package uk.me.parabola.util; |
13 | 13 | |
14 | 14 | import static org.junit.Assert.assertEquals; |
15 | import static org.junit.Assert.assertFalse; | |
15 | 16 | import static org.junit.Assert.assertTrue; |
16 | 17 | |
17 | 18 | import java.awt.geom.Area; |
32 | 33 | */ |
33 | 34 | @Test |
34 | 35 | public void testPolygonConversion() throws Exception { |
35 | List<Coord> polygon = new ArrayList<Coord>(); | |
36 | List<Coord> polygon = new ArrayList<>(); | |
36 | 37 | polygon.add(new Coord(0,0)); |
37 | 38 | polygon.add(new Coord(100,10)); |
38 | 39 | polygon.add(new Coord(120,89)); |
84 | 85 | List<Coord> singularPolygon2 = Java2DConverter.singularAreaToPoints(a2); |
85 | 86 | List<Coord> singularPolygon3 = Java2DConverter.singularAreaToPoints(a3); |
86 | 87 | |
87 | assertTrue(a1.equals(a2) == false); | |
88 | assertTrue(a1.equals(a3) == false); | |
89 | assertTrue(a2.equals(a3) == false); | |
88 | assertFalse(a1.equals(a2)); | |
89 | assertFalse(a1.equals(a3)); | |
90 | assertFalse(a2.equals(a3)); | |
90 | 91 | assertEquals(1, convPolygon1.size()); |
91 | 92 | assertEquals(1, convPolygon2.size()); |
92 | 93 | assertEquals(1, convPolygon3.size()); |
93 | assertTrue(Arrays.deepEquals(convPolygon1.toArray(), convPolygon2.toArray()) == true); | |
94 | assertTrue(Arrays.deepEquals(convPolygon1.toArray(), convPolygon3.toArray()) == false); | |
94 | assertTrue(Arrays.deepEquals(convPolygon1.toArray(), convPolygon2.toArray())); | |
95 | assertFalse(Arrays.deepEquals(convPolygon1.toArray(), convPolygon3.toArray())); | |
95 | 96 | assertEquals(4, convPolygon1.get(0).size()); |
96 | 97 | assertEquals(4, convPolygon2.get(0).size()); |
97 | 98 | assertEquals(5, convPolygon3.get(0).size()); |