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

Last change on this file since 8344 was 8344, checked in by bastiK, 9 years ago

applied #10454 - Mapbox "empty" tile (imagery with zoom level > 17) (patch by wiktorn)

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