source: josm/trunk/src/org/openstreetmap/josm/io/imagery/ImageryReader.java@ 9619

Last change on this file since 9619 was 9619, checked in by wiktorn, 8 years ago

Finish removal of tile-size workaround code started in [9617].

Now WMTS TileSources with other than 256px tile size should work properly.
See #12437, #12186

Fix name of "valid-georeference", so it will be properly loaded from preferences

  • Property svn:eol-style set to native
File size: 15.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.imagery;
3
4import java.io.BufferedReader;
5import java.io.Closeable;
6import java.io.IOException;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.HashMap;
10import java.util.List;
11import java.util.Map;
12import java.util.Objects;
13import java.util.Stack;
14
15import javax.xml.parsers.ParserConfigurationException;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.data.imagery.ImageryInfo;
19import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
20import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
21import org.openstreetmap.josm.data.imagery.Shape;
22import org.openstreetmap.josm.io.CachedFile;
23import org.openstreetmap.josm.tools.HttpClient;
24import org.openstreetmap.josm.tools.LanguageInfo;
25import org.openstreetmap.josm.tools.Utils;
26import org.xml.sax.Attributes;
27import org.xml.sax.InputSource;
28import org.xml.sax.SAXException;
29import org.xml.sax.helpers.DefaultHandler;
30
31public class ImageryReader implements Closeable {
32
33 private final String source;
34 private transient CachedFile cachedFile;
35 private transient boolean fastFail;
36
37 private enum State {
38 INIT, // initial state, should always be at the bottom of the stack
39 IMAGERY, // inside the imagery element
40 ENTRY, // inside an entry
41 ENTRY_ATTRIBUTE, // note we are inside an entry attribute to collect the character data
42 PROJECTIONS,
43 CODE,
44 BOUNDS,
45 SHAPE,
46 NO_TILE,
47 NO_TILESUM,
48 METADATA,
49 UNKNOWN, // element is not recognized in the current context
50 }
51
52 public ImageryReader(String source) {
53 this.source = source;
54 }
55
56 public List<ImageryInfo> parse() throws SAXException, IOException {
57 Parser parser = new Parser();
58 try {
59 cachedFile = new CachedFile(source);
60 cachedFile.setFastFail(fastFail);
61 try (BufferedReader in = cachedFile
62 .setMaxAge(CachedFile.DAYS)
63 .setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince)
64 .getContentReader()) {
65 InputSource is = new InputSource(in);
66 Utils.parseSafeSAX(is, parser);
67 return parser.entries;
68 }
69 } catch (SAXException e) {
70 throw e;
71 } catch (ParserConfigurationException e) {
72 Main.error(e); // broken SAXException chaining
73 throw new SAXException(e);
74 }
75 }
76
77 private static class Parser extends DefaultHandler {
78 private StringBuilder accumulator = new StringBuilder();
79
80 private Stack<State> states;
81
82 private List<ImageryInfo> entries;
83
84 /**
85 * Skip the current entry because it has mandatory attributes
86 * that this version of JOSM cannot process.
87 */
88 private boolean skipEntry;
89
90 private ImageryInfo entry;
91 private ImageryBounds bounds;
92 private Shape shape;
93 // language of last element, does only work for simple ENTRY_ATTRIBUTE's
94 private String lang;
95 private List<String> projections;
96 private Map<String, List<String>> noTileHeaders;
97 private Map<String, List<String>> noTileChecksums;
98 private Map<String, String> metadataHeaders;
99
100 @Override
101 public void startDocument() {
102 accumulator = new StringBuilder();
103 skipEntry = false;
104 states = new Stack<>();
105 states.push(State.INIT);
106 entries = new ArrayList<>();
107 entry = null;
108 bounds = null;
109 projections = null;
110 noTileHeaders = null;
111 noTileChecksums = null;
112 }
113
114 @Override
115 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
116 accumulator.setLength(0);
117 State newState = null;
118 switch (states.peek()) {
119 case INIT:
120 if ("imagery".equals(qName)) {
121 newState = State.IMAGERY;
122 }
123 break;
124 case IMAGERY:
125 if ("entry".equals(qName)) {
126 entry = new ImageryInfo();
127 skipEntry = false;
128 newState = State.ENTRY;
129 noTileHeaders = new HashMap<>();
130 noTileChecksums = new HashMap<>();
131 metadataHeaders = new HashMap<>();
132 }
133 break;
134 case ENTRY:
135 if (Arrays.asList(new String[] {
136 "name",
137 "id",
138 "type",
139 "description",
140 "default",
141 "url",
142 "eula",
143 "min-zoom",
144 "max-zoom",
145 "attribution-text",
146 "attribution-url",
147 "logo-image",
148 "logo-url",
149 "terms-of-use-text",
150 "terms-of-use-url",
151 "country-code",
152 "icon",
153 "tile-size",
154 "valid-georeference",
155 "epsg4326to3857Supported",
156 }).contains(qName)) {
157 newState = State.ENTRY_ATTRIBUTE;
158 lang = atts.getValue("lang");
159 } else if ("bounds".equals(qName)) {
160 try {
161 bounds = new ImageryBounds(
162 atts.getValue("min-lat") + ',' +
163 atts.getValue("min-lon") + ',' +
164 atts.getValue("max-lat") + ',' +
165 atts.getValue("max-lon"), ",");
166 } catch (IllegalArgumentException e) {
167 break;
168 }
169 newState = State.BOUNDS;
170 } else if ("projections".equals(qName)) {
171 projections = new ArrayList<>();
172 newState = State.PROJECTIONS;
173 } else if ("no-tile-header".equals(qName)) {
174 String name = atts.getValue("name");
175 List<String> l;
176 if (noTileHeaders.containsKey(name)) {
177 l = noTileHeaders.get(name);
178 } else {
179 l = new ArrayList<String>();
180 noTileHeaders.put(atts.getValue("name"), l);
181 }
182 l.add(atts.getValue("value"));
183 newState = State.NO_TILE;
184 } else if ("no-tile-checksum".equals(qName)) {
185 String type = atts.getValue("type");
186 List<String> l;
187 if (noTileChecksums.containsKey(type)) {
188 l = noTileChecksums.get(type);
189 } else {
190 l = new ArrayList<String>();
191 noTileChecksums.put(type, l);
192 }
193 l.add(atts.getValue("value"));
194 newState = State.NO_TILESUM;
195 } else if ("metadata-header".equals(qName)) {
196 metadataHeaders.put(atts.getValue("header-name"), atts.getValue("metadata-key"));
197 newState = State.METADATA;
198 }
199 break;
200 case BOUNDS:
201 if ("shape".equals(qName)) {
202 shape = new Shape();
203 newState = State.SHAPE;
204 }
205 break;
206 case SHAPE:
207 if ("point".equals(qName)) {
208 try {
209 shape.addPoint(atts.getValue("lat"), atts.getValue("lon"));
210 } catch (IllegalArgumentException e) {
211 break;
212 }
213 }
214 break;
215 case PROJECTIONS:
216 if ("code".equals(qName)) {
217 newState = State.CODE;
218 }
219 break;
220 }
221 /**
222 * Did not recognize the element, so the new state is UNKNOWN.
223 * This includes the case where we are already inside an unknown
224 * element, i.e. we do not try to understand the inner content
225 * of an unknown element, but wait till it's over.
226 */
227 if (newState == null) {
228 newState = State.UNKNOWN;
229 }
230 states.push(newState);
231 if (newState == State.UNKNOWN && "true".equals(atts.getValue("mandatory"))) {
232 skipEntry = true;
233 }
234 }
235
236 @Override
237 public void characters(char[] ch, int start, int length) {
238 accumulator.append(ch, start, length);
239 }
240
241 @Override
242 public void endElement(String namespaceURI, String qName, String rqName) {
243 switch (states.pop()) {
244 case INIT:
245 throw new RuntimeException("parsing error: more closing than opening elements");
246 case ENTRY:
247 if ("entry".equals(qName)) {
248 entry.setNoTileHeaders(noTileHeaders);
249 noTileHeaders = null;
250 entry.setNoTileChecksums(noTileChecksums);
251 noTileChecksums = null;
252 entry.setMetadataHeaders(metadataHeaders);
253 metadataHeaders = null;
254
255 if (!skipEntry) {
256 entries.add(entry);
257 }
258 entry = null;
259 }
260 break;
261 case ENTRY_ATTRIBUTE:
262 switch(qName) {
263 case "name":
264 entry.setName(lang == null ? LanguageInfo.getJOSMLocaleCode(null) : lang, accumulator.toString());
265 break;
266 case "description":
267 entry.setDescription(lang, accumulator.toString());
268 break;
269 case "id":
270 entry.setId(accumulator.toString());
271 break;
272 case "type":
273 boolean found = false;
274 for (ImageryType type : ImageryType.values()) {
275 if (Objects.equals(accumulator.toString(), type.getTypeString())) {
276 entry.setImageryType(type);
277 found = true;
278 break;
279 }
280 }
281 if (!found) {
282 skipEntry = true;
283 }
284 break;
285 case "default":
286 switch (accumulator.toString()) {
287 case "true":
288 entry.setDefaultEntry(true);
289 break;
290 case "false":
291 entry.setDefaultEntry(false);
292 break;
293 default:
294 skipEntry = true;
295 }
296 break;
297 case "url":
298 entry.setUrl(accumulator.toString());
299 break;
300 case "eula":
301 entry.setEulaAcceptanceRequired(accumulator.toString());
302 break;
303 case "min-zoom":
304 case "max-zoom":
305 Integer val = null;
306 try {
307 val = Integer.valueOf(accumulator.toString());
308 } catch (NumberFormatException e) {
309 val = null;
310 }
311 if (val == null) {
312 skipEntry = true;
313 } else {
314 if ("min-zoom".equals(qName)) {
315 entry.setDefaultMinZoom(val);
316 } else {
317 entry.setDefaultMaxZoom(val);
318 }
319 }
320 break;
321 case "attribution-text":
322 entry.setAttributionText(accumulator.toString());
323 break;
324 case "attribution-url":
325 entry.setAttributionLinkURL(accumulator.toString());
326 break;
327 case "logo-image":
328 entry.setAttributionImage(accumulator.toString());
329 break;
330 case "logo-url":
331 entry.setAttributionImageURL(accumulator.toString());
332 break;
333 case "terms-of-use-text":
334 entry.setTermsOfUseText(accumulator.toString());
335 break;
336 case "terms-of-use-url":
337 entry.setTermsOfUseURL(accumulator.toString());
338 break;
339 case "country-code":
340 entry.setCountryCode(accumulator.toString());
341 break;
342 case "icon":
343 entry.setIcon(accumulator.toString());
344 break;
345 case "tile-size":
346 Integer tileSize = null;
347 try {
348 tileSize = Integer.valueOf(accumulator.toString());
349 } catch (NumberFormatException e) {
350 tileSize = null;
351 }
352 if (tileSize == null) {
353 skipEntry = true;
354 } else {
355 entry.setTileSize(tileSize.intValue());
356 }
357 break;
358 case "valid-georeference":
359 entry.setGeoreferenceValid(Boolean.valueOf(accumulator.toString()));
360 break;
361 case "epsg4326to3857Supported":
362 entry.setEpsg4326To3857Supported(Boolean.valueOf(accumulator.toString()));
363 break;
364 }
365 break;
366 case BOUNDS:
367 entry.setBounds(bounds);
368 bounds = null;
369 break;
370 case SHAPE:
371 bounds.addShape(shape);
372 shape = null;
373 break;
374 case CODE:
375 projections.add(accumulator.toString());
376 break;
377 case PROJECTIONS:
378 entry.setServerProjections(projections);
379 projections = null;
380 break;
381 case NO_TILE:
382 break;
383
384 }
385 }
386 }
387
388 /**
389 * Sets whether opening HTTP connections should fail fast, i.e., whether a
390 * {@link HttpClient#setConnectTimeout(int) low connect timeout} should be used.
391 * @param fastFail whether opening HTTP connections should fail fast
392 * @see CachedFile#setFastFail(boolean)
393 */
394 public void setFastFail(boolean fastFail) {
395 this.fastFail = fastFail;
396 }
397
398 @Override
399 public void close() throws IOException {
400 Utils.close(cachedFile);
401 }
402}
Note: See TracBrowser for help on using the repository browser.