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

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

Fix NPE and hang when adding erroneus imageries.

When user points to a getCapabilities document that is XHTML file, parser was by default loading external entities. For XHTML, it was trying to load for ex.: http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd - which takes about 15 seconds to load. It referes to other external entities, that take no shorter to load, hence was the hang.

Now factory will not try to load external resources, nor validate the XML, so this should prevent such errors.

See: #12450

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