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

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

Introduce imagery-source warnings about alignment and reprojections.

Introduce new setting to maps.xsd:

  • valid-georeference - which marks imagery sources that are properly georeferenced (i.e. do not need imagery offset adjustments)
  • epsg4326to3857Supported - which marks imagery sources that might be safely queried using EPSG:3857 square BBOX, using EPSG:4326 projection

Make the AlignImageryPanel to show only, when valid-georeference is not set to true and provide the ability to hide this message ("do not show again"), which will be now remembered on per-source basis. Due to this changes, good imagery sources will not show warning to novice users and - experienced users will be presented with a warning, when they open an new imagery source which might be unaligned.

Introduce the ability to check "do not show again" the warning about EPSG:3857 to EPSG:4326 reprojections - as above - on per imagery basis.

All imagery sources should be now reviewed and set valid-georeference=true when appropriate.

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