001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Dimension; 007import java.awt.GridBagLayout; 008import java.awt.Point; 009import java.io.ByteArrayInputStream; 010import java.io.IOException; 011import java.io.InputStream; 012import java.net.MalformedURLException; 013import java.net.URL; 014import java.util.ArrayList; 015import java.util.Collection; 016import java.util.Comparator; 017import java.util.HashSet; 018import java.util.Map; 019import java.util.Set; 020import java.util.SortedSet; 021import java.util.TreeSet; 022import java.util.concurrent.ConcurrentHashMap; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import javax.swing.JPanel; 027import javax.swing.JTable; 028import javax.swing.ListSelectionModel; 029import javax.swing.table.AbstractTableModel; 030import javax.xml.XMLConstants; 031import javax.xml.namespace.QName; 032import javax.xml.parsers.DocumentBuilder; 033import javax.xml.parsers.DocumentBuilderFactory; 034import javax.xml.parsers.ParserConfigurationException; 035import javax.xml.xpath.XPath; 036import javax.xml.xpath.XPathConstants; 037import javax.xml.xpath.XPathExpression; 038import javax.xml.xpath.XPathExpressionException; 039import javax.xml.xpath.XPathFactory; 040 041import org.openstreetmap.gui.jmapviewer.Coordinate; 042import org.openstreetmap.gui.jmapviewer.Tile; 043import org.openstreetmap.gui.jmapviewer.TileXY; 044import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 045import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource; 046import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource; 047import org.openstreetmap.josm.Main; 048import org.openstreetmap.josm.data.coor.EastNorth; 049import org.openstreetmap.josm.data.coor.LatLon; 050import org.openstreetmap.josm.data.projection.Projection; 051import org.openstreetmap.josm.data.projection.Projections; 052import org.openstreetmap.josm.gui.ExtendedDialog; 053import org.openstreetmap.josm.io.CachedFile; 054import org.openstreetmap.josm.tools.CheckParameterUtil; 055import org.openstreetmap.josm.tools.GBC; 056import org.openstreetmap.josm.tools.Utils; 057import org.w3c.dom.Document; 058import org.w3c.dom.Node; 059import org.w3c.dom.NodeList; 060 061/** 062 * Tile Source handling WMS providers 063 * 064 * @author Wiktor Niesiobędzki 065 * @since 8526 066 */ 067public class WMTSTileSource extends TMSTileSource implements TemplatedTileSource { 068 private static final String PATTERN_HEADER = "\\{header\\(([^,]+),([^}]+)\\)\\}"; 069 070 private static final String URL_GET_ENCODING_PARAMS = "SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER={layer}&STYLE={Style}&" 071 + "FORMAT={format}&tileMatrixSet={TileMatrixSet}&tileMatrix={TileMatrix}&tileRow={TileRow}&tileCol={TileCol}"; 072 073 private static final String[] ALL_PATTERNS = { 074 PATTERN_HEADER, 075 }; 076 077 private static class TileMatrix { 078 private String identifier; 079 private double scaleDenominator; 080 private EastNorth topLeftCorner; 081 private int tileWidth; 082 private int tileHeight; 083 private int matrixWidth = -1; 084 private int matrixHeight = -1; 085 } 086 087 private static class TileMatrixSet { 088 SortedSet<TileMatrix> tileMatrix = new TreeSet<>(new Comparator<TileMatrix>() { 089 @Override 090 public int compare(TileMatrix o1, TileMatrix o2) { 091 // reverse the order, so it will be from greatest (lowest zoom level) to lowest value (highest zoom level) 092 return -1 * Double.compare(o1.scaleDenominator, o2.scaleDenominator); 093 } 094 }); // sorted by zoom level 095 private String crs; 096 private String identifier; 097 } 098 099 private static class Layer { 100 private String format; 101 private String name; 102 private TileMatrixSet tileMatrixSet; 103 private String baseUrl; 104 private String style; 105 } 106 107 private enum TransferMode { 108 KVP("KVP"), 109 REST("RESTful"); 110 111 private final String typeString; 112 113 TransferMode(String urlString) { 114 this.typeString = urlString; 115 } 116 117 private String getTypeString() { 118 return typeString; 119 } 120 121 private static TransferMode fromString(String s) { 122 for (TransferMode type : TransferMode.values()) { 123 if (type.getTypeString().equals(s)) { 124 return type; 125 } 126 } 127 return null; 128 } 129 } 130 131 private static final class SelectLayerDialog extends ExtendedDialog { 132 private final Layer[] layers; 133 private final JTable list; 134 135 SelectLayerDialog(Collection<Layer> layers) { 136 super(Main.parent, tr("Select WMTS layer"), new String[]{tr("Add layers"), tr("Cancel")}); 137 this.layers = layers.toArray(new Layer[]{}); 138 //getLayersTable(layers, Main.getProjection()) 139 this.list = new JTable( 140 new AbstractTableModel() { 141 @Override 142 public Object getValueAt(int rowIndex, int columnIndex) { 143 switch (columnIndex) { 144 case 0: 145 return SelectLayerDialog.this.layers[rowIndex].name; 146 case 1: 147 return SelectLayerDialog.this.layers[rowIndex].tileMatrixSet.crs; 148 case 2: 149 return SelectLayerDialog.this.layers[rowIndex].tileMatrixSet.identifier; 150 default: 151 throw new IllegalArgumentException(); 152 } 153 } 154 155 @Override 156 public int getRowCount() { 157 return SelectLayerDialog.this.layers.length; 158 } 159 160 @Override 161 public int getColumnCount() { 162 return 3; 163 } 164 165 @Override 166 public String getColumnName(int column) { 167 switch (column) { 168 case 0: return tr("Layer name"); 169 case 1: return tr("Projection"); 170 case 2: return tr("Matrix set identifier"); 171 default: 172 throw new IllegalArgumentException(); 173 } 174 } 175 176 @Override 177 public boolean isCellEditable(int row, int column) { 178 return false; 179 } 180 }); 181 this.list.setPreferredSize(new Dimension(400, 400)); 182 this.list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 183 this.list.setRowSelectionAllowed(true); 184 this.list.setColumnSelectionAllowed(false); 185 JPanel panel = new JPanel(new GridBagLayout()); 186 panel.add(this.list, GBC.eol().fill()); 187 setContent(panel); 188 } 189 190 public Layer getSelectedLayer() { 191 int index = list.getSelectedRow(); 192 if (index < 0) { 193 return null; //nothing selected 194 } 195 return layers[index]; 196 } 197 } 198 199 private final Map<String, String> headers = new ConcurrentHashMap<>(); 200 private Collection<Layer> layers; 201 private Layer currentLayer; 202 private TileMatrixSet currentTileMatrixSet; 203 private double crsScale; 204 private TransferMode transferMode; 205 206 /** 207 * Creates a tile source based on imagery info 208 * @param info imagery info 209 * @throws IOException if any I/O error occurs 210 */ 211 public WMTSTileSource(ImageryInfo info) throws IOException { 212 super(info); 213 this.baseUrl = normalizeCapabilitiesUrl(handleTemplate(info.getUrl())); 214 this.layers = getCapabilities(); 215 if (this.layers.isEmpty()) 216 throw new IllegalArgumentException(tr("No layers defined by getCapabilities document: {0}", info.getUrl())); 217 } 218 219 private Layer userSelectLayer(Collection<Layer> layers) { 220 if (layers.size() == 1) 221 return layers.iterator().next(); 222 Layer ret = null; 223 224 final SelectLayerDialog layerSelection = new SelectLayerDialog(layers); 225 if (layerSelection.showDialog().getValue() == 1) { 226 ret = layerSelection.getSelectedLayer(); 227 // TODO: save layer information into ImageryInfo / ImageryPreferences? 228 } 229 if (ret == null) { 230 // user canceled operation or did not choose any layer 231 throw new IllegalArgumentException(tr("No layer selected")); 232 } 233 return ret; 234 } 235 236 private String handleTemplate(String url) { 237 Pattern pattern = Pattern.compile(PATTERN_HEADER); 238 StringBuffer output = new StringBuffer(); 239 Matcher matcher = pattern.matcher(url); 240 while (matcher.find()) { 241 this.headers.put(matcher.group(1), matcher.group(2)); 242 matcher.appendReplacement(output, ""); 243 } 244 matcher.appendTail(output); 245 return output.toString(); 246 } 247 248 private Collection<Layer> getCapabilities() throws IOException { 249 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); 250 builderFactory.setValidating(false); 251 builderFactory.setNamespaceAware(false); 252 try { 253 builderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 254 } catch (ParserConfigurationException e) { 255 //this should not happen 256 throw new IllegalArgumentException(e); 257 } 258 DocumentBuilder builder = null; 259 InputStream in = new CachedFile(baseUrl). 260 setHttpHeaders(headers). 261 setMaxAge(7 * CachedFile.DAYS). 262 setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince). 263 getInputStream(); 264 try { 265 builder = builderFactory.newDocumentBuilder(); 266 byte[] data = Utils.readBytesFromStream(in); 267 if (data == null || data.length == 0) { 268 throw new IllegalArgumentException("Could not read data from: " + baseUrl); 269 } 270 Document document = builder.parse(new ByteArrayInputStream(data)); 271 Node getTileOperation = getByXpath(document, 272 "/Capabilities/OperationsMetadata/Operation[@name=\"GetTile\"]/DCP/HTTP/Get").item(0); 273 this.baseUrl = getStringByXpath(getTileOperation, "@href"); 274 this.transferMode = TransferMode.fromString(getStringByXpath(getTileOperation, 275 "Constraint[@name=\"GetEncoding\"]/AllowedValues/Value")); 276 NodeList layersNodeList = getByXpath(document, "/Capabilities/Contents/Layer"); 277 Map<String, TileMatrixSet> matrixSetById = parseMatrices(getByXpath(document, "/Capabilities/Contents/TileMatrixSet")); 278 return parseLayer(layersNodeList, matrixSetById); 279 } catch (Exception e) { 280 throw new IllegalArgumentException(e); 281 } 282 } 283 284 private static String normalizeCapabilitiesUrl(String url) throws MalformedURLException { 285 URL inUrl = new URL(url); 286 URL ret = new URL(inUrl.getProtocol(), inUrl.getHost(), inUrl.getPort(), inUrl.getFile()); 287 return ret.toExternalForm(); 288 } 289 290 private Collection<Layer> parseLayer(NodeList nodeList, Map<String, TileMatrixSet> matrixSetById) throws XPathExpressionException { 291 Collection<Layer> ret = new ArrayList<>(); 292 for (int layerId = 0; layerId < nodeList.getLength(); layerId++) { 293 Node layerNode = nodeList.item(layerId); 294 NodeList tileMatrixSetLinks = getByXpath(layerNode, "TileMatrixSetLink"); 295 296 // we add an layer for all matrix sets to allow user to choose, with which tileset he wants to work 297 for (int tileMatrixId = 0; tileMatrixId < tileMatrixSetLinks.getLength(); tileMatrixId++) { 298 Layer layer = new Layer(); 299 layer.format = getStringByXpath(layerNode, "Format"); 300 layer.name = getStringByXpath(layerNode, "Identifier"); 301 layer.baseUrl = getStringByXpath(layerNode, "ResourceURL[@resourceType='tile']/@template"); 302 layer.style = getStringByXpath(layerNode, "Style[@isDefault='true']/Identifier"); 303 if (layer.style == null) { 304 layer.style = ""; 305 } 306 Node tileMatrixLink = tileMatrixSetLinks.item(tileMatrixId); 307 TileMatrixSet tms = matrixSetById.get(getStringByXpath(tileMatrixLink, "TileMatrixSet")); 308 layer.tileMatrixSet = tms; 309 ret.add(layer); 310 } 311 } 312 return ret; 313 314 } 315 316 private Map<String, TileMatrixSet> parseMatrices(NodeList nodeList) throws XPathExpressionException { 317 Map<String, TileMatrixSet> ret = new ConcurrentHashMap<>(); 318 for (int matrixSetId = 0; matrixSetId < nodeList.getLength(); matrixSetId++) { 319 Node matrixSetNode = nodeList.item(matrixSetId); 320 TileMatrixSet matrixSet = new TileMatrixSet(); 321 matrixSet.identifier = getStringByXpath(matrixSetNode, "Identifier"); 322 matrixSet.crs = crsToCode(getStringByXpath(matrixSetNode, "SupportedCRS")); 323 NodeList tileMatrixList = getByXpath(matrixSetNode, "TileMatrix"); 324 Projection matrixProj = Projections.getProjectionByCode(matrixSet.crs); 325 if (matrixProj == null) { 326 // use current projection if none found. Maybe user is using custom string 327 matrixProj = Main.getProjection(); 328 } 329 for (int matrixId = 0; matrixId < tileMatrixList.getLength(); matrixId++) { 330 Node tileMatrixNode = tileMatrixList.item(matrixId); 331 TileMatrix tileMatrix = new TileMatrix(); 332 tileMatrix.identifier = getStringByXpath(tileMatrixNode, "Identifier"); 333 tileMatrix.scaleDenominator = Double.parseDouble(getStringByXpath(tileMatrixNode, "ScaleDenominator")); 334 String[] topLeftCorner = getStringByXpath(tileMatrixNode, "TopLeftCorner").split(" "); 335 336 if (matrixProj.switchXY()) { 337 tileMatrix.topLeftCorner = new EastNorth(Double.parseDouble(topLeftCorner[1]), Double.parseDouble(topLeftCorner[0])); 338 } else { 339 tileMatrix.topLeftCorner = new EastNorth(Double.parseDouble(topLeftCorner[0]), Double.parseDouble(topLeftCorner[1])); 340 } 341 tileMatrix.tileHeight = Integer.parseInt(getStringByXpath(tileMatrixNode, "TileHeight")); 342 tileMatrix.tileWidth = Integer.parseInt(getStringByXpath(tileMatrixNode, "TileHeight")); 343 tileMatrix.matrixWidth = getOptionalIntegerByXpath(tileMatrixNode, "MatrixWidth"); 344 tileMatrix.matrixHeight = getOptionalIntegerByXpath(tileMatrixNode, "MatrixHeight"); 345 if (tileMatrix.tileHeight != tileMatrix.tileWidth) { 346 throw new AssertionError(tr("Only square tiles are supported. {0}x{1} returned by server for TileMatrix identifier {2}", 347 tileMatrix.tileHeight, tileMatrix.tileWidth, tileMatrix.identifier)); 348 } 349 350 matrixSet.tileMatrix.add(tileMatrix); 351 } 352 ret.put(matrixSet.identifier, matrixSet); 353 } 354 return ret; 355 } 356 357 private static String crsToCode(String crsIdentifier) { 358 if (crsIdentifier.startsWith("urn:ogc:def:crs:")) { 359 return crsIdentifier.replaceFirst("urn:ogc:def:crs:([^:]*):.*:(.*)$", "$1:$2"); 360 } 361 return crsIdentifier; 362 } 363 364 private static int getOptionalIntegerByXpath(Node document, String xpathQuery) throws XPathExpressionException { 365 String ret = getStringByXpath(document, xpathQuery); 366 if (ret == null || "".equals(ret)) { 367 return -1; 368 } 369 return Integer.parseInt(ret); 370 } 371 372 private static String getStringByXpath(Node document, String xpathQuery) throws XPathExpressionException { 373 return (String) getByXpath(document, xpathQuery, XPathConstants.STRING); 374 } 375 376 private static NodeList getByXpath(Node document, String xpathQuery) throws XPathExpressionException { 377 return (NodeList) getByXpath(document, xpathQuery, XPathConstants.NODESET); 378 } 379 380 private static Object getByXpath(Node document, String xpathQuery, QName returnType) throws XPathExpressionException { 381 XPath xpath = XPathFactory.newInstance().newXPath(); 382 XPathExpression expr = xpath.compile(xpathQuery); 383 return expr.evaluate(document, returnType); 384 } 385 386 /** 387 * Initializes projection for this TileSource with projection 388 * @param proj projection to be used by this TileSource 389 */ 390 public void initProjection(Projection proj) { 391 String layerName = null; 392 if (currentLayer != null) { 393 layerName = currentLayer.name; 394 } 395 Collection<Layer> candidates = getLayers(layerName, proj.toCode()); 396 if (!candidates.isEmpty()) { 397 Layer newLayer = userSelectLayer(candidates); 398 if (newLayer != null) { 399 this.currentTileMatrixSet = newLayer.tileMatrixSet; 400 this.currentLayer = newLayer; 401 } 402 } 403 404 this.crsScale = getTileSize() * 0.28e-03 / proj.getMetersPerUnit(); 405 } 406 407 private Collection<Layer> getLayers(String name, String projectionCode) { 408 Collection<Layer> ret = new ArrayList<>(); 409 for (Layer layer: this.layers) { 410 if ((name == null || name.equals(layer.name)) && (projectionCode == null || projectionCode.equals(layer.tileMatrixSet.crs))) { 411 ret.add(layer); 412 } 413 } 414 return ret; 415 } 416 417 @Override 418 public int getDefaultTileSize() { 419 return getTileSize(); 420 } 421 422 // FIXME: remove in September 2015, when ImageryPreferenceEntry.tileSize will be initialized to -1 instead to 256 423 // need to leave it as it is to keep compatiblity between tested and latest JOSM versions 424 @Override 425 public int getTileSize() { 426 TileMatrix matrix = getTileMatrix(1); 427 if (matrix == null) { 428 return 1; 429 } 430 return matrix.tileHeight; 431 } 432 433 @Override 434 public String getTileUrl(int zoom, int tilex, int tiley) { 435 String url; 436 if (currentLayer == null) { 437 return ""; 438 } 439 440 switch (transferMode) { 441 case KVP: 442 url = baseUrl + URL_GET_ENCODING_PARAMS; 443 break; 444 case REST: 445 url = currentLayer.baseUrl; 446 break; 447 default: 448 url = ""; 449 break; 450 } 451 452 TileMatrix tileMatrix = getTileMatrix(zoom); 453 454 if (tileMatrix == null) { 455 return ""; // no matrix, probably unsupported CRS selected. 456 } 457 458 return url.replaceAll("\\{layer\\}", this.currentLayer.name) 459 .replaceAll("\\{format\\}", this.currentLayer.format) 460 .replaceAll("\\{TileMatrixSet\\}", this.currentTileMatrixSet.identifier) 461 .replaceAll("\\{TileMatrix\\}", tileMatrix.identifier) 462 .replaceAll("\\{TileRow\\}", Integer.toString(tiley)) 463 .replaceAll("\\{TileCol\\}", Integer.toString(tilex)) 464 .replaceAll("\\{Style\\}", this.currentLayer.style); 465 } 466 467 /** 468 * 469 * @param zoom zoom level 470 * @return TileMatrix that's working on this zoom level 471 */ 472 private TileMatrix getTileMatrix(int zoom) { 473 if (zoom > getMaxZoom()) { 474 return null; 475 } 476 if (zoom < 1) { 477 return null; 478 } 479 return this.currentTileMatrixSet.tileMatrix.toArray(new TileMatrix[]{})[zoom - 1]; 480 } 481 482 @Override 483 public double getDistance(double lat1, double lon1, double lat2, double lon2) { 484 throw new UnsupportedOperationException("Not implemented"); 485 } 486 487 @Override 488 public ICoordinate tileXYToLatLon(Tile tile) { 489 return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom()); 490 } 491 492 @Override 493 public ICoordinate tileXYToLatLon(TileXY xy, int zoom) { 494 return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom); 495 } 496 497 @Override 498 public ICoordinate tileXYToLatLon(int x, int y, int zoom) { 499 TileMatrix matrix = getTileMatrix(zoom); 500 if (matrix == null) { 501 return Main.getProjection().getWorldBoundsLatLon().getCenter().toCoordinate(); 502 } 503 double scale = matrix.scaleDenominator * this.crsScale; 504 EastNorth ret = new EastNorth(matrix.topLeftCorner.east() + x * scale, matrix.topLeftCorner.north() - y * scale); 505 return Main.getProjection().eastNorth2latlon(ret).toCoordinate(); 506 } 507 508 @Override 509 public TileXY latLonToTileXY(double lat, double lon, int zoom) { 510 TileMatrix matrix = getTileMatrix(zoom); 511 if (matrix == null) { 512 return new TileXY(0, 0); 513 } 514 515 Projection proj = Main.getProjection(); 516 EastNorth enPoint = proj.latlon2eastNorth(new LatLon(lat, lon)); 517 double scale = matrix.scaleDenominator * this.crsScale; 518 return new TileXY( 519 (enPoint.east() - matrix.topLeftCorner.east()) / scale, 520 (matrix.topLeftCorner.north() - enPoint.north()) / scale 521 ); 522 } 523 524 @Override 525 public TileXY latLonToTileXY(ICoordinate point, int zoom) { 526 return latLonToTileXY(point.getLat(), point.getLon(), zoom); 527 } 528 529 @Override 530 public int getTileXMax(int zoom) { 531 return getTileXMax(zoom, Main.getProjection()); 532 } 533 534 @Override 535 public int getTileXMin(int zoom) { 536 return 0; 537 } 538 539 @Override 540 public int getTileYMax(int zoom) { 541 return getTileYMax(zoom, Main.getProjection()); 542 } 543 544 @Override 545 public int getTileYMin(int zoom) { 546 return 0; 547 } 548 549 @Override 550 public Point latLonToXY(double lat, double lon, int zoom) { 551 TileMatrix matrix = getTileMatrix(zoom); 552 if (matrix == null) { 553 return new Point(0, 0); 554 } 555 double scale = matrix.scaleDenominator * this.crsScale; 556 EastNorth point = Main.getProjection().latlon2eastNorth(new LatLon(lat, lon)); 557 return new Point( 558 (int) Math.round((point.east() - matrix.topLeftCorner.east()) / scale), 559 (int) Math.round((matrix.topLeftCorner.north() - point.north()) / scale) 560 ); 561 } 562 563 @Override 564 public Point latLonToXY(ICoordinate point, int zoom) { 565 return latLonToXY(point.getLat(), point.getLon(), zoom); 566 } 567 568 @Override 569 public Coordinate xyToLatLon(Point point, int zoom) { 570 return xyToLatLon(point.x, point.y, zoom); 571 } 572 573 @Override 574 public Coordinate xyToLatLon(int x, int y, int zoom) { 575 TileMatrix matrix = getTileMatrix(zoom); 576 if (matrix == null) { 577 return new Coordinate(0, 0); 578 } 579 double scale = matrix.scaleDenominator * this.crsScale; 580 Projection proj = Main.getProjection(); 581 EastNorth ret = new EastNorth( 582 matrix.topLeftCorner.east() + x * scale, 583 matrix.topLeftCorner.north() - y * scale 584 ); 585 LatLon ll = proj.eastNorth2latlon(ret); 586 return new Coordinate(ll.lat(), ll.lon()); 587 } 588 589 @Override 590 public Map<String, String> getHeaders() { 591 return headers; 592 } 593 594 @Override 595 public int getMaxZoom() { 596 if (this.currentTileMatrixSet != null) { 597 return this.currentTileMatrixSet.tileMatrix.size(); 598 } 599 return 0; 600 } 601 602 @Override 603 public String getTileId(int zoom, int tilex, int tiley) { 604 return getTileUrl(zoom, tilex, tiley); 605 } 606 607 /** 608 * Checks if url is acceptable by this Tile Source 609 * @param url URL to check 610 */ 611 public static void checkUrl(String url) { 612 CheckParameterUtil.ensureParameterNotNull(url, "url"); 613 Matcher m = Pattern.compile("\\{[^}]*\\}").matcher(url); 614 while (m.find()) { 615 boolean isSupportedPattern = false; 616 for (String pattern : ALL_PATTERNS) { 617 if (m.group().matches(pattern)) { 618 isSupportedPattern = true; 619 break; 620 } 621 } 622 if (!isSupportedPattern) { 623 throw new IllegalArgumentException( 624 tr("{0} is not a valid WMS argument. Please check this server URL:\n{1}", m.group(), url)); 625 } 626 } 627 } 628 629 /** 630 * @return set of projection codes that this TileSource supports 631 */ 632 public Set<String> getSupportedProjections() { 633 Set<String> ret = new HashSet<>(); 634 if (currentLayer == null) { 635 for (Layer layer: this.layers) { 636 ret.add(layer.tileMatrixSet.crs); 637 } 638 } else { 639 for (Layer layer: this.layers) { 640 if (currentLayer.name.equals(layer.name)) { 641 ret.add(layer.tileMatrixSet.crs); 642 } 643 } 644 } 645 return ret; 646 } 647 648 private int getTileYMax(int zoom, Projection proj) { 649 TileMatrix matrix = getTileMatrix(zoom); 650 if (matrix == null) { 651 return 0; 652 } 653 654 if (matrix.matrixHeight != -1) { 655 return matrix.matrixHeight; 656 } 657 658 double scale = matrix.scaleDenominator * this.crsScale; 659 EastNorth min = matrix.topLeftCorner; 660 EastNorth max = proj.latlon2eastNorth(proj.getWorldBoundsLatLon().getMax()); 661 return (int) Math.ceil(Math.abs(max.north() - min.north()) / scale); 662 } 663 664 private int getTileXMax(int zoom, Projection proj) { 665 TileMatrix matrix = getTileMatrix(zoom); 666 if (matrix == null) { 667 return 0; 668 } 669 if (matrix.matrixWidth != -1) { 670 return matrix.matrixWidth; 671 } 672 673 double scale = matrix.scaleDenominator * this.crsScale; 674 EastNorth min = matrix.topLeftCorner; 675 EastNorth max = proj.latlon2eastNorth(proj.getWorldBoundsLatLon().getMax()); 676 return (int) Math.ceil(Math.abs(max.east() - min.east()) / scale); 677 } 678}