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

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

sonar - fix various recent issues

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