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

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

Fix 12168.

  • added testcase for bug #12168. I removed all but one layers from the GetCapabilities document to avoid showing dialog
  • added case, that when ResourceURL is provided, but no OperationsMetadata information are present - to assume REST transfer mode
  • added ignore-case paramter for {style} replacement, as some servers use lowercase name, and some capitalized versions

Closes #12168

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