source: josm/trunk/src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java@ 10000

Last change on this file since 10000 was 10000, checked in by Don-vip, 8 years ago

sonar - fix various issues

  • Property svn:eol-style set to native
File size: 38.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.imagery;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.GridBagLayout;
7import java.awt.Point;
8import java.io.ByteArrayInputStream;
9import java.io.IOException;
10import java.io.InputStream;
11import java.net.MalformedURLException;
12import java.net.URL;
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.Comparator;
17import java.util.HashSet;
18import java.util.List;
19import java.util.Map;
20import java.util.Set;
21import java.util.SortedSet;
22import java.util.Stack;
23import java.util.TreeSet;
24import java.util.concurrent.ConcurrentHashMap;
25import java.util.regex.Matcher;
26import java.util.regex.Pattern;
27
28import javax.swing.JPanel;
29import javax.swing.JScrollPane;
30import javax.swing.JTable;
31import javax.swing.ListSelectionModel;
32import javax.swing.table.AbstractTableModel;
33import javax.xml.namespace.QName;
34import javax.xml.stream.XMLInputFactory;
35import javax.xml.stream.XMLStreamException;
36import javax.xml.stream.XMLStreamReader;
37
38import org.openstreetmap.gui.jmapviewer.Coordinate;
39import org.openstreetmap.gui.jmapviewer.Tile;
40import org.openstreetmap.gui.jmapviewer.TileXY;
41import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
42import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource;
43import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;
44import org.openstreetmap.josm.Main;
45import org.openstreetmap.josm.data.coor.EastNorth;
46import org.openstreetmap.josm.data.coor.LatLon;
47import org.openstreetmap.josm.data.projection.Projection;
48import org.openstreetmap.josm.data.projection.Projections;
49import org.openstreetmap.josm.gui.ExtendedDialog;
50import org.openstreetmap.josm.gui.layer.NativeScaleLayer.ScaleList;
51import org.openstreetmap.josm.io.CachedFile;
52import org.openstreetmap.josm.tools.CheckParameterUtil;
53import org.openstreetmap.josm.tools.GBC;
54import org.openstreetmap.josm.tools.Utils;
55
56/**
57 * Tile Source handling WMS providers
58 *
59 * @author Wiktor Niesiobędzki
60 * @since 8526
61 */
62public class WMTSTileSource extends AbstractTMSTileSource implements TemplatedTileSource {
63 private static final String PATTERN_HEADER = "\\{header\\(([^,]+),([^}]+)\\)\\}";
64
65 private static final String URL_GET_ENCODING_PARAMS = "SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER={layer}&STYLE={style}&"
66 + "FORMAT={format}&tileMatrixSet={TileMatrixSet}&tileMatrix={TileMatrix}&tileRow={TileRow}&tileCol={TileCol}";
67
68 private static final String[] ALL_PATTERNS = {
69 PATTERN_HEADER,
70 };
71
72 private static final String OWS_NS_URL = "http://www.opengis.net/ows/1.1";
73 private static final String WMTS_NS_URL = "http://www.opengis.net/wmts/1.0";
74 private static final String XLINK_NS_URL = "http://www.w3.org/1999/xlink";
75
76 private static class TileMatrix {
77 private String identifier;
78 private double scaleDenominator;
79 private EastNorth topLeftCorner;
80 private int tileWidth;
81 private int tileHeight;
82 private int matrixWidth = -1;
83 private int matrixHeight = -1;
84 }
85
86 private static class TileMatrixSetBuilder {
87 SortedSet<TileMatrix> tileMatrix = new TreeSet<>(new Comparator<TileMatrix>() {
88 @Override
89 public int compare(TileMatrix o1, TileMatrix o2) {
90 // reverse the order, so it will be from greatest (lowest zoom level) to lowest value (highest zoom level)
91 return -1 * Double.compare(o1.scaleDenominator, o2.scaleDenominator);
92 }
93 }); // sorted by zoom level
94 private String crs;
95 private String identifier;
96
97 TileMatrixSet build() {
98 return new TileMatrixSet(this);
99 }
100 }
101
102 private static class TileMatrixSet {
103
104 private final List<TileMatrix> tileMatrix;
105 private final String crs;
106 private final String identifier;
107
108 TileMatrixSet(TileMatrixSet tileMatrixSet) {
109 if (tileMatrixSet != null) {
110 tileMatrix = new ArrayList<>(tileMatrixSet.tileMatrix);
111 crs = tileMatrixSet.crs;
112 identifier = tileMatrixSet.identifier;
113 } else {
114 tileMatrix = Collections.emptyList();
115 crs = null;
116 identifier = null;
117 }
118 }
119
120 TileMatrixSet(TileMatrixSetBuilder builder) {
121 tileMatrix = new ArrayList<>(builder.tileMatrix);
122 crs = builder.crs;
123 identifier = builder.identifier;
124 }
125
126 }
127
128 private static class Layer {
129 private String format;
130 private String name;
131 private TileMatrixSet tileMatrixSet;
132 private String baseUrl;
133 private String style;
134 public Collection<String> tileMatrixSetLinks = new ArrayList<>();
135
136 Layer(Layer l) {
137 if (l != null) {
138 format = l.format;
139 name = l.name;
140 baseUrl = l.baseUrl;
141 style = l.style;
142 tileMatrixSet = new TileMatrixSet(l.tileMatrixSet);
143 }
144 }
145
146 Layer() {
147 }
148 }
149
150 private enum TransferMode {
151 KVP("KVP"),
152 REST("RESTful");
153
154 private final String typeString;
155
156 TransferMode(String urlString) {
157 this.typeString = urlString;
158 }
159
160 private String getTypeString() {
161 return typeString;
162 }
163
164 private static TransferMode fromString(String s) {
165 for (TransferMode type : TransferMode.values()) {
166 if (type.getTypeString().equals(s)) {
167 return type;
168 }
169 }
170 return null;
171 }
172 }
173
174 private static final class SelectLayerDialog extends ExtendedDialog {
175 private final transient Layer[] layers;
176 private final JTable list;
177
178 SelectLayerDialog(Collection<Layer> layers) {
179 super(Main.parent, tr("Select WMTS layer"), new String[]{tr("Add layers"), tr("Cancel")});
180 this.layers = layers.toArray(new Layer[]{});
181 //getLayersTable(layers, Main.getProjection())
182 this.list = new JTable(
183 new AbstractTableModel() {
184 @Override
185 public Object getValueAt(int rowIndex, int columnIndex) {
186 switch (columnIndex) {
187 case 0:
188 return SelectLayerDialog.this.layers[rowIndex].name;
189 case 1:
190 return SelectLayerDialog.this.layers[rowIndex].tileMatrixSet.crs;
191 case 2:
192 return SelectLayerDialog.this.layers[rowIndex].tileMatrixSet.identifier;
193 default:
194 throw new IllegalArgumentException();
195 }
196 }
197
198 @Override
199 public int getRowCount() {
200 return SelectLayerDialog.this.layers.length;
201 }
202
203 @Override
204 public int getColumnCount() {
205 return 3;
206 }
207
208 @Override
209 public String getColumnName(int column) {
210 switch (column) {
211 case 0: return tr("Layer name");
212 case 1: return tr("Projection");
213 case 2: return tr("Matrix set identifier");
214 default:
215 throw new IllegalArgumentException();
216 }
217 }
218
219 @Override
220 public boolean isCellEditable(int row, int column) {
221 return false;
222 }
223 });
224 this.list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
225 this.list.setRowSelectionAllowed(true);
226 this.list.setColumnSelectionAllowed(false);
227 JPanel panel = new JPanel(new GridBagLayout());
228 panel.add(new JScrollPane(this.list), GBC.eol().fill());
229 setContent(panel);
230 }
231
232 public Layer getSelectedLayer() {
233 int index = list.getSelectedRow();
234 if (index < 0) {
235 return null; //nothing selected
236 }
237 return layers[index];
238 }
239 }
240
241 private final Map<String, String> headers = new ConcurrentHashMap<>();
242 private Collection<Layer> layers;
243 private Layer currentLayer;
244 private TileMatrixSet currentTileMatrixSet;
245 private double crsScale;
246 private TransferMode transferMode;
247
248 private ScaleList nativeScaleList;
249
250 /**
251 * Creates a tile source based on imagery info
252 * @param info imagery info
253 * @throws IOException if any I/O error occurs
254 */
255 public WMTSTileSource(ImageryInfo info) throws IOException {
256 super(info);
257 this.baseUrl = normalizeCapabilitiesUrl(handleTemplate(info.getUrl()));
258 this.layers = getCapabilities();
259 if (this.layers.isEmpty())
260 throw new IllegalArgumentException(tr("No layers defined by getCapabilities document: {0}", info.getUrl()));
261 }
262
263 private Layer userSelectLayer(Collection<Layer> layers) {
264 if (layers.size() == 1)
265 return layers.iterator().next();
266 Layer ret = null;
267
268 final SelectLayerDialog layerSelection = new SelectLayerDialog(layers);
269 if (layerSelection.showDialog().getValue() == 1) {
270 ret = layerSelection.getSelectedLayer();
271 // TODO: save layer information into ImageryInfo / ImageryPreferences?
272 }
273 if (ret == null) {
274 // user canceled operation or did not choose any layer
275 throw new IllegalArgumentException(tr("No layer selected"));
276 }
277 return ret;
278 }
279
280 private String handleTemplate(String url) {
281 Pattern pattern = Pattern.compile(PATTERN_HEADER);
282 StringBuffer output = new StringBuffer(); // NOSONAR
283 Matcher matcher = pattern.matcher(url);
284 while (matcher.find()) {
285 this.headers.put(matcher.group(1), matcher.group(2));
286 matcher.appendReplacement(output, "");
287 }
288 matcher.appendTail(output);
289 return output.toString();
290 }
291
292 private Collection<Layer> getCapabilities() {
293 XMLInputFactory factory = XMLInputFactory.newFactory();
294 // do not try to load external entities, nor validate the XML
295 factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
296 factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
297 factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
298
299 try (CachedFile cf = new CachedFile(baseUrl); InputStream in = cf.setHttpHeaders(headers).
300 setMaxAge(7 * CachedFile.DAYS).
301 setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince).
302 getInputStream()) {
303 byte[] data = Utils.readBytesFromStream(in);
304 if (data == null || data.length == 0) {
305 throw new IllegalArgumentException("Could not read data from: " + baseUrl);
306 }
307 XMLStreamReader reader = factory.createXMLStreamReader(new ByteArrayInputStream(data));
308
309 Collection<Layer> ret = new ArrayList<>();
310 for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) {
311 if (event == XMLStreamReader.START_ELEMENT) {
312 if (new QName(OWS_NS_URL, "OperationsMetadata").equals(reader.getName())) {
313 parseOperationMetadata(reader);
314 }
315
316 if (new QName(WMTS_NS_URL, "Contents").equals(reader.getName())) {
317 ret = parseContents(reader);
318 }
319 }
320 }
321 return ret;
322 } catch (Exception e) {
323 throw new IllegalArgumentException(e);
324 }
325 }
326
327 /**
328 * Parse Contents tag. Renturns when reader reaches Contents closing tag
329 *
330 * @param reader StAX reader instance
331 * @return collection of layers within contents with properly linked TileMatrixSets
332 * @throws XMLStreamException See {@link XMLStreamReader}
333 */
334 private static Collection<Layer> parseContents(XMLStreamReader reader) throws XMLStreamException {
335 Map<String, TileMatrixSet> matrixSetById = new ConcurrentHashMap<>();
336 Collection<Layer> layers = new ArrayList<>();
337 for (int event = reader.getEventType();
338 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "Contents").equals(reader.getName()));
339 event = reader.next()) {
340 if (event == XMLStreamReader.START_ELEMENT) {
341 if (new QName(WMTS_NS_URL, "Layer").equals(reader.getName())) {
342 layers.add(parseLayer(reader));
343 }
344 if (new QName(WMTS_NS_URL, "TileMatrixSet").equals(reader.getName())) {
345 TileMatrixSet entry = parseTileMatrixSet(reader);
346 matrixSetById.put(entry.identifier, entry);
347 }
348 }
349 }
350 Collection<Layer> ret = new ArrayList<>();
351 // link layers to matrix sets
352 for (Layer l: layers) {
353 for (String tileMatrixId: l.tileMatrixSetLinks) {
354 Layer newLayer = new Layer(l); // create a new layer object for each tile matrix set supported
355 newLayer.tileMatrixSet = matrixSetById.get(tileMatrixId);
356 ret.add(newLayer);
357 }
358 }
359 return ret;
360 }
361
362 /**
363 * Parse Layer tag. Returns when reader will reach Layer closing tag
364 *
365 * @param reader StAX reader instance
366 * @return Layer object, with tileMatrixSetLinks and no tileMatrixSet attribute set.
367 * @throws XMLStreamException See {@link XMLStreamReader}
368 */
369 private static Layer parseLayer(XMLStreamReader reader) throws XMLStreamException {
370 Layer layer = new Layer();
371 Stack<QName> tagStack = new Stack<>();
372
373 for (int event = reader.getEventType();
374 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "Layer").equals(reader.getName()));
375 event = reader.next()) {
376 if (event == XMLStreamReader.START_ELEMENT) {
377 tagStack.push(reader.getName());
378 if (tagStack.size() == 2) {
379 if (new QName(WMTS_NS_URL, "Format").equals(reader.getName())) {
380 layer.format = reader.getElementText();
381 } else if (new QName(OWS_NS_URL, "Identifier").equals(reader.getName())) {
382 layer.name = reader.getElementText();
383 } else if (new QName(WMTS_NS_URL, "ResourceURL").equals(reader.getName()) &&
384 "tile".equals(reader.getAttributeValue("", "resourceType"))) {
385 layer.baseUrl = reader.getAttributeValue("", "template");
386 } else if (new QName(WMTS_NS_URL, "Style").equals(reader.getName()) &&
387 "true".equals(reader.getAttributeValue("", "isDefault"))) {
388 if (moveReaderToTag(reader, new QName[] {new QName(OWS_NS_URL, "Identifier")})) {
389 layer.style = reader.getElementText();
390 tagStack.push(reader.getName()); // keep tagStack in sync
391 }
392 } else if (new QName(WMTS_NS_URL, "TileMatrixSetLink").equals(reader.getName())) {
393 layer.tileMatrixSetLinks.add(praseTileMatrixSetLink(reader));
394 } else {
395 moveReaderToEndCurrentTag(reader);
396 }
397 }
398 }
399 // need to get event type from reader, as parsing might have change position of reader
400 if (reader.getEventType() == XMLStreamReader.END_ELEMENT) {
401 QName start = tagStack.pop();
402 if (!start.equals(reader.getName())) {
403 throw new IllegalStateException(tr("WMTS Parser error - start element {0} has different name than end element {2}",
404 start, reader.getName()));
405 }
406 }
407 }
408 if (layer.style == null) {
409 layer.style = "";
410 }
411 return layer;
412 }
413
414 /**
415 * Moves the reader to the closing tag of current tag.
416 * @param reader XML stream reader positioned on XMLStreamReader.START_ELEMENT
417 * @throws XMLStreamException when parse exception occurs
418 */
419 private static void moveReaderToEndCurrentTag(XMLStreamReader reader) throws XMLStreamException {
420 int level = 0;
421 QName tag = reader.getName();
422 for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) {
423 switch (event) {
424 case XMLStreamReader.START_ELEMENT:
425 level += 1;
426 break;
427 case XMLStreamReader.END_ELEMENT:
428 level -= 1;
429 if (level == 0 && tag.equals(reader.getName())) {
430 return;
431 }
432 }
433 if (level < 0) {
434 throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag");
435 }
436 }
437 throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag");
438
439 }
440
441 /**
442 * Gets TileMatrixSetLink value. Returns when reader is on TileMatrixSetLink closing tag
443 *
444 * @param reader StAX reader instance
445 * @return TileMatrixSetLink identifier
446 * @throws XMLStreamException See {@link XMLStreamReader}
447 */
448 private static String praseTileMatrixSetLink(XMLStreamReader reader) throws XMLStreamException {
449 String ret = null;
450 for (int event = reader.getEventType();
451 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT &&
452 new QName(WMTS_NS_URL, "TileMatrixSetLink").equals(reader.getName()));
453 event = reader.next()) {
454 if (event == XMLStreamReader.START_ELEMENT && new QName(WMTS_NS_URL, "TileMatrixSet").equals(reader.getName())) {
455 ret = reader.getElementText();
456 }
457 }
458 return ret;
459 }
460
461 /**
462 * Parses TileMatrixSet section. Returns when reader is on TileMatrixSet closing tag
463 * @param reader StAX reader instance
464 * @return TileMatrixSet object
465 * @throws XMLStreamException See {@link XMLStreamReader}
466 */
467 private static TileMatrixSet parseTileMatrixSet(XMLStreamReader reader) throws XMLStreamException {
468 TileMatrixSetBuilder matrixSet = new TileMatrixSetBuilder();
469 for (int event = reader.getEventType();
470 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "TileMatrixSet").equals(reader.getName()));
471 event = reader.next()) {
472 if (event == XMLStreamReader.START_ELEMENT) {
473 if (new QName(OWS_NS_URL, "Identifier").equals(reader.getName())) {
474 matrixSet.identifier = reader.getElementText();
475 }
476 if (new QName(OWS_NS_URL, "SupportedCRS").equals(reader.getName())) {
477 matrixSet.crs = crsToCode(reader.getElementText());
478 }
479 if (new QName(WMTS_NS_URL, "TileMatrix").equals(reader.getName())) {
480 matrixSet.tileMatrix.add(parseTileMatrix(reader, matrixSet.crs));
481 }
482 }
483 }
484 return matrixSet.build();
485 }
486
487 /**
488 * Parses TileMatrix section. Returns when reader is on TileMatrix closing tag.
489 * @param reader StAX reader instance
490 * @param matrixCrs projection used by this matrix
491 * @return TileMatrix object
492 * @throws XMLStreamException See {@link XMLStreamReader}
493 */
494 private static TileMatrix parseTileMatrix(XMLStreamReader reader, String matrixCrs) throws XMLStreamException {
495 Projection matrixProj = Projections.getProjectionByCode(matrixCrs);
496 TileMatrix ret = new TileMatrix();
497
498 if (matrixProj == null) {
499 // use current projection if none found. Maybe user is using custom string
500 matrixProj = Main.getProjection();
501 }
502 for (int event = reader.getEventType();
503 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "TileMatrix").equals(reader.getName()));
504 event = reader.next()) {
505 if (event == XMLStreamReader.START_ELEMENT) {
506 if (new QName(OWS_NS_URL, "Identifier").equals(reader.getName())) {
507 ret.identifier = reader.getElementText();
508 }
509 if (new QName(WMTS_NS_URL, "ScaleDenominator").equals(reader.getName())) {
510 ret.scaleDenominator = Double.parseDouble(reader.getElementText());
511 }
512 if (new QName(WMTS_NS_URL, "TopLeftCorner").equals(reader.getName())) {
513 String[] topLeftCorner = reader.getElementText().split(" ");
514 if (matrixProj.switchXY()) {
515 ret.topLeftCorner = new EastNorth(Double.parseDouble(topLeftCorner[1]), Double.parseDouble(topLeftCorner[0]));
516 } else {
517 ret.topLeftCorner = new EastNorth(Double.parseDouble(topLeftCorner[0]), Double.parseDouble(topLeftCorner[1]));
518 }
519 }
520 if (new QName(WMTS_NS_URL, "TileHeight").equals(reader.getName())) {
521 ret.tileHeight = Integer.parseInt(reader.getElementText());
522 }
523 if (new QName(WMTS_NS_URL, "TileWidth").equals(reader.getName())) {
524 ret.tileWidth = Integer.parseInt(reader.getElementText());
525 }
526 if (new QName(WMTS_NS_URL, "MatrixHeight").equals(reader.getName())) {
527 ret.matrixHeight = Integer.parseInt(reader.getElementText());
528 }
529 if (new QName(WMTS_NS_URL, "MatrixWidth").equals(reader.getName())) {
530 ret.matrixWidth = Integer.parseInt(reader.getElementText());
531 }
532 }
533 }
534 if (ret.tileHeight != ret.tileWidth) {
535 throw new AssertionError(tr("Only square tiles are supported. {0}x{1} returned by server for TileMatrix identifier {2}",
536 ret.tileHeight, ret.tileWidth, ret.identifier));
537 }
538 return ret;
539 }
540
541 /**
542 * Parses OperationMetadata section. Returns when reader is on OperationsMetadata closing tag.
543 * Sets this.baseUrl and this.transferMode
544 *
545 * @param reader StAX reader instance
546 * @throws XMLStreamException See {@link XMLStreamReader}
547 */
548 private void parseOperationMetadata(XMLStreamReader reader) throws XMLStreamException {
549 for (int event = reader.getEventType();
550 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT &&
551 new QName(OWS_NS_URL, "OperationsMetadata").equals(reader.getName()));
552 event = reader.next()) {
553 if (event == XMLStreamReader.START_ELEMENT) {
554 if (new QName(OWS_NS_URL, "Operation").equals(reader.getName()) && "GetTile".equals(reader.getAttributeValue("", "name")) &&
555 moveReaderToTag(reader, new QName[]{
556 new QName(OWS_NS_URL, "DCP"),
557 new QName(OWS_NS_URL, "HTTP"),
558 new QName(OWS_NS_URL, "Get"),
559
560 })) {
561 this.baseUrl = reader.getAttributeValue(XLINK_NS_URL, "href");
562 this.transferMode = getTransferMode(reader);
563 }
564 }
565 }
566 }
567
568 /**
569 * Parses Operation[@name='GetTile']/DCP/HTTP/Get section. Returns when reader is on Get closing tag.
570 * @param reader StAX reader instance
571 * @return TransferMode coded in this section
572 * @throws XMLStreamException See {@link XMLStreamReader}
573 */
574 private static TransferMode getTransferMode(XMLStreamReader reader) throws XMLStreamException {
575 QName getQname = new QName(OWS_NS_URL, "Get");
576
577 Utils.ensure(getQname.equals(reader.getName()), "WMTS Parser state invalid. Expected element %s, got %s",
578 getQname, reader.getName());
579 for (int event = reader.getEventType();
580 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && getQname.equals(reader.getName()));
581 event = reader.next()) {
582 if (event == XMLStreamReader.START_ELEMENT && new QName(OWS_NS_URL, "Constraint").equals(reader.getName())
583 && "GetEncoding".equals(reader.getAttributeValue("", "name"))) {
584 moveReaderToTag(reader, new QName[]{
585 new QName(OWS_NS_URL, "AllowedValues"),
586 new QName(OWS_NS_URL, "Value")
587 });
588 return TransferMode.fromString(reader.getElementText());
589 }
590 }
591 return null;
592 }
593
594 /**
595 * Moves reader to first occurrence of the structure equivalent of Xpath tags[0]/tags[1]../tags[n]. If fails to find
596 * moves the reader to the closing tag of current tag
597 *
598 * @param reader StAX reader instance
599 * @param tags array of tags
600 * @return true if tag was found, false otherwise
601 * @throws XMLStreamException See {@link XMLStreamReader}
602 */
603 private static boolean moveReaderToTag(XMLStreamReader reader, QName[] tags) throws XMLStreamException {
604 QName stopTag = reader.getName();
605 int currentLevel = 0;
606 QName searchTag = tags[currentLevel];
607 QName parentTag = null;
608 QName skipTag = null;
609
610 for (int event = 0; //skip current element, so we will not skip it as a whole
611 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && stopTag.equals(reader.getName()));
612 event = reader.next()) {
613 if (event == XMLStreamReader.END_ELEMENT && skipTag != null && skipTag.equals(reader.getName())) {
614 skipTag = null;
615 }
616 if (skipTag == null) {
617 if (event == XMLStreamReader.START_ELEMENT) {
618 if (searchTag.equals(reader.getName())) {
619 currentLevel += 1;
620 if (currentLevel >= tags.length) {
621 return true; // found!
622 }
623 parentTag = searchTag;
624 searchTag = tags[currentLevel];
625 } else {
626 skipTag = reader.getName();
627 }
628 }
629
630 if (event == XMLStreamReader.END_ELEMENT && parentTag != null && parentTag.equals(reader.getName())) {
631 currentLevel -= 1;
632 searchTag = parentTag;
633 if (currentLevel >= 0) {
634 parentTag = tags[currentLevel];
635 } else {
636 parentTag = null;
637 }
638 }
639 }
640 }
641 return false;
642 }
643
644 private static String normalizeCapabilitiesUrl(String url) throws MalformedURLException {
645 URL inUrl = new URL(url);
646 URL ret = new URL(inUrl.getProtocol(), inUrl.getHost(), inUrl.getPort(), inUrl.getFile());
647 return ret.toExternalForm();
648 }
649
650 private static String crsToCode(String crsIdentifier) {
651 if (crsIdentifier.startsWith("urn:ogc:def:crs:")) {
652 return crsIdentifier.replaceFirst("urn:ogc:def:crs:([^:]*):.*:(.*)$", "$1:$2");
653 }
654 return crsIdentifier;
655 }
656
657 /**
658 * Initializes projection for this TileSource with projection
659 * @param proj projection to be used by this TileSource
660 */
661 public void initProjection(Projection proj) {
662 // getLayers will return only layers matching the name, if the user already choose the layer
663 // so we will not ask the user again to chose the layer, if he just changes projection
664 Collection<Layer> candidates = getLayers(currentLayer != null ? currentLayer.name : null, proj.toCode());
665 if (!candidates.isEmpty()) {
666 Layer newLayer = userSelectLayer(candidates);
667 if (newLayer != null) {
668 this.currentTileMatrixSet = newLayer.tileMatrixSet;
669 this.currentLayer = newLayer;
670 Collection<Double> scales = new ArrayList<>(currentTileMatrixSet.tileMatrix.size());
671 for (TileMatrix tileMatrix : currentTileMatrixSet.tileMatrix) {
672 scales.add(tileMatrix.scaleDenominator * 0.28e-03);
673 }
674 this.nativeScaleList = new ScaleList(scales);
675 }
676 }
677 this.crsScale = getTileSize() * 0.28e-03 / proj.getMetersPerUnit();
678 }
679
680 /**
681 *
682 * @param name of the layer to match
683 * @param projectionCode projection code to match
684 * @return Collection of layers matching the name of the layer and projection, or only projection if name is not provided
685 */
686 private Collection<Layer> getLayers(String name, String projectionCode) {
687 Collection<Layer> ret = new ArrayList<>();
688 if (this.layers != null) {
689 for (Layer layer: this.layers) {
690 if ((name == null || name.equals(layer.name)) && (projectionCode == null || projectionCode.equals(layer.tileMatrixSet.crs))) {
691 ret.add(layer);
692 }
693 }
694 }
695 return ret;
696 }
697
698 @Override
699 public int getTileSize() {
700 // no support for non-square tiles (tileHeight != tileWidth)
701 // and for different tile sizes at different zoom levels
702 Collection<Layer> layers = getLayers(null, Main.getProjection().toCode());
703 if (!layers.isEmpty()) {
704 return layers.iterator().next().tileMatrixSet.tileMatrix.get(0).tileHeight;
705 }
706 // if no layers is found, fallback to default mercator tile size. Maybe it will work
707 Main.warn("WMTS: Could not determine tile size. Using default tile size of: {0}", getDefaultTileSize());
708 return getDefaultTileSize();
709 }
710
711 @Override
712 public String getTileUrl(int zoom, int tilex, int tiley) {
713 String url;
714 if (currentLayer == null) {
715 return "";
716 }
717
718 if (currentLayer.baseUrl != null && transferMode == null) {
719 url = currentLayer.baseUrl;
720 } else {
721 switch (transferMode) {
722 case KVP:
723 url = baseUrl + URL_GET_ENCODING_PARAMS;
724 break;
725 case REST:
726 url = currentLayer.baseUrl;
727 break;
728 default:
729 url = "";
730 break;
731 }
732 }
733
734 TileMatrix tileMatrix = getTileMatrix(zoom);
735
736 if (tileMatrix == null) {
737 return ""; // no matrix, probably unsupported CRS selected.
738 }
739
740 return url.replaceAll("\\{layer\\}", this.currentLayer.name)
741 .replaceAll("\\{format\\}", this.currentLayer.format)
742 .replaceAll("\\{TileMatrixSet\\}", this.currentTileMatrixSet.identifier)
743 .replaceAll("\\{TileMatrix\\}", tileMatrix.identifier)
744 .replaceAll("\\{TileRow\\}", Integer.toString(tiley))
745 .replaceAll("\\{TileCol\\}", Integer.toString(tilex))
746 .replaceAll("(?i)\\{style\\}", this.currentLayer.style);
747 }
748
749 /**
750 *
751 * @param zoom zoom level
752 * @return TileMatrix that's working on this zoom level
753 */
754 private TileMatrix getTileMatrix(int zoom) {
755 if (zoom > getMaxZoom()) {
756 return null;
757 }
758 if (zoom < 0) {
759 return null;
760 }
761 return this.currentTileMatrixSet.tileMatrix.get(zoom);
762 }
763
764 @Override
765 public double getDistance(double lat1, double lon1, double lat2, double lon2) {
766 throw new UnsupportedOperationException("Not implemented");
767 }
768
769 @Override
770 public ICoordinate tileXYToLatLon(Tile tile) {
771 return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom());
772 }
773
774 @Override
775 public ICoordinate tileXYToLatLon(TileXY xy, int zoom) {
776 return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom);
777 }
778
779 @Override
780 public ICoordinate tileXYToLatLon(int x, int y, int zoom) {
781 TileMatrix matrix = getTileMatrix(zoom);
782 if (matrix == null) {
783 return Main.getProjection().getWorldBoundsLatLon().getCenter().toCoordinate();
784 }
785 double scale = matrix.scaleDenominator * this.crsScale;
786 EastNorth ret = new EastNorth(matrix.topLeftCorner.east() + x * scale, matrix.topLeftCorner.north() - y * scale);
787 return Main.getProjection().eastNorth2latlon(ret).toCoordinate();
788 }
789
790 @Override
791 public TileXY latLonToTileXY(double lat, double lon, int zoom) {
792 TileMatrix matrix = getTileMatrix(zoom);
793 if (matrix == null) {
794 return new TileXY(0, 0);
795 }
796
797 Projection proj = Main.getProjection();
798 EastNorth enPoint = proj.latlon2eastNorth(new LatLon(lat, lon));
799 double scale = matrix.scaleDenominator * this.crsScale;
800 return new TileXY(
801 (enPoint.east() - matrix.topLeftCorner.east()) / scale,
802 (matrix.topLeftCorner.north() - enPoint.north()) / scale
803 );
804 }
805
806 @Override
807 public TileXY latLonToTileXY(ICoordinate point, int zoom) {
808 return latLonToTileXY(point.getLat(), point.getLon(), zoom);
809 }
810
811 @Override
812 public int getTileXMax(int zoom) {
813 return getTileXMax(zoom, Main.getProjection());
814 }
815
816 @Override
817 public int getTileXMin(int zoom) {
818 return 0;
819 }
820
821 @Override
822 public int getTileYMax(int zoom) {
823 return getTileYMax(zoom, Main.getProjection());
824 }
825
826 @Override
827 public int getTileYMin(int zoom) {
828 return 0;
829 }
830
831 @Override
832 public Point latLonToXY(double lat, double lon, int zoom) {
833 TileMatrix matrix = getTileMatrix(zoom);
834 if (matrix == null) {
835 return new Point(0, 0);
836 }
837 double scale = matrix.scaleDenominator * this.crsScale;
838 EastNorth point = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));
839 return new Point(
840 (int) Math.round((point.east() - matrix.topLeftCorner.east()) / scale),
841 (int) Math.round((matrix.topLeftCorner.north() - point.north()) / scale)
842 );
843 }
844
845 @Override
846 public Point latLonToXY(ICoordinate point, int zoom) {
847 return latLonToXY(point.getLat(), point.getLon(), zoom);
848 }
849
850 @Override
851 public Coordinate xyToLatLon(Point point, int zoom) {
852 return xyToLatLon(point.x, point.y, zoom);
853 }
854
855 @Override
856 public Coordinate xyToLatLon(int x, int y, int zoom) {
857 TileMatrix matrix = getTileMatrix(zoom);
858 if (matrix == null) {
859 return new Coordinate(0, 0);
860 }
861 double scale = matrix.scaleDenominator * this.crsScale;
862 Projection proj = Main.getProjection();
863 EastNorth ret = new EastNorth(
864 matrix.topLeftCorner.east() + x * scale,
865 matrix.topLeftCorner.north() - y * scale
866 );
867 LatLon ll = proj.eastNorth2latlon(ret);
868 return new Coordinate(ll.lat(), ll.lon());
869 }
870
871 @Override
872 public Map<String, String> getHeaders() {
873 return headers;
874 }
875
876 @Override
877 public int getMaxZoom() {
878 if (this.currentTileMatrixSet != null) {
879 return this.currentTileMatrixSet.tileMatrix.size()-1;
880 }
881 return 0;
882 }
883
884 @Override
885 public String getTileId(int zoom, int tilex, int tiley) {
886 return getTileUrl(zoom, tilex, tiley);
887 }
888
889 /**
890 * Checks if url is acceptable by this Tile Source
891 * @param url URL to check
892 */
893 public static void checkUrl(String url) {
894 CheckParameterUtil.ensureParameterNotNull(url, "url");
895 Matcher m = Pattern.compile("\\{[^}]*\\}").matcher(url);
896 while (m.find()) {
897 boolean isSupportedPattern = false;
898 for (String pattern : ALL_PATTERNS) {
899 if (m.group().matches(pattern)) {
900 isSupportedPattern = true;
901 break;
902 }
903 }
904 if (!isSupportedPattern) {
905 throw new IllegalArgumentException(
906 tr("{0} is not a valid WMS argument. Please check this server URL:\n{1}", m.group(), url));
907 }
908 }
909 }
910
911 /**
912 * @return set of projection codes that this TileSource supports
913 */
914 public Set<String> getSupportedProjections() {
915 Set<String> ret = new HashSet<>();
916 if (currentLayer == null) {
917 for (Layer layer: this.layers) {
918 ret.add(layer.tileMatrixSet.crs);
919 }
920 } else {
921 for (Layer layer: this.layers) {
922 if (currentLayer.name.equals(layer.name)) {
923 ret.add(layer.tileMatrixSet.crs);
924 }
925 }
926 }
927 return ret;
928 }
929
930 private int getTileYMax(int zoom, Projection proj) {
931 TileMatrix matrix = getTileMatrix(zoom);
932 if (matrix == null) {
933 return 0;
934 }
935
936 if (matrix.matrixHeight != -1) {
937 return matrix.matrixHeight;
938 }
939
940 double scale = matrix.scaleDenominator * this.crsScale;
941 EastNorth min = matrix.topLeftCorner;
942 EastNorth max = proj.latlon2eastNorth(proj.getWorldBoundsLatLon().getMax());
943 return (int) Math.ceil(Math.abs(max.north() - min.north()) / scale);
944 }
945
946 private int getTileXMax(int zoom, Projection proj) {
947 TileMatrix matrix = getTileMatrix(zoom);
948 if (matrix == null) {
949 return 0;
950 }
951 if (matrix.matrixWidth != -1) {
952 return matrix.matrixWidth;
953 }
954
955 double scale = matrix.scaleDenominator * this.crsScale;
956 EastNorth min = matrix.topLeftCorner;
957 EastNorth max = proj.latlon2eastNorth(proj.getWorldBoundsLatLon().getMax());
958 return (int) Math.ceil(Math.abs(max.east() - min.east()) / scale);
959 }
960
961 /**
962 * Get native scales of tile source.
963 * @return {@link ScaleList} of native scales
964 */
965 public ScaleList getNativeScales() {
966 return nativeScaleList;
967 }
968
969}
Note: See TracBrowser for help on using the repository browser.