New upstream version 0.0.0+svn4885
Bas Couwenberg
2 years ago
27 | 27 | file for each tile, then merges all the tiles together into a |
28 | 28 | single gmapsupp.img file. |
29 | 29 | |
30 | Regional extracts of OpenStreetMap data can be obtained from | |
31 | [https://download.geofabrik.de/ '''download.geofabrik.de''']. | |
32 | ||
30 | 33 | == Documentation == |
31 | 34 | The documentation that is currently available on this site is listed |
32 | 35 | below. |
49 | 52 | |
50 | 53 | [/doc/tuning '''Tuning'''] |
51 | 54 | Instructions on how to minimise execution time and avoid running out of memory. |
52 | ||
53 | Further information can be found in the | |
54 | [http://wiki.openstreetmap.org/wiki/Mkgmap '''Open Street Map wiki'''] |
219 | 219 | ;--mdr7-excl=name[,name...] |
220 | 220 | : Specify words which should be omitted from the road index. |
221 | 221 | It was developed before option --road-name-config and is probably no longer needed. |
222 | Matching is done case insensitive. | |
222 | Matching is case insensitive. | |
223 | 223 | : Example usage: --mdr7-excl="Road, Street, Weg" |
224 | 224 | |
225 | 225 | ;--mdr7-del=name[,name...] |
226 | 226 | : Use this option if your style adds strings to the labels of roads which you |
227 | 227 | want to see in the map but which should not appear in the result list |
228 | of a road name / address search. The list is used like this: | |
229 | For each road label, mkgmap searches for the last blank. If one is found, it checks | |
230 | whether the word after it appears in the given list. If so, the word is removed | |
231 | and the search is repeated. The remaining string is used to create the index. | |
232 | The search is done case insensitive. | |
228 | of a road name / address search. | |
229 | When creating the index, words in the given list are removed from the end of road labels. | |
230 | Any word not found in the given list is considerered to be a valid label; words | |
231 | before the last valid label are not removed. | |
232 | If the label consists of a single word in the given list, or all the words in the | |
233 | label are contained in the given list, then the label is omitted from the index. | |
234 | The comparison is case insensitive and words are separated by a space. | |
233 | 235 | : Example: Assume your style adds surface attributes like 'pav.' or 'unp.' to a road |
234 | 236 | label. You can use --mdr7-del="pav.,unp." to remove these suffixes from the index. |
235 | 237 |
19 | 19 | // see [[Mkgmap/help/Tags]] |
20 | 20 | |
21 | 21 | .Tags for routable roads |
22 | [options="header"] | |
22 | [cols="4,6,3,7",options="header"] | |
23 | 23 | |========================================================= |
24 | 24 | | Attribute | mkgmap tag | Example | Notes |
25 | 25 | | Labels | +mkgmap:label:1+ + |
55 | 55 | |========================================================= |
56 | 56 | |
57 | 57 | .Tags that control the treatment of roads |
58 | [options="header"] | |
58 | [cols="8,7,5",options="header"] | |
59 | 59 | |========================================================= |
60 | 60 | | Tag | Description | Required mkgmap option |
61 | 61 | | +mkgmap:way-has-pois+ | +true+ for ways that have at least one point with a tag +access=\*+, +barrier=\*+, or +highway=*+ | 'link-pois-to-ways' |
1 | 1 | // This is the list of variable filters. |
2 | 2 | // |
3 | 3 | .List of all substitution filters |
4 | [width="100%",grid="rows",cols="<1,<1,3a",options="header"] | |
4 | [width="100%",grid="rows",cols="<1,<1,4a",options="header"] | |
5 | 5 | |======= |
6 | 6 | | Name | Arguments | Description |
7 | 7 | | def | `default` | |
9 | 9 | This means that the variable will never be `unset' in places where that |
10 | 10 | matters. |
11 | 11 | |
12 | `${oneway\|def:no}` | |
12 | `${oneway\|def:"no"}` | |
13 | 13 | |
14 | 14 | | conv | `m=>ft` | |
15 | 15 | Use for conversions between units. |
48 | 48 | | part | `separator operator partnumber` | |
49 | 49 | Split a value in parts and returns one or more part(s) of it. If +partnumber+ is negative, the part returned is counted from the end of the split |
50 | 50 | |
51 | If not specified, the default separator is ';' and the first part is returned (i.e. `${name\|part:}`=`${name\|part:;:1}`). | |
51 | If not specified, the default separator is ';' and the first part is returned (i.e. `${name\|part:""}`=`${name\|part:";:1"}`). | |
52 | 52 | |
53 | 53 | If the operator is `:` the part specified by +partnumber+ is returned. |
54 | 54 | |
79 | 79 | Prepares the value as a highway reference such as "A21" "I-80" and so |
80 | 80 | on. |
81 | 81 | A code is added to the front of the string so that a highway shield is |
82 | displayed, spaces are removed and the text is truncated so as not to overflow the | |
83 | symbol. | |
82 | displayed, any ";" are replaced by "/" and spaces are removed. | |
84 | 83 | |
85 | 84 | `${ref\|highway-symbol:"box:4:8"}` |
86 | 85 | |
90 | 89 | references that contain numbers and letters. |
91 | 90 | The second is the maximum length of references that do not contain numbers. |
92 | 91 | If there is just the one number then it is used in both cases. |
92 | If no numbers are given, the default value 8 is used. | |
93 | ||
94 | If the reference, after spaces have been removed, is longer than the maximum length | |
95 | then the filter passes the string onwards unchanged; the highway-symbol is not prepended. | |
93 | 96 | |
94 | 97 | | height | `m=>ft` | |
95 | 98 | This is exactly the same as the +conv+ filter, except that it prepends a special |
103 | 106 | | country-ISO | | |
104 | 107 | Use to normalize country names to the 3 character ISO 1366 code. |
105 | 108 | The filter has no arguments. It uses the list in LocatorConfig.xml. |
106 | Possible arguments are country names, or ISO codes in 2 or 3 characters, | |
109 | Possible inputs are country names, or ISO codes in 2 or 3 characters, | |
107 | 110 | for example "Deutschland", "Germany", "Bundesrepublik Deutschland", or "DE" |
108 | 111 | will all return "DEU", also different cases like "GERMANY" or " germany " |
109 | 112 | will work. |
117 | 120 | |
118 | 121 | .... |
119 | 122 | place=* { |
120 | name '${name} (${int_name\|not-equal:name})' | |
123 | name '${name} (${int_name\|not-equal:"name"})' | |
121 | 124 | \| '${name}' |
122 | 125 | } |
123 | 126 | .... |
129 | 132 | Extract part of the string. The start and end positions |
130 | 133 | are counted starting from zero and the end position is not included. |
131 | 134 | |
132 | `${name\|substring:2:5}` | |
135 | `${name\|substring:"2:5"}` | |
133 | 136 | If the "name" was "Dorset Lane", then the result is "rse". If there is just the one number, |
134 | 137 | then the substring starts from that character until the end of the string. |
135 | 138 | |
136 | | not-contained | `separator tag` | | |
139 | | not-contained | `separator:tag` | | |
137 | 140 | Used to check for duplicate values. If the value of this tag is contained in the list being |
138 | 141 | the value of the tag named as the argument to +not-contained+, then value |
139 | 142 | of this tag is set to undefined. |
141 | 144 | .... |
142 | 145 | type=route & route=bus & ref=* { |
143 | 146 | apply { |
144 | set route_ref='$(route_ref),${ref\|not-contained:,:route_ref}' \| '$(route_ref)' \| '${ref}'; | |
147 | set route_ref= | |
148 | '$(route_ref),${ref\|not-contained:",:route_ref"}' | |
149 | \| '$(route_ref)' \| '${ref}'; | |
145 | 150 | } |
146 | 151 | } |
147 | 152 | .... |
577 | 577 | one argument required then they are usually separated by colons too, but |
578 | 578 | that is not a rule. |
579 | 579 | |
580 | +${tagname|filter:arg1:arg2}+ | |
580 | +${tagname|filter:"arg1:arg2"}+ | |
581 | 581 | |
582 | 582 | You can apply as many filter expressions to a substitution as you like. |
583 | 583 | |
584 | 584 | |
585 | +${tagname|filter1:arg|filter2:arg}+ | |
586 | ||
587 | If the argument contains spaces or symbols it should be quoted. | |
588 | ||
589 | +${tagname|filter1:"arg with spaces"}+ | |
590 | ||
591 | For backward compatibility, most cases where you have spaces or symbols | |
585 | +${tagname|filter1:"f1Args"|filter2:"f2Args"}+ | |
586 | ||
587 | For backward compatibility, most argument strings | |
592 | 588 | do not actually need to be quoted, however we would recommend that you |
593 | 589 | do for clarity. If you need a pipe symbol or a closing curly backet, |
594 | 590 | then you must use quotes. |
713 | 709 | connect different regions, down to class 0 which is used for residential |
714 | 710 | streets and other roads that you would only use for local travel. |
715 | 711 | |
716 | It is important for routing to work well that most roads are class 0 and | |
712 | It is important for routing to work well that most roads have lower classes and | |
717 | 713 | there are fewer and fewer roads in each of the higher classes. |
714 | Also, the class of connector roads (links, roundabouts, ramps) matches | |
715 | the class of the highest class of roads being connected. | |
718 | 716 | |
719 | 717 | .Road classes |
720 | 718 | [width="40%",frame="topbot",grid="rows",cols="<1,<4",options="header"] |
723 | 721 | | 4 | Major HW/Ramp |
724 | 722 | | 3 | Principal HW |
725 | 723 | | 2 | Arterial St / Other HW |
726 | | 1 | Roundabout / Collector | |
724 | | 1 | Minor or Service road | |
727 | 725 | | 0 | Residential Street / Unpaved road / Trail |
728 | 726 | |===== |
729 | 727 |
218 | 218 | --mdr7-excl=name[,name...] |
219 | 219 | Specify words which should be omitted from the road index. It was developed |
220 | 220 | before option --road-name-config and is probably no longer needed. Matching |
221 | is done case insensitive. | |
221 | is case insensitive. | |
222 | 222 | Example usage: --mdr7-excl="Road, Street, Weg" |
223 | 223 | |
224 | 224 | --mdr7-del=name[,name...] |
225 | 225 | Use this option if your style adds strings to the labels of roads which you |
226 | 226 | want to see in the map but which should not appear in the result list of a |
227 | road name / address search. The list is used like this: For each road | |
228 | label, mkgmap searches for the last blank. If one is found, it checks | |
229 | whether the word after it appears in the given list. If so, the word is | |
230 | removed and the search is repeated. The remaining string is used to create | |
231 | the index. The search is done case insensitive. | |
227 | road name / address search. When creating the index, words in the given | |
228 | list are removed from the end of road labels. Any word not found in the | |
229 | given list is considerered to be a valid label; words before the last valid | |
230 | label are not removed. If the label consists of a single word in the given | |
231 | list, or all the words in the label are contained in the given list, then | |
232 | the label is omitted from the index. The comparison is case insensitive and | |
233 | words are separated by a space. | |
232 | 234 | Example: Assume your style adds surface attributes like 'pav.' or 'unp.' to |
233 | 235 | a road label. You can use --mdr7-del="pav.,unp." to remove these suffixes |
234 | 236 | from the index. |
0 | svn.version: 4839 | |
1 | build.timestamp: 2021-12-30T15:30:14+0000 | |
0 | svn.version: 4885 | |
1 | build.timestamp: 2022-01-30T08:18:40+0000 |
44 | 44 | |
45 | 45 | // We keep our own idea of the file position. |
46 | 46 | private long position; |
47 | // the position of the header, normally 0, but not in GMP format | |
48 | private final int gmpOffset; | |
47 | 49 | |
48 | 50 | public BufferedImgFileReader(ImgChannel chan) { |
51 | this(chan, 0); | |
52 | } | |
53 | ||
54 | public BufferedImgFileReader(ImgChannel chan, int gmpOffset) { | |
49 | 55 | this.chan = chan; |
56 | this.gmpOffset = gmpOffset; | |
50 | 57 | // buf.order(ByteOrder.LITTLE_ENDIAN); |
51 | 58 | // could use getShort/getChar/getInt if sure buffer loaded |
59 | } | |
60 | ||
61 | @Override | |
62 | public int getGMPOffset() { | |
63 | return gmpOffset; | |
52 | 64 | } |
53 | 65 | |
54 | 66 | /** |
40 | 40 | |
41 | 41 | // Set to 0x80 on locked maps. We are not interested in creating locked |
42 | 42 | // maps, but may be useful to recognise them for completeness. |
43 | // private byte lockFlag; | |
43 | private byte lockFlag; | |
44 | 44 | |
45 | 45 | // A date of creation. |
46 | 46 | private Date creationDate; |
77 | 77 | * @param reader Used to read the header. |
78 | 78 | */ |
79 | 79 | public final void readHeader(ImgFileReader reader) throws ReadFailedException { |
80 | reader.position(0); | |
80 | reader.position(reader.getGMPOffset()); | |
81 | 81 | headerLength = reader.get2u(); |
82 | 82 | byte[] bytes = reader.get(TYPE_LEN); |
83 | 83 | type = new String(bytes, StandardCharsets.US_ASCII); |
84 | 84 | reader.get(); // ignore |
85 | reader.get(); // ignore | |
85 | this.lockFlag = reader.get(); // 0x80 if locked | |
86 | 86 | |
87 | 87 | byte[] date = reader.get(7); |
88 | 88 | creationDate = Utils.makeCreationTime(date); |
115 | 115 | if (creationDate == null) |
116 | 116 | creationDate = new Date(); |
117 | 117 | } |
118 | ||
119 | /** | |
120 | * @return the lockFlag | |
121 | */ | |
122 | public byte getLockFlag() { | |
123 | return lockFlag; | |
124 | } | |
118 | 125 | } |
16 | 16 | package uk.me.parabola.imgfmt.app; |
17 | 17 | |
18 | 18 | import java.io.Closeable; |
19 | import java.util.concurrent.atomic.AtomicInteger; | |
19 | 20 | |
20 | 21 | import uk.me.parabola.imgfmt.ReadFailedException; |
21 | 22 | |
108 | 109 | * @return A phone number possibly containing the delimiter character. |
109 | 110 | */ |
110 | 111 | public String getBase11str(byte firstChar, char delimiter); |
112 | ||
113 | /** | |
114 | * Get the offset in the GMP file. | |
115 | * @return the offset, 0 if not a GMP file | |
116 | */ | |
117 | default int getGMPOffset() { | |
118 | return 0; | |
119 | } | |
120 | ||
121 | /** | |
122 | * Read varying length integer where first byte also gives number of following bytes. | |
123 | * See also imgfmt/app/mdr/MdrUtils.writeVarLength() and ./MdrDisplay.printSect17() | |
124 | * | |
125 | * @return the length | |
126 | */ | |
127 | default int readVarLength(AtomicInteger varLength) { | |
128 | int peek = get1u(); | |
129 | if ((peek & 0x1) == 0x1) { | |
130 | varLength.set(1); | |
131 | return peek >> 1; | |
132 | } else if ((peek & 0x3) == 0x2) { | |
133 | varLength.set(2); | |
134 | return peek >> 2 | get1u() << 6; | |
135 | } else if ((peek & 0x7) == 0x4) { | |
136 | varLength.set(3); | |
137 | return peek >> 3 | get2u() << 5; | |
138 | } else { // bottom 3 bits clear so assume: | |
139 | varLength.set(4); | |
140 | return peek >> 4 | get3u() << 4; // mkgmap writeVarLength does this | |
141 | } | |
142 | } | |
111 | 143 | } |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.imgfmt.app.lbl; |
13 | 13 | |
14 | import static uk.me.parabola.imgfmt.app.Label.NULL_LABEL; | |
15 | ||
14 | 16 | import java.util.ArrayList; |
15 | 17 | import java.util.Collections; |
16 | 18 | import java.util.HashMap; |
23 | 25 | import uk.me.parabola.imgfmt.app.ImgFile; |
24 | 26 | import uk.me.parabola.imgfmt.app.ImgFileReader; |
25 | 27 | import uk.me.parabola.imgfmt.app.Label; |
28 | import uk.me.parabola.imgfmt.app.Section; | |
26 | 29 | import uk.me.parabola.imgfmt.app.labelenc.CharacterDecoder; |
27 | 30 | import uk.me.parabola.imgfmt.app.labelenc.CodeFunctions; |
28 | 31 | import uk.me.parabola.imgfmt.app.labelenc.DecodedText; |
29 | 32 | import uk.me.parabola.imgfmt.app.trergn.Subdivision; |
30 | 33 | import uk.me.parabola.imgfmt.fs.ImgChannel; |
31 | 34 | import uk.me.parabola.log.Logger; |
32 | ||
33 | import static uk.me.parabola.imgfmt.app.Label.NULL_LABEL; | |
34 | 35 | |
35 | 36 | /** |
36 | 37 | * The file that holds all the labels for the map. |
58 | 59 | private final List<City> cities = new ArrayList<>(); |
59 | 60 | |
60 | 61 | public LBLFileReader(ImgChannel chan) { |
61 | this(chan, true); | |
62 | this(chan, true, 0); | |
62 | 63 | } |
63 | 64 | |
64 | public LBLFileReader(ImgChannel chan, boolean fullData) { | |
65 | public LBLFileReader(ImgChannel chan, boolean fullData, int gmpOffset) { | |
65 | 66 | setHeader(header); |
66 | 67 | |
67 | setReader(new BufferedImgFileReader(chan)); | |
68 | setReader(new BufferedImgFileReader(chan, gmpOffset)); | |
68 | 69 | header.readHeader(getReader()); |
69 | 70 | if (!fullData) |
70 | 71 | return; |
139 | 140 | PlacesHeader placeHeader = header.getPlaceHeader(); |
140 | 141 | |
141 | 142 | countries.add(null); // 1 based indexes |
142 | ||
143 | int start = placeHeader.getCountriesStart(); | |
144 | int end = placeHeader.getCountriesEnd(); | |
145 | ||
143 | Section s = placeHeader.getCountrySection(); | |
144 | int start = s.getPosition(); | |
145 | int end = s.getEndPos(); | |
146 | int recSize = s.getItemSize(); | |
147 | int unkSize = recSize - 3; // new maps may have extra data | |
146 | 148 | reader.position(start); |
147 | 149 | int index = 1; |
148 | 150 | while (reader.position() < end) { |
149 | 151 | int offset = reader.get3u(); |
150 | Label label = fetchLabel(offset); | |
152 | Label label = fetchLabel(offset & 0x3fffff); | |
153 | if (unkSize > 0) | |
154 | reader.get(unkSize); | |
151 | 155 | |
152 | 156 | if (label != null) { |
153 | 157 | Country country = new Country(index, label); |
164 | 168 | ImgFileReader reader = getReader(); |
165 | 169 | |
166 | 170 | PlacesHeader placeHeader = header.getPlaceHeader(); |
167 | ||
168 | int start = placeHeader.getRegionsStart(); | |
169 | int end = placeHeader.getRegionsEnd(); | |
171 | Section s = placeHeader.getRegionSection(); | |
172 | int start = s.getPosition(); | |
173 | int end = s.getEndPos(); | |
174 | int recSize = s.getItemSize(); | |
175 | int unkSize = recSize - 5; // new maps may have extra data | |
170 | 176 | |
171 | 177 | regions.add(null); |
172 | 178 | |
175 | 181 | while (reader.position() < end) { |
176 | 182 | int country = reader.get2u(); |
177 | 183 | int offset = reader.get3u(); |
178 | Label label = fetchLabel(offset); | |
184 | if (unkSize > 0) | |
185 | reader.get(unkSize); | |
186 | Label label = fetchLabel(offset & 0x3fffff); | |
179 | 187 | if (label != null) { |
180 | 188 | Region region = new Region(countries.get(country), label); |
181 | 189 | region.setIndex(index); |
193 | 201 | */ |
194 | 202 | private void readCities() { |
195 | 203 | PlacesHeader placeHeader = header.getPlaceHeader(); |
196 | int start = placeHeader.getCitiesStart(); | |
197 | int end = placeHeader.getCitiesEnd(); | |
198 | ||
204 | Section s = placeHeader.getCitySection(); | |
205 | int start = s.getPosition(); | |
206 | int end = s.getEndPos(); | |
207 | int recSize = s.getItemSize(); | |
208 | int unkSize = recSize - 5; // new maps may have extra data | |
199 | 209 | ImgFileReader reader = getReader(); |
200 | 210 | |
201 | 211 | // Since cities are indexed starting from 1, we add a null one at index 0 |
207 | 217 | // don't know until we have read further |
208 | 218 | int label = reader.get3u(); |
209 | 219 | int info = reader.get2u(); |
220 | if (unkSize > 0) | |
221 | reader.get(unkSize); | |
210 | 222 | |
211 | 223 | City city; |
212 | 224 | if ((info & 0x4000) == 0) { |
220 | 232 | city.setIndex(index); |
221 | 233 | if ((info & 0x8000) == 0) { |
222 | 234 | city.setSubdivision(Subdivision.createEmptySubdivision(1)); |
223 | Label label1 = labels.get(label & 0x3fffff); | |
235 | Label label1 = fetchLabel(label & 0x3fffff); | |
224 | 236 | city.setLabel(label1); |
225 | 237 | } else { |
226 | 238 | // Has subdiv/point index |
309 | 321 | ImgFileReader reader = getReader(); |
310 | 322 | |
311 | 323 | PlacesHeader placeHeader = header.getPlaceHeader(); |
312 | int start = placeHeader.getZipsStart(); | |
313 | int end = placeHeader.getZipsEnd(); | |
324 | Section s = placeHeader.getZipSection(); | |
325 | int start = s.getPosition(); | |
326 | int end = s.getEndPos(); | |
327 | int recSize = s.getItemSize(); | |
328 | int unkSize = recSize - 3; // new maps may have extra data | |
314 | 329 | |
315 | 330 | reader.position(start); |
316 | 331 | |
317 | 332 | int zipIndex = 1; |
318 | 333 | while (reader.position() < end) { |
319 | 334 | int lblOffset = reader.get3u(); |
320 | ||
321 | Zip zip = new Zip(fetchLabel(lblOffset)); | |
335 | if (unkSize > 0) | |
336 | reader.get(unkSize); | |
337 | ||
338 | Zip zip = new Zip(fetchLabel(lblOffset & 0x3fffff)); | |
322 | 339 | zip.setIndex(zipIndex); |
323 | 340 | |
324 | 341 | zips.put(zip.getIndex(), zip); |
121 | 121 | } |
122 | 122 | |
123 | 123 | void readFileHeader(ImgFileReader reader) { |
124 | reader.position(0x1f); | |
124 | reader.position(0x1fL + reader.getGMPOffset()); | |
125 | 125 | |
126 | 126 | country.readSectionInfo(reader, true); |
127 | 127 | reader.get4(); |
222 | 222 | return poiProperties.getEndPos(); |
223 | 223 | } |
224 | 224 | |
225 | public int getCitiesStart() { | |
226 | return city.getPosition(); | |
227 | } | |
228 | public int getCitiesEnd() { | |
229 | return city.getEndPos(); | |
230 | } | |
231 | ||
225 | public Section getCitySection() { | |
226 | return city; | |
227 | } | |
232 | 228 | public int getNumExits() { |
233 | 229 | return exitFacility.getNumItems(); |
234 | 230 | } |
235 | 231 | |
236 | public int getCountriesStart() { | |
237 | return country.getPosition(); | |
238 | } | |
239 | ||
240 | public int getCountriesEnd() { | |
241 | return country.getEndPos(); | |
242 | } | |
243 | ||
244 | public int getRegionsStart() { | |
245 | return region.getPosition(); | |
246 | } | |
247 | ||
248 | public int getRegionsEnd() { | |
249 | return region.getEndPos(); | |
232 | public Section getCountrySection() { | |
233 | return country; | |
234 | } | |
235 | ||
236 | public Section getRegionSection() { | |
237 | return region; | |
250 | 238 | } |
251 | 239 | |
252 | 240 | public int getNumHighways() { |
253 | 241 | return highway.getNumItems(); |
254 | 242 | } |
255 | 243 | |
256 | public int getZipsStart() { | |
257 | return zip.getPosition(); | |
258 | } | |
259 | ||
260 | public int getZipsEnd() { | |
261 | return zip.getEndPos(); | |
262 | } | |
244 | public Section getZipSection() { | |
245 | return zip; | |
246 | } | |
263 | 247 | } |
17 | 17 | |
18 | 18 | import java.util.Arrays; |
19 | 19 | |
20 | import uk.me.parabola.imgfmt.Utils; | |
20 | 21 | import uk.me.parabola.imgfmt.app.CommonHeader; |
21 | 22 | import uk.me.parabola.imgfmt.app.ImgFileReader; |
22 | 23 | import uk.me.parabola.imgfmt.app.ImgFileWriter; |
40 | 41 | private final Section boundary = new Section(roads, BOUNDARY_ITEM_SIZE); |
41 | 42 | private final Section highClassBoundary = new Section(boundary); |
42 | 43 | private final int[] classBoundaries = new int[5]; |
44 | private final Section nod5 = new Section(); | |
45 | private final Section nod6 = new Section(); | |
43 | 46 | |
44 | 47 | private int flags; |
45 | 48 | private int align; |
46 | 49 | private int mult1; |
47 | 50 | private int tableARecordLen; |
48 | 51 | private boolean driveOnLeft; |
52 | ||
53 | private int indexIdSize; | |
49 | 54 | |
50 | 55 | public NODHeader() { |
51 | 56 | super(HEADER_LEN, "GARMIN NOD"); |
79 | 84 | classBoundaries[3] = classBoundaries[2] + reader.get4(); |
80 | 85 | classBoundaries[4] = classBoundaries[3] + reader.get4(); |
81 | 86 | } |
87 | if (getHeaderLength() >= 0x7f) { | |
88 | reader.position(reader.getGMPOffset() + 0x67); | |
89 | nod5.readSectionInfo(reader, false); | |
90 | reader.get2u(); | |
91 | nod6.readSectionInfo(reader, true); | |
92 | indexIdSize = Utils.numberToPointerSize(nod6.getNumItems()); | |
93 | } | |
94 | ||
82 | 95 | } |
83 | 96 | |
84 | 97 | /** |
198 | 211 | public int getTableARecordLen() { |
199 | 212 | return tableARecordLen; |
200 | 213 | } |
214 | ||
215 | /** | |
216 | * @return the indexIdSize | |
217 | */ | |
218 | public int getIndexIdSize() { | |
219 | return indexIdSize; | |
220 | } | |
201 | 221 | } |
49 | 49 | private int indPointPtrOff; |
50 | 50 | private int polylinePtrOff; |
51 | 51 | private int polygonPtrOff; |
52 | private ByteArrayOutputStream extTypePointsData; | |
53 | private ByteArrayOutputStream extTypeLinesData; | |
54 | private ByteArrayOutputStream extTypeAreasData; | |
52 | private final ByteArrayOutputStream extTypePointsData = new ByteArrayOutputStream(); | |
53 | private final ByteArrayOutputStream extTypeLinesData = new ByteArrayOutputStream(); | |
54 | private final ByteArrayOutputStream extTypeAreasData = new ByteArrayOutputStream(); | |
55 | 55 | |
56 | 56 | public RGNFile(ImgChannel chan) { |
57 | 57 | setHeader(header); |
68 | 68 | |
69 | 69 | header.setDataSize(position() - HEADER_LEN); |
70 | 70 | |
71 | if(extTypeAreasData != null) { | |
71 | if(extTypeAreasData.size() > 0) { | |
72 | 72 | header.setExtTypeAreasInfo(position(), extTypeAreasData.size()); |
73 | 73 | getWriter().put(extTypeAreasData.toByteArray()); |
74 | 74 | } |
75 | if(extTypeLinesData != null) { | |
75 | if(extTypeLinesData.size() > 0) { | |
76 | 76 | header.setExtTypeLinesInfo(position(), extTypeLinesData.size()); |
77 | 77 | getWriter().put(extTypeLinesData.toByteArray()); |
78 | 78 | } |
79 | if(extTypePointsData != null) { | |
79 | if(extTypePointsData.size() > 0) { | |
80 | 80 | header.setExtTypePointsInfo(position(), extTypePointsData.size()); |
81 | 81 | getWriter().put(extTypePointsData.toByteArray()); |
82 | 82 | } |
94 | 94 | // needed as it will always be first if present. |
95 | 95 | if (sd.needsIndPointPtr()) { |
96 | 96 | indPointPtrOff = position(); |
97 | position(position() + 2); | |
97 | position(position() + 2L); | |
98 | 98 | } |
99 | 99 | |
100 | 100 | if (sd.needsPolylinePtr()) { |
101 | 101 | polylinePtrOff = position(); |
102 | position(position() + 2); | |
102 | position(position() + 2L); | |
103 | 103 | } |
104 | 104 | |
105 | 105 | if (sd.needsPolygonPtr()) { |
106 | 106 | polygonPtrOff = position(); |
107 | position(position() + 2); | |
107 | position(position() + 2L); | |
108 | 108 | } |
109 | 109 | |
110 | 110 | currentDivision = sd; |
111 | 111 | } |
112 | 112 | |
113 | 113 | public void addMapObject(MapObject item) { |
114 | if(item.hasExtendedType()) { | |
114 | if (item.hasExtendedType()) { | |
115 | 115 | try { |
116 | if(item instanceof Point) { | |
117 | if(extTypePointsData == null) | |
118 | extTypePointsData = new ByteArrayOutputStream(); | |
116 | if (item instanceof Point) { | |
119 | 117 | item.write(extTypePointsData); |
120 | } | |
121 | else if(item instanceof Polygon) { | |
122 | if(extTypeAreasData == null) | |
123 | extTypeAreasData = new ByteArrayOutputStream(); | |
118 | } else if (item instanceof Polygon) { | |
124 | 119 | item.write(extTypeAreasData); |
125 | } | |
126 | else if(item instanceof Polyline) { | |
127 | if(extTypeLinesData == null) | |
128 | extTypeLinesData = new ByteArrayOutputStream(); | |
120 | } else if (item instanceof Polyline) { | |
129 | 121 | item.write(extTypeLinesData); |
130 | } | |
131 | else | |
122 | } else | |
132 | 123 | log.error("Can't add object of type " + item.getClass()); |
133 | } | |
134 | catch (IOException ioe) { | |
124 | } catch (IOException ioe) { | |
135 | 125 | log.error("Error writing extended type object: " + ioe.getMessage()); |
136 | 126 | } |
137 | } | |
138 | else { | |
127 | } else { | |
139 | 128 | item.write(getWriter()); |
140 | 129 | } |
141 | 130 | } |
191 | 180 | } |
192 | 181 | |
193 | 182 | public int getExtTypePointsSize() { |
194 | return (extTypePointsData == null)? 0 : extTypePointsData.size(); | |
183 | return extTypePointsData.size(); | |
195 | 184 | } |
196 | 185 | |
197 | 186 | public int getExtTypeLinesSize() { |
198 | return (extTypeLinesData == null)? 0 : extTypeLinesData.size(); | |
187 | return extTypeLinesData.size(); | |
199 | 188 | } |
200 | 189 | |
201 | 190 | public int getExtTypeAreasSize() { |
202 | return (extTypeAreasData == null)? 0 : extTypeAreasData.size(); | |
191 | return extTypeAreasData.size(); | |
203 | 192 | } |
204 | 193 | |
205 | 194 | public boolean haveExtendedTypes() { |
206 | return extTypePointsData != null || | |
207 | extTypeLinesData != null || | |
208 | extTypeAreasData != null; | |
195 | return extTypeAreasData.size() != 0 || | |
196 | extTypeLinesData.size() != 0 || | |
197 | extTypePointsData.size() != 0; | |
209 | 198 | } |
210 | 199 | } |
84 | 84 | fetchPointsCommon(sd, rgnOffsets.getPointStart(), rgnOffsets.getPointEnd(), list); |
85 | 85 | } |
86 | 86 | if (withExtType && sd.getExtTypePointsSize() > 0) |
87 | fetchPointsCommonExtType(sd, rgnHeader.getExtTypePointsOffset() + sd.getExtTypePointsOffset(), sd.getExtTypePointsSize(), list); | |
87 | fetchPointsExtType(sd, list); | |
88 | 88 | |
89 | 89 | return list; |
90 | 90 | } |
127 | 127 | if (hasSubtype) { |
128 | 128 | int st = reader.get1u(); |
129 | 129 | t |= st; |
130 | //p.setHasSubtype(true); | |
131 | 130 | } |
132 | 131 | p.setType(t); |
133 | 132 | |
137 | 136 | } |
138 | 137 | |
139 | 138 | /** |
140 | * The indexed points and the points sections are both read just the same. | |
141 | */ | |
142 | private void fetchPointsCommonExtType(Subdivision sd, long start, long end, List<Point> points) { | |
139 | * The points with extended types | |
140 | */ | |
141 | private void fetchPointsExtType(Subdivision sd, List<Point> points) { | |
142 | long start = rgnHeader.getExtTypePointsOffset() + sd.getExtTypePointsOffset(); | |
143 | long end = start + sd.getExtTypePointsSize(); | |
143 | 144 | position(start); |
144 | 145 | ImgFileReader reader = getReader(); |
145 | 146 | |
338 | 339 | |
339 | 340 | if (hasLabel){ |
340 | 341 | int labelOffset = reader.get3u(); |
341 | Label label; | |
342 | if ((labelOffset & 0x800000) == 0) { | |
343 | label = lblFile.fetchLabel(labelOffset & 0x7fffff); | |
344 | } else { | |
345 | int netoff = labelOffset & 0x3fffff; | |
346 | labelOffset = netFile.getLabelOffset(netoff); | |
347 | label = lblFile.fetchLabel(labelOffset); | |
348 | RoadDef roadDef = new RoadDef(0, netoff, label.getText()); | |
349 | line.setRoadDef(roadDef); | |
350 | } | |
342 | Label label = lblFile.fetchLabel(labelOffset & 0x3fffff); | |
351 | 343 | line.setLabel(label); |
352 | 344 | } |
353 | 345 | if (hasExtraBytes){ |
67 | 67 | private boolean hasIndPoints; |
68 | 68 | private boolean hasPolylines; |
69 | 69 | private boolean hasPolygons; |
70 | private boolean hasRoadRefs; | |
70 | 71 | |
71 | 72 | private int numPolylines; |
72 | 73 | |
166 | 167 | setHasPolylines(true); |
167 | 168 | if ((elem & 0x80) != 0) |
168 | 169 | setHasPolygons(true); |
169 | } | |
170 | if ((elem & 0x1) != 0) // from top bit in height | |
171 | setHasRoadRefs(true); | |
172 | } | |
170 | 173 | |
171 | 174 | /** |
172 | 175 | * Create a subdivision at a given zoom level. |
398 | 401 | this.hasPolygons = hasPolygons; |
399 | 402 | } |
400 | 403 | |
404 | public void setHasRoadRefs(boolean hasRoeadRefs) { | |
405 | this.hasRoadRefs = hasRoeadRefs; | |
406 | } | |
407 | ||
401 | 408 | public boolean hasPoints() { |
402 | 409 | return hasPoints; |
403 | 410 | } |
412 | 419 | |
413 | 420 | public boolean hasPolygons() { |
414 | 421 | return hasPolygons; |
422 | } | |
423 | ||
424 | public boolean hasRoadRefs() { | |
425 | return hasRoadRefs; | |
415 | 426 | } |
416 | 427 | |
417 | 428 | /** |
433 | 444 | } |
434 | 445 | |
435 | 446 | /** |
447 | * Needed if it exists and is not first, ie there is a points or | |
448 | * indexed points or polyline section. | |
449 | * @return true if pointer needed. | |
450 | */ | |
451 | public boolean needsPolygonPtr() { | |
452 | return hasPolygons && (hasPoints || hasIndPoints || hasPolylines); | |
453 | } | |
454 | ||
455 | /** | |
436 | 456 | * As this is last in the list it is needed if it exists and there |
437 | 457 | * is another section. |
438 | 458 | * @return true if pointer needed. |
439 | 459 | */ |
440 | public boolean needsPolygonPtr() { | |
441 | return hasPolygons && (hasPoints || hasIndPoints || hasPolylines); | |
442 | } | |
443 | ||
460 | public boolean needsRoadRefPtr() { | |
461 | return hasRoadRefs && (hasPoints || hasIndPoints || hasPolylines || hasPolygons); | |
462 | } | |
463 | ||
444 | 464 | public String toString() { |
445 | 465 | return "Sub" + zoomLevel + '(' + getCenter().toOSMURL() + ')'; |
446 | 466 | } |
49 | 49 | |
50 | 50 | |
51 | 51 | public TREFileReader(ImgChannel chan) { |
52 | this(chan, 0); | |
53 | } | |
54 | ||
55 | public TREFileReader(ImgChannel chan, int gmpOffset) { | |
52 | 56 | setHeader(header); |
53 | 57 | |
54 | setReader(new BufferedImgFileReader(chan)); | |
58 | setReader(new BufferedImgFileReader(chan, gmpOffset)); | |
55 | 59 | header.readHeader(getReader()); |
56 | 60 | readMapLevels(); |
57 | 61 | readSubdivs(); |
107 | 111 | int lat = reader.get3s(); |
108 | 112 | int width = reader.get2u() & 0x7fff; |
109 | 113 | int height = reader.get2u(); |
114 | int extFlags = flags; | |
115 | if (reader.getGMPOffset() > 0 && (height & 0x8000) != 0) { | |
116 | extFlags |= 0x1; | |
117 | height &= 0x7fff; | |
118 | } | |
110 | 119 | |
111 | 120 | if (count < levelDivs.length-1) |
112 | 121 | reader.get2u(); |
113 | 122 | |
114 | 123 | int endRgnOffset = reader.get3u(); |
115 | 124 | |
116 | SubdivData subdivData = new SubdivData(flags, | |
117 | // why??? lat, lon, 2*width, 2*height, | |
125 | SubdivData subdivData = new SubdivData(extFlags, | |
118 | 126 | lat, lon, width, height, |
119 | 127 | lastRgnOffset, endRgnOffset); |
120 | 128 | |
169 | 177 | int levelsPos = header.getMapLevelsPos(); |
170 | 178 | int levelsSize = header.getMapLevelsSize(); |
171 | 179 | reader.position(levelsPos); |
180 | byte[] levelsData = reader.get(levelsSize); | |
172 | 181 | |
173 | 182 | List<Subdivision[]> levelDivsList = new ArrayList<>(); |
174 | 183 | List<Zoom> mapLevelsList = new ArrayList<>(); |
175 | int end = levelsPos + levelsSize; | |
176 | while (reader.position() < end) { | |
177 | int level = reader.get1u(); | |
178 | int nbits = reader.get1u(); | |
179 | int ndivs = reader.get2u(); | |
180 | ||
184 | int key = 0; | |
185 | if (header.getLockFlag() != 0) { | |
186 | long pos = reader.position(); | |
187 | if (header.getHeaderLength() >= 0xAA) { | |
188 | reader.position((reader.getGMPOffset() + 0xAA)); | |
189 | key = reader.get4(); | |
190 | ||
191 | } | |
192 | reader.position(pos); | |
193 | ||
194 | demangle(levelsData, levelsSize, key); | |
195 | } | |
196 | ||
197 | int used = 0; | |
198 | while (used < levelsSize) { | |
199 | int level = levelsData[used++] & 0xff; | |
200 | int nbits = levelsData[used++] & 0xff; | |
201 | byte b1 = levelsData[used++]; | |
202 | byte b2 = levelsData[used++]; | |
203 | int ndivs = (b1 & 0xff) | ((b2 & 0xff) << 8); | |
181 | 204 | Subdivision[] divs = new Subdivision[ndivs]; |
182 | 205 | levelDivsList.add(divs); |
183 | 206 | level &= 0x7f; |
237 | 260 | } |
238 | 261 | return msgs.toArray(new String[msgs.size()]); |
239 | 262 | } |
263 | ||
264 | ||
265 | // code taken from GPXsee | |
266 | // origin is much older: | |
267 | // https://github.com/wuyongzheng/gimgtools/blob/92d015749e105c5fb8eb704ae503a5c7e51af2bd/gimgunlock.c#L15 | |
268 | private static void demangle(byte[] data, int size, int key) { | |
269 | final byte[] shuf = { | |
270 | 0xb, 0xc, 0xa, 0x0, | |
271 | 0x8, 0xf, 0x2, 0x1, | |
272 | 0x6, 0x4, 0x9, 0x3, | |
273 | 0xd, 0x5, 0x7, 0xe | |
274 | }; | |
275 | ||
276 | int sum = shuf[((key >> 24) + (key >> 16) + (key >> 8) + key) & 0xf]; | |
277 | int ringctr = 16; | |
278 | for (int i = 0; i < size; i++) { | |
279 | int upper = data[i] >> 4; | |
280 | int lower = data[i]; | |
281 | ||
282 | upper -= sum; | |
283 | upper -= key >> ringctr; | |
284 | upper -= shuf[(key >> ringctr) & 0xf]; | |
285 | ringctr = ringctr != 0 ? ringctr - 4 : 16; | |
286 | ||
287 | lower -= sum; | |
288 | lower -= key >> ringctr; | |
289 | lower -= shuf[(key >> ringctr) & 0xf]; | |
290 | ringctr = ringctr != 0 ? ringctr - 4 : 16; | |
291 | ||
292 | data[i] = (byte) (((upper << 4) & 0xf0) | (lower & 0xf)); | |
293 | } | |
294 | } | |
295 | ||
240 | 296 | } |
106 | 106 | * @param reader The header is read from here. |
107 | 107 | */ |
108 | 108 | protected void readFileHeader(ImgFileReader reader) throws ReadFailedException { |
109 | if (reader.position() != COMMON_HEADER_LEN) { | |
109 | if (reader.position() - reader.getGMPOffset() != COMMON_HEADER_LEN) { | |
110 | 110 | throw new ReadFailedException("Reader position not at expected header length", new IOException()); |
111 | 111 | } |
112 | 112 | int maxLat = reader.get3s(); |
357 | 357 | } |
358 | 358 | |
359 | 359 | /** |
360 | * Convert a string to a byte array. | |
361 | * @param s The string | |
362 | * @return A byte array. | |
363 | */ | |
364 | private static byte[] toByte(String s) { | |
365 | // NB: what character set should be used? | |
366 | return s.getBytes(); | |
367 | } | |
368 | ||
369 | /** | |
370 | 360 | * Convert to the one byte code that is used for the year. |
371 | 361 | * If the year is in the 1900, then subtract 1900 and add the result to 0x63, |
372 | 362 | * else subtract 2000. |
36 | 36 | |
37 | 37 | private final MapDataSource mapSource; |
38 | 38 | |
39 | // There is an absolute largest size as offsets are in 16 bits, we are | |
40 | // staying safely inside it however. | |
39 | // There is an absolute largest size as offsets are in 16 bits, but | |
40 | // the top bit of height is probably a flag for GMP "RoadRef" data. | |
41 | 41 | public static final int MAX_DIVISION_SIZE = 0x7fff; |
42 | 42 | |
43 | 43 | // Not a real limit. Note that the offset to the start of a section |
335 | 335 | } |
336 | 336 | |
337 | 337 | private static void lblInfo(ImgChannel chan, FileInfo info) { |
338 | try (LBLFileReader lblFile = new LBLFileReader(chan, false)) { | |
338 | try (LBLFileReader lblFile = new LBLFileReader(chan, false, 0)) { | |
339 | 339 | info.setCodePage(lblFile.getCodePage()); |
340 | 340 | info.setSortOrderId(lblFile.getSortOrderId()); |
341 | 341 | } |