source: josm/trunk/src/org/openstreetmap/josm/data/imagery/WMTSTileSource.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: 34.8 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.TMSTileSource;
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 TMSTileSource 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 InputStream in = new CachedFile(baseUrl).
271 setHttpHeaders(headers).
272 setMaxAge(7 * CachedFile.DAYS).
273 setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince).
274 getInputStream();
275 try {
276 byte[] data = Utils.readBytesFromStream(in);
277 if (data == null || data.length == 0) {
278 throw new IllegalArgumentException("Could not read data from: " + baseUrl);
279 }
280 XMLStreamReader reader = factory.createXMLStreamReader(new ByteArrayInputStream(data));
281
282 Collection<Layer> ret = null;
283 for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) {
284 if (event == XMLStreamReader.START_ELEMENT) {
285 if (new QName(OWS_NS_URL, "OperationsMetadata").equals(reader.getName())) {
286 parseOperationMetadata(reader);
287 }
288
289 if (new QName(WMTS_NS_URL, "Contents").equals(reader.getName())) {
290 ret = parseContents(reader);
291 }
292 }
293 }
294 return ret;
295 } catch (Exception e) {
296 throw new IllegalArgumentException(e);
297 }
298 }
299
300 /**
301 * Parse Contents tag. Renturns when reader reaches Contents closing tag
302 *
303 * @param reader StAX reader instance
304 * @return collection of layers within contents with properly linked TileMatrixSets
305 * @throws XMLStreamException See {@link XMLStreamReader}
306 */
307 private static Collection<Layer> parseContents(XMLStreamReader reader) throws XMLStreamException {
308 Map<String, TileMatrixSet> matrixSetById = new ConcurrentHashMap<>();
309 Collection<Layer> layers = new ArrayList<>();
310 for (int event = reader.getEventType();
311 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "Contents").equals(reader.getName()));
312 event = reader.next()) {
313 if (event == XMLStreamReader.START_ELEMENT) {
314 if (new QName(WMTS_NS_URL, "Layer").equals(reader.getName())) {
315 layers.add(parseLayer(reader));
316 }
317 if (new QName(WMTS_NS_URL, "TileMatrixSet").equals(reader.getName())) {
318 TileMatrixSet entry = parseTileMatrixSet(reader);
319 matrixSetById.put(entry.identifier, entry);
320 }
321 }
322 }
323 Collection<Layer> ret = new ArrayList<>();
324 // link layers to matrix sets
325 for (Layer l: layers) {
326 for (String tileMatrixId: l.tileMatrixSetLinks) {
327 Layer newLayer = new Layer(l); // create a new layer object for each tile matrix set supported
328 newLayer.tileMatrixSet = matrixSetById.get(tileMatrixId);
329 ret.add(newLayer);
330 }
331 }
332 return ret;
333 }
334
335 /**
336 * Parse Layer tag. Returns when reader will reach Layer closing tag
337 *
338 * @param reader StAX reader instance
339 * @return Layer object, with tileMatrixSetLinks and no tileMatrixSet attribute set.
340 * @throws XMLStreamException See {@link XMLStreamReader}
341 */
342 private static Layer parseLayer(XMLStreamReader reader) throws XMLStreamException {
343 Layer layer = new Layer();
344
345 for (int event = reader.getEventType();
346 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "Layer").equals(reader.getName()));
347 event = reader.next()) {
348 if (event == XMLStreamReader.START_ELEMENT) {
349 if (new QName(WMTS_NS_URL, "Format").equals(reader.getName())) {
350 layer.format = reader.getElementText();
351 }
352 if (new QName(OWS_NS_URL, "Identifier").equals(reader.getName())) {
353 layer.name = reader.getElementText();
354 }
355 if (new QName(WMTS_NS_URL, "ResourceURL").equals(reader.getName()) &&
356 "tile".equals(reader.getAttributeValue("", "resourceType"))) {
357 layer.baseUrl = reader.getAttributeValue("", "template");
358 }
359 if (new QName(WMTS_NS_URL, "Style").equals(reader.getName()) &&
360 "true".equals(reader.getAttributeValue("", "isDefault")) &&
361 moveReaderToTag(reader, new QName[] {new QName(OWS_NS_URL, "Identifier")})) {
362 layer.style = reader.getElementText();
363 }
364 if (new QName(WMTS_NS_URL, "TileMatrixSetLink").equals(reader.getName())) {
365 layer.tileMatrixSetLinks.add(praseTileMatrixSetLink(reader));
366 }
367 }
368 }
369 if (layer.style == null) {
370 layer.style = "";
371 }
372 return layer;
373 }
374
375 /**
376 * Gets TileMatrixSetLink value. Returns when reader is on TileMatrixSetLink closing tag
377 *
378 * @param reader StAX reader instance
379 * @return TileMatrixSetLink identifier
380 * @throws XMLStreamException See {@link XMLStreamReader}
381 */
382 private static String praseTileMatrixSetLink(XMLStreamReader reader) throws XMLStreamException {
383 String ret = null;
384 for (int event = reader.getEventType();
385 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT &&
386 new QName(WMTS_NS_URL, "TileMatrixSetLink").equals(reader.getName()));
387 event = reader.next()) {
388 if (event == XMLStreamReader.START_ELEMENT && new QName(WMTS_NS_URL, "TileMatrixSet").equals(reader.getName())) {
389 ret = reader.getElementText();
390 }
391 }
392 return ret;
393 }
394
395 /**
396 * Parses TileMatrixSet section. Returns when reader is on TileMatrixSet closing tag
397 * @param reader StAX reader instance
398 * @return TileMatrixSet object
399 * @throws XMLStreamException See {@link XMLStreamReader}
400 */
401 private static TileMatrixSet parseTileMatrixSet(XMLStreamReader reader) throws XMLStreamException {
402 TileMatrixSet matrixSet = new TileMatrixSet();
403 for (int event = reader.getEventType();
404 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "TileMatrixSet").equals(reader.getName()));
405 event = reader.next()) {
406 if (event == XMLStreamReader.START_ELEMENT) {
407 if (new QName(OWS_NS_URL, "Identifier").equals(reader.getName())) {
408 matrixSet.identifier = reader.getElementText();
409 }
410 if (new QName(OWS_NS_URL, "SupportedCRS").equals(reader.getName())) {
411 matrixSet.crs = crsToCode(reader.getElementText());
412 }
413 if (new QName(WMTS_NS_URL, "TileMatrix").equals(reader.getName())) {
414 matrixSet.tileMatrix.add(parseTileMatrix(reader, matrixSet.crs));
415 }
416 }
417 }
418 return matrixSet;
419 }
420
421 /**
422 * Parses TileMatrix section. Returns when reader is on TileMatrix closing tag.
423 * @param reader StAX reader instance
424 * @param matrixCrs projection used by this matrix
425 * @return TileMatrix object
426 * @throws XMLStreamException See {@link XMLStreamReader}
427 */
428 private static TileMatrix parseTileMatrix(XMLStreamReader reader, String matrixCrs) throws XMLStreamException {
429 Projection matrixProj = Projections.getProjectionByCode(matrixCrs);
430 TileMatrix ret = new TileMatrix();
431
432 if (matrixProj == null) {
433 // use current projection if none found. Maybe user is using custom string
434 matrixProj = Main.getProjection();
435 }
436 for (int event = reader.getEventType();
437 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "TileMatrix").equals(reader.getName()));
438 event = reader.next()) {
439 if (event == XMLStreamReader.START_ELEMENT) {
440 if (new QName(OWS_NS_URL, "Identifier").equals(reader.getName())) {
441 ret.identifier = reader.getElementText();
442 }
443 if (new QName(WMTS_NS_URL, "ScaleDenominator").equals(reader.getName())) {
444 ret.scaleDenominator = Double.parseDouble(reader.getElementText());
445 }
446 if (new QName(WMTS_NS_URL, "TopLeftCorner").equals(reader.getName())) {
447 String[] topLeftCorner = reader.getElementText().split(" ");
448 if (matrixProj.switchXY()) {
449 ret.topLeftCorner = new EastNorth(Double.parseDouble(topLeftCorner[1]), Double.parseDouble(topLeftCorner[0]));
450 } else {
451 ret.topLeftCorner = new EastNorth(Double.parseDouble(topLeftCorner[0]), Double.parseDouble(topLeftCorner[1]));
452 }
453 }
454 if (new QName(WMTS_NS_URL, "TileHeight").equals(reader.getName())) {
455 ret.tileHeight = Integer.parseInt(reader.getElementText());
456 }
457 if (new QName(WMTS_NS_URL, "TileWidth").equals(reader.getName())) {
458 ret.tileWidth = Integer.parseInt(reader.getElementText());
459 }
460 if (new QName(WMTS_NS_URL, "MatrixHeight").equals(reader.getName())) {
461 ret.matrixHeight = Integer.parseInt(reader.getElementText());
462 }
463 if (new QName(WMTS_NS_URL, "MatrixWidth").equals(reader.getName())) {
464 ret.matrixWidth = Integer.parseInt(reader.getElementText());
465 }
466 }
467 }
468 if (ret.tileHeight != ret.tileWidth) {
469 throw new AssertionError(tr("Only square tiles are supported. {0}x{1} returned by server for TileMatrix identifier {2}",
470 ret.tileHeight, ret.tileWidth, ret.identifier));
471 }
472 return ret;
473 }
474
475 /**
476 * Parses OperationMetadata section. Returns when reader is on OperationsMetadata closing tag.
477 * Sets this.baseUrl and this.transferMode
478 *
479 * @param reader StAX reader instance
480 * @throws XMLStreamException See {@link XMLStreamReader}
481 */
482 private void parseOperationMetadata(XMLStreamReader reader) throws XMLStreamException {
483 for (int event = reader.getEventType();
484 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT &&
485 new QName(OWS_NS_URL, "OperationsMetadata").equals(reader.getName()));
486 event = reader.next()) {
487 if (event == XMLStreamReader.START_ELEMENT) {
488 if (new QName(OWS_NS_URL, "Operation").equals(reader.getName()) && "GetTile".equals(reader.getAttributeValue("", "name")) &&
489 moveReaderToTag(reader, new QName[]{
490 new QName(OWS_NS_URL, "DCP"),
491 new QName(OWS_NS_URL, "HTTP"),
492 new QName(OWS_NS_URL, "Get"),
493
494 })) {
495 this.baseUrl = reader.getAttributeValue(XLINK_NS_URL, "href");
496 this.transferMode = getTransferMode(reader);
497 }
498 }
499 }
500 }
501
502 /**
503 * Parses Operation[@name='GetTile']/DCP/HTTP/Get section. Returns when reader is on Get closing tag.
504 * @param reader StAX reader instance
505 * @return TransferMode coded in this section
506 * @throws XMLStreamException See {@link XMLStreamReader}
507 */
508 private static TransferMode getTransferMode(XMLStreamReader reader) throws XMLStreamException {
509 QName getQname = new QName(OWS_NS_URL, "Get");
510
511 Utils.ensure(getQname.equals(reader.getName()), "WMTS Parser state invalid. Expected element %s, got %s",
512 getQname, reader.getName());
513 for (int event = reader.getEventType();
514 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && getQname.equals(reader.getName()));
515 event = reader.next()) {
516 if (event == XMLStreamReader.START_ELEMENT && new QName(OWS_NS_URL, "Constraint").equals(reader.getName())
517 && "GetEncoding".equals(reader.getAttributeValue("", "name"))) {
518 moveReaderToTag(reader, new QName[]{
519 new QName(OWS_NS_URL, "AllowedValues"),
520 new QName(OWS_NS_URL, "Value")
521 });
522 return TransferMode.fromString(reader.getElementText());
523 }
524 }
525 return null;
526 }
527
528 /**
529 * Moves reader to first occurrence of the structure equivalent of Xpath tags[0]/tags[1]../tags[n]. If fails to find
530 * moves the reader to the closing tag of current tag
531 *
532 * @param reader StAX reader instance
533 * @param tags array of tags
534 * @return true if tag was found, false otherwise
535 * @throws XMLStreamException See {@link XMLStreamReader}
536 */
537 private static boolean moveReaderToTag(XMLStreamReader reader, QName[] tags) throws XMLStreamException {
538 QName stopTag = reader.getName();
539 int currentLevel = 0;
540 QName searchTag = tags[currentLevel];
541 QName parentTag = null;
542 QName skipTag = null;
543
544 for (int event = 0; //skip current element, so we will not skip it as a whole
545 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && stopTag.equals(reader.getName()));
546 event = reader.next()) {
547 if (event == XMLStreamReader.END_ELEMENT && skipTag != null && skipTag.equals(reader.getName())) {
548 skipTag = null;
549 }
550 if (skipTag == null) {
551 if (event == XMLStreamReader.START_ELEMENT) {
552 if (searchTag.equals(reader.getName())) {
553 currentLevel += 1;
554 if (currentLevel >= tags.length) {
555 return true; // found!
556 }
557 parentTag = searchTag;
558 searchTag = tags[currentLevel];
559 } else {
560 skipTag = reader.getName();
561 }
562 }
563
564 if (event == XMLStreamReader.END_ELEMENT && parentTag != null && parentTag.equals(reader.getName())) {
565 currentLevel -= 1;
566 searchTag = parentTag;
567 if (currentLevel >= 0) {
568 parentTag = tags[currentLevel];
569 } else {
570 parentTag = null;
571 }
572 }
573 }
574 }
575 return false;
576 }
577
578 private static String normalizeCapabilitiesUrl(String url) throws MalformedURLException {
579 URL inUrl = new URL(url);
580 URL ret = new URL(inUrl.getProtocol(), inUrl.getHost(), inUrl.getPort(), inUrl.getFile());
581 return ret.toExternalForm();
582 }
583
584 private static String crsToCode(String crsIdentifier) {
585 if (crsIdentifier.startsWith("urn:ogc:def:crs:")) {
586 return crsIdentifier.replaceFirst("urn:ogc:def:crs:([^:]*):.*:(.*)$", "$1:$2");
587 }
588 return crsIdentifier;
589 }
590
591 /**
592 * Initializes projection for this TileSource with projection
593 * @param proj projection to be used by this TileSource
594 */
595 public void initProjection(Projection proj) {
596 // getLayers will return only layers matching the name, if the user already choose the layer
597 // so we will not ask the user again to chose the layer, if he just changes projection
598 Collection<Layer> candidates = getLayers(currentLayer != null ? currentLayer.name : null, proj.toCode());
599 if (!candidates.isEmpty()) {
600 Layer newLayer = userSelectLayer(candidates);
601 if (newLayer != null) {
602 this.currentTileMatrixSet = newLayer.tileMatrixSet;
603 this.currentLayer = newLayer;
604 }
605 }
606
607 this.crsScale = getTileSize() * 0.28e-03 / proj.getMetersPerUnit();
608 }
609
610 /**
611 *
612 * @param name of the layer to match
613 * @param projectionCode projection code to match
614 * @return Collection of layers matching the name of the layer and projection, or only projection if name is not provided
615 */
616 private Collection<Layer> getLayers(String name, String projectionCode) {
617 Collection<Layer> ret = new ArrayList<>();
618 if (this.layers != null) {
619 for (Layer layer: this.layers) {
620 if ((name == null || name.equals(layer.name)) && (projectionCode == null || projectionCode.equals(layer.tileMatrixSet.crs))) {
621 ret.add(layer);
622 }
623 }
624 }
625 return ret;
626 }
627
628 @Override
629 public int getTileSize() {
630 // no support for non-square tiles (tileHeight != tileWidth)
631 // and for different tile sizes at different zoom levels
632 Collection<Layer> layers = getLayers(null, Main.getProjection().toCode());
633 if (!layers.isEmpty()) {
634 return layers.iterator().next().tileMatrixSet.tileMatrix.first().tileHeight;
635 }
636 // if no layers is found, fallback to default mercator tile size. Maybe it will work
637 Main.warn("WMTS: Could not determine tile size. Using default tile size of: {0}", getDefaultTileSize());
638 return getDefaultTileSize();
639 }
640
641 @Override
642 public String getTileUrl(int zoom, int tilex, int tiley) {
643 String url;
644 if (currentLayer == null) {
645 return "";
646 }
647
648 if (currentLayer.baseUrl != null && transferMode == null) {
649 url = currentLayer.baseUrl;
650 } else {
651 switch (transferMode) {
652 case KVP:
653 url = baseUrl + URL_GET_ENCODING_PARAMS;
654 break;
655 case REST:
656 url = currentLayer.baseUrl;
657 break;
658 default:
659 url = "";
660 break;
661 }
662 }
663
664 TileMatrix tileMatrix = getTileMatrix(zoom);
665
666 if (tileMatrix == null) {
667 return ""; // no matrix, probably unsupported CRS selected.
668 }
669
670 return url.replaceAll("\\{layer\\}", this.currentLayer.name)
671 .replaceAll("\\{format\\}", this.currentLayer.format)
672 .replaceAll("\\{TileMatrixSet\\}", this.currentTileMatrixSet.identifier)
673 .replaceAll("\\{TileMatrix\\}", tileMatrix.identifier)
674 .replaceAll("\\{TileRow\\}", Integer.toString(tiley))
675 .replaceAll("\\{TileCol\\}", Integer.toString(tilex))
676 .replaceAll("(?i)\\{style\\}", this.currentLayer.style);
677 }
678
679 /**
680 *
681 * @param zoom zoom level
682 * @return TileMatrix that's working on this zoom level
683 */
684 private TileMatrix getTileMatrix(int zoom) {
685 if (zoom > getMaxZoom()) {
686 return null;
687 }
688 if (zoom < 1) {
689 return null;
690 }
691 return this.currentTileMatrixSet.tileMatrix.toArray(new TileMatrix[]{})[zoom - 1];
692 }
693
694 @Override
695 public double getDistance(double lat1, double lon1, double lat2, double lon2) {
696 throw new UnsupportedOperationException("Not implemented");
697 }
698
699 @Override
700 public ICoordinate tileXYToLatLon(Tile tile) {
701 return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom());
702 }
703
704 @Override
705 public ICoordinate tileXYToLatLon(TileXY xy, int zoom) {
706 return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom);
707 }
708
709 @Override
710 public ICoordinate tileXYToLatLon(int x, int y, int zoom) {
711 TileMatrix matrix = getTileMatrix(zoom);
712 if (matrix == null) {
713 return Main.getProjection().getWorldBoundsLatLon().getCenter().toCoordinate();
714 }
715 double scale = matrix.scaleDenominator * this.crsScale;
716 EastNorth ret = new EastNorth(matrix.topLeftCorner.east() + x * scale, matrix.topLeftCorner.north() - y * scale);
717 return Main.getProjection().eastNorth2latlon(ret).toCoordinate();
718 }
719
720 @Override
721 public TileXY latLonToTileXY(double lat, double lon, int zoom) {
722 TileMatrix matrix = getTileMatrix(zoom);
723 if (matrix == null) {
724 return new TileXY(0, 0);
725 }
726
727 Projection proj = Main.getProjection();
728 EastNorth enPoint = proj.latlon2eastNorth(new LatLon(lat, lon));
729 double scale = matrix.scaleDenominator * this.crsScale;
730 return new TileXY(
731 (enPoint.east() - matrix.topLeftCorner.east()) / scale,
732 (matrix.topLeftCorner.north() - enPoint.north()) / scale
733 );
734 }
735
736 @Override
737 public TileXY latLonToTileXY(ICoordinate point, int zoom) {
738 return latLonToTileXY(point.getLat(), point.getLon(), zoom);
739 }
740
741 @Override
742 public int getTileXMax(int zoom) {
743 return getTileXMax(zoom, Main.getProjection());
744 }
745
746 @Override
747 public int getTileXMin(int zoom) {
748 return 0;
749 }
750
751 @Override
752 public int getTileYMax(int zoom) {
753 return getTileYMax(zoom, Main.getProjection());
754 }
755
756 @Override
757 public int getTileYMin(int zoom) {
758 return 0;
759 }
760
761 @Override
762 public Point latLonToXY(double lat, double lon, int zoom) {
763 TileMatrix matrix = getTileMatrix(zoom);
764 if (matrix == null) {
765 return new Point(0, 0);
766 }
767 double scale = matrix.scaleDenominator * this.crsScale;
768 EastNorth point = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));
769 return new Point(
770 (int) Math.round((point.east() - matrix.topLeftCorner.east()) / scale),
771 (int) Math.round((matrix.topLeftCorner.north() - point.north()) / scale)
772 );
773 }
774
775 @Override
776 public Point latLonToXY(ICoordinate point, int zoom) {
777 return latLonToXY(point.getLat(), point.getLon(), zoom);
778 }
779
780 @Override
781 public Coordinate xyToLatLon(Point point, int zoom) {
782 return xyToLatLon(point.x, point.y, zoom);
783 }
784
785 @Override
786 public Coordinate xyToLatLon(int x, int y, int zoom) {
787 TileMatrix matrix = getTileMatrix(zoom);
788 if (matrix == null) {
789 return new Coordinate(0, 0);
790 }
791 double scale = matrix.scaleDenominator * this.crsScale;
792 Projection proj = Main.getProjection();
793 EastNorth ret = new EastNorth(
794 matrix.topLeftCorner.east() + x * scale,
795 matrix.topLeftCorner.north() - y * scale
796 );
797 LatLon ll = proj.eastNorth2latlon(ret);
798 return new Coordinate(ll.lat(), ll.lon());
799 }
800
801 @Override
802 public Map<String, String> getHeaders() {
803 return headers;
804 }
805
806 @Override
807 public int getMaxZoom() {
808 if (this.currentTileMatrixSet != null) {
809 return this.currentTileMatrixSet.tileMatrix.size();
810 }
811 return 0;
812 }
813
814 @Override
815 public String getTileId(int zoom, int tilex, int tiley) {
816 return getTileUrl(zoom, tilex, tiley);
817 }
818
819 /**
820 * Checks if url is acceptable by this Tile Source
821 * @param url URL to check
822 */
823 public static void checkUrl(String url) {
824 CheckParameterUtil.ensureParameterNotNull(url, "url");
825 Matcher m = Pattern.compile("\\{[^}]*\\}").matcher(url);
826 while (m.find()) {
827 boolean isSupportedPattern = false;
828 for (String pattern : ALL_PATTERNS) {
829 if (m.group().matches(pattern)) {
830 isSupportedPattern = true;
831 break;
832 }
833 }
834 if (!isSupportedPattern) {
835 throw new IllegalArgumentException(
836 tr("{0} is not a valid WMS argument. Please check this server URL:\n{1}", m.group(), url));
837 }
838 }
839 }
840
841 /**
842 * @return set of projection codes that this TileSource supports
843 */
844 public Set<String> getSupportedProjections() {
845 Set<String> ret = new HashSet<>();
846 if (currentLayer == null) {
847 for (Layer layer: this.layers) {
848 ret.add(layer.tileMatrixSet.crs);
849 }
850 } else {
851 for (Layer layer: this.layers) {
852 if (currentLayer.name.equals(layer.name)) {
853 ret.add(layer.tileMatrixSet.crs);
854 }
855 }
856 }
857 return ret;
858 }
859
860 private int getTileYMax(int zoom, Projection proj) {
861 TileMatrix matrix = getTileMatrix(zoom);
862 if (matrix == null) {
863 return 0;
864 }
865
866 if (matrix.matrixHeight != -1) {
867 return matrix.matrixHeight;
868 }
869
870 double scale = matrix.scaleDenominator * this.crsScale;
871 EastNorth min = matrix.topLeftCorner;
872 EastNorth max = proj.latlon2eastNorth(proj.getWorldBoundsLatLon().getMax());
873 return (int) Math.ceil(Math.abs(max.north() - min.north()) / scale);
874 }
875
876 private int getTileXMax(int zoom, Projection proj) {
877 TileMatrix matrix = getTileMatrix(zoom);
878 if (matrix == null) {
879 return 0;
880 }
881 if (matrix.matrixWidth != -1) {
882 return matrix.matrixWidth;
883 }
884
885 double scale = matrix.scaleDenominator * this.crsScale;
886 EastNorth min = matrix.topLeftCorner;
887 EastNorth max = proj.latlon2eastNorth(proj.getWorldBoundsLatLon().getMax());
888 return (int) Math.ceil(Math.abs(max.east() - min.east()) / scale);
889 }
890}
Note: See TracBrowser for help on using the repository browser.