- Timestamp:
- 2015-11-29T19:41:22+01:00 (9 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java
r8855 r9068 28 28 import javax.swing.ListSelectionModel; 29 29 import javax.swing.table.AbstractTableModel; 30 import javax.xml.XMLConstants;31 30 import javax.xml.namespace.QName; 32 import javax.xml.parsers.DocumentBuilder; 33 import javax.xml.parsers.DocumentBuilderFactory; 34 import javax.xml.parsers.ParserConfigurationException; 35 import javax.xml.xpath.XPath; 36 import javax.xml.xpath.XPathConstants; 37 import javax.xml.xpath.XPathExpression; 38 import javax.xml.xpath.XPathExpressionException; 39 import javax.xml.xpath.XPathFactory; 31 import javax.xml.stream.XMLInputFactory; 32 import javax.xml.stream.XMLStreamException; 33 import javax.xml.stream.XMLStreamReader; 40 34 41 35 import org.openstreetmap.gui.jmapviewer.Coordinate; … … 55 49 import org.openstreetmap.josm.tools.GBC; 56 50 import org.openstreetmap.josm.tools.Utils; 57 import org.w3c.dom.Document;58 import org.w3c.dom.Node;59 import org.w3c.dom.NodeList;60 51 61 52 /** … … 75 66 }; 76 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 77 72 private static class TileMatrix { 78 73 private String identifier; … … 95 90 private String crs; 96 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 97 104 } 98 105 99 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 100 120 private String format; 101 121 private String name; … … 103 123 private String baseUrl; 104 124 private String style; 125 public Collection<String> tileMatrixSetLinks = new ArrayList<>(); 105 126 } 106 127 … … 247 268 248 269 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; 270 XMLInputFactory factory = XMLInputFactory.newFactory(); 259 271 InputStream in = new CachedFile(baseUrl). 260 272 setHttpHeaders(headers). … … 263 275 getInputStream(); 264 276 try { 265 builder = builderFactory.newDocumentBuilder();266 277 byte[] data = Utils.readBytesFromStream(in); 267 278 if (data == null || data.length == 0) { 268 279 throw new IllegalArgumentException("Could not read data from: " + baseUrl); 269 280 } 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); 281 XMLStreamReader reader = factory.createXMLStreamReader( 282 new ByteArrayInputStream(data) 283 ); 284 285 Collection<Layer> ret = null; 286 for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) { 287 if (event == XMLStreamReader.START_ELEMENT) { 288 if (new QName(OWS_NS_URL, "OperationsMetadata").equals(reader.getName())) { 289 parseOperationMetadata(reader); 290 } 291 292 if (new QName(WMTS_NS_URL, "Contents").equals(reader.getName())) { 293 ret = parseContents(reader); 294 } 295 } 296 } 297 return ret; 279 298 } catch (Exception e) { 280 299 throw new IllegalArgumentException(e); … … 282 301 } 283 302 284 private static String normalizeCapabilitiesUrl(String url) throws MalformedURLException { 303 /** 304 * Parse Contents tag. Renturns when reader reaches Contents closing tag 305 * 306 * @param reader StAX reader instance 307 * @return collection of layers within contents with properly linked TileMatrixSets 308 * @throws XMLStreamException See {@link XMLStreamReader} 309 */ 310 private static final Collection<Layer> parseContents(XMLStreamReader reader) throws XMLStreamException { 311 Map<String, TileMatrixSet> matrixSetById = new ConcurrentHashMap<>(); 312 Collection<Layer> layers = new ArrayList<>(); 313 for (int event = reader.getEventType(); 314 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "Contents").equals(reader.getName())); 315 event = reader.next()) { 316 if (event == XMLStreamReader.START_ELEMENT) { 317 if (new QName(WMTS_NS_URL, "Layer").equals(reader.getName())) { 318 layers.add(parseLayer(reader)); 319 } 320 if (new QName(WMTS_NS_URL, "TileMatrixSet").equals(reader.getName())) { 321 TileMatrixSet entry = parseTileMatrixSet(reader); 322 matrixSetById.put(entry.identifier, entry); 323 } 324 } 325 } 326 Collection<Layer> ret = new ArrayList<>(); 327 // link layers to matrix sets 328 for (Layer l: layers) { 329 for (String tileMatrixId: l.tileMatrixSetLinks) { 330 Layer newLayer = new Layer(l); // create a new layer object for each tile matrix set supported 331 newLayer.tileMatrixSet = matrixSetById.get(tileMatrixId); 332 ret.add(newLayer); 333 } 334 } 335 return ret; 336 } 337 338 /** 339 * Parse Layer tag. Returns when reader will reach Layer closing tag 340 * 341 * @param reader StAX reader instance 342 * @return Layer object, with tileMatrixSetLinks and no tileMatrixSet attribute set. 343 * @throws XMLStreamException See {@link XMLStreamReader} 344 */ 345 private static final Layer parseLayer(XMLStreamReader reader) throws XMLStreamException { 346 Layer layer = new Layer(); 347 348 for (int event = reader.getEventType(); 349 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "Layer").equals(reader.getName())); 350 event = reader.next()) { 351 if (event == XMLStreamReader.START_ELEMENT) { 352 if (new QName(WMTS_NS_URL, "Format").equals(reader.getName())) { 353 layer.format = reader.getElementText(); 354 } 355 if (new QName(OWS_NS_URL, "Identifier").equals(reader.getName())) { 356 layer.name = reader.getElementText(); 357 } 358 if (new QName(WMTS_NS_URL, "ResourceURL").equals(reader.getName()) && 359 "tile".equals(reader.getAttributeValue("", "resourceType"))) { 360 layer.baseUrl = reader.getAttributeValue("", "template"); 361 } 362 if (new QName(WMTS_NS_URL, "Style").equals(reader.getName()) && 363 "true".equals(reader.getAttributeValue("", "isDefault")) && 364 moveReaderToTag(reader, new QName[] {new QName(OWS_NS_URL, "Identifier")})) { 365 layer.style = reader.getElementText(); 366 } 367 if (new QName(WMTS_NS_URL, "TileMatrixSetLink").equals(reader.getName())) { 368 layer.tileMatrixSetLinks.add(praseTileMatrixSetLink(reader)); 369 } 370 } 371 } 372 if (layer.style == null) { 373 layer.style = ""; 374 } 375 return layer; 376 } 377 378 /** 379 * Gets TileMatrixSetLink value. Returns when reader is on TileMatrixSetLink closing tag 380 * 381 * @param reader StAX reader instance 382 * @return TileMatrixSetLink identifier 383 * @throws XMLStreamException See {@link XMLStreamReader} 384 */ 385 private static final String praseTileMatrixSetLink(XMLStreamReader reader) throws XMLStreamException { 386 String ret = null; 387 for (int event = reader.getEventType(); 388 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && 389 new QName(WMTS_NS_URL, "TileMatrixSetLink").equals(reader.getName())); 390 event = reader.next()) { 391 if (event == XMLStreamReader.START_ELEMENT && new QName(WMTS_NS_URL, "TileMatrixSet").equals(reader.getName())) { 392 ret = reader.getElementText(); 393 } 394 } 395 return ret; 396 } 397 398 /** 399 * Parses TileMatrixSet section. Returns when reader is on TileMatrixSet closing tag 400 * @param reader StAX reader instance 401 * @return TileMatrixSet object 402 * @throws XMLStreamException See {@link XMLStreamReader} 403 */ 404 private static final TileMatrixSet parseTileMatrixSet(XMLStreamReader reader) throws XMLStreamException { 405 TileMatrixSet matrixSet = new TileMatrixSet(); 406 for (int event = reader.getEventType(); 407 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "TileMatrixSet").equals(reader.getName())); 408 event = reader.next()) { 409 if (event == XMLStreamReader.START_ELEMENT) { 410 if (new QName(OWS_NS_URL, "Identifier").equals(reader.getName())) { 411 matrixSet.identifier = reader.getElementText(); 412 } 413 if (new QName(OWS_NS_URL, "SupportedCRS").equals(reader.getName())) { 414 matrixSet.crs = crsToCode(reader.getElementText()); 415 } 416 if (new QName(WMTS_NS_URL, "TileMatrix").equals(reader.getName())) { 417 matrixSet.tileMatrix.add(parseTileMatrix(reader, matrixSet.crs)); 418 } 419 } 420 } 421 return matrixSet; 422 } 423 424 /** 425 * Parses TileMatrix section. Returns when reader is on TileMatrix closing tag. 426 * @param reader StAX reader instance 427 * @param matrixCrs projection used by this matrix 428 * @return TileMatrix object 429 * @throws XMLStreamException See {@link XMLStreamReader} 430 */ 431 private static final TileMatrix parseTileMatrix(XMLStreamReader reader, String matrixCrs) throws XMLStreamException { 432 Projection matrixProj = Projections.getProjectionByCode(matrixCrs); 433 TileMatrix ret = new TileMatrix(); 434 435 if (matrixProj == null) { 436 // use current projection if none found. Maybe user is using custom string 437 matrixProj = Main.getProjection(); 438 } 439 for (int event = reader.getEventType(); 440 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && new QName(WMTS_NS_URL, "TileMatrix").equals(reader.getName())); 441 event = reader.next()) { 442 if (event == XMLStreamReader.START_ELEMENT) { 443 if (new QName(OWS_NS_URL, "Identifier").equals(reader.getName())) { 444 ret.identifier = reader.getElementText(); 445 } 446 if (new QName(WMTS_NS_URL, "ScaleDenominator").equals(reader.getName())) { 447 ret.scaleDenominator = Double.parseDouble(reader.getElementText()); 448 } 449 if (new QName(WMTS_NS_URL, "TopLeftCorner").equals(reader.getName())) { 450 String[] topLeftCorner = reader.getElementText().split(" "); 451 if (matrixProj.switchXY()) { 452 ret.topLeftCorner = new EastNorth(Double.parseDouble(topLeftCorner[1]), Double.parseDouble(topLeftCorner[0])); 453 } else { 454 ret.topLeftCorner = new EastNorth(Double.parseDouble(topLeftCorner[0]), Double.parseDouble(topLeftCorner[1])); 455 } 456 } 457 if (new QName(WMTS_NS_URL, "TileHeight").equals(reader.getName())) { 458 ret.tileHeight = Integer.parseInt(reader.getElementText()); 459 } 460 if (new QName(WMTS_NS_URL, "TileWidth").equals(reader.getName())) { 461 ret.tileWidth = Integer.parseInt(reader.getElementText()); 462 } 463 if (new QName(WMTS_NS_URL, "MatrixHeight").equals(reader.getName())) { 464 ret.matrixHeight = Integer.parseInt(reader.getElementText()); 465 } 466 if (new QName(WMTS_NS_URL, "MatrixWidth").equals(reader.getName())) { 467 ret.matrixWidth = Integer.parseInt(reader.getElementText()); 468 } 469 } 470 } 471 if (ret.tileHeight != ret.tileWidth) { 472 throw new AssertionError(tr("Only square tiles are supported. {0}x{1} returned by server for TileMatrix identifier {2}", 473 ret.tileHeight, ret.tileWidth, ret.identifier)); 474 } 475 return ret; 476 } 477 478 /** 479 * Parses OperationMetadata section. Returns when reader is on OperationsMetadata closing tag. 480 * Sets this.baseUrl and this.transferMode 481 * 482 * @param reader StAX reader instance 483 * @throws XMLStreamException See {@link XMLStreamReader} 484 */ 485 private void parseOperationMetadata(XMLStreamReader reader) throws XMLStreamException { 486 for (int event = reader.getEventType(); 487 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && 488 new QName(OWS_NS_URL, "OperationsMetadata").equals(reader.getName())); 489 event = reader.next()) { 490 if (event == XMLStreamReader.START_ELEMENT) { 491 if (new QName(OWS_NS_URL, "Operation").equals(reader.getName()) && "GetTile".equals(reader.getAttributeValue("", "name")) && 492 moveReaderToTag(reader, new QName[]{ 493 new QName(OWS_NS_URL, "DCP"), 494 new QName(OWS_NS_URL, "HTTP"), 495 new QName(OWS_NS_URL, "Get"), 496 497 })) { 498 this.baseUrl = reader.getAttributeValue(XLINK_NS_URL, "href"); 499 this.transferMode = getTransferMode(reader); 500 } 501 } 502 } 503 } 504 505 /** 506 * Parses Operation[@name='GetTile']/DCP/HTTP/Get section. Returns when reader is on Get closing tag. 507 * @param reader StAX reader instance 508 * @return TransferMode coded in this section 509 * @throws XMLStreamException See {@link XMLStreamReader} 510 */ 511 private static final TransferMode getTransferMode(XMLStreamReader reader) throws XMLStreamException { 512 QName GET_QNAME = new QName(OWS_NS_URL, "Get"); 513 514 Utils.ensure(GET_QNAME.equals(reader.getName()), "WMTS Parser state invalid. Expected element %s, got %s", 515 GET_QNAME, reader.getName()); 516 for (int event = reader.getEventType(); 517 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && GET_QNAME.equals(reader.getName())); 518 event = reader.next()) { 519 if (event == XMLStreamReader.START_ELEMENT && new QName(OWS_NS_URL, "Constraint").equals(reader.getName())) { 520 if ("GetEncoding".equals(reader.getAttributeValue("", "name"))) { 521 moveReaderToTag(reader, new QName[]{ 522 new QName(OWS_NS_URL, "AllowedValues"), 523 new QName(OWS_NS_URL, "Value") 524 }); 525 return TransferMode.fromString(reader.getElementText()); 526 } 527 } 528 } 529 return null; 530 } 531 532 /** 533 * Moves reader to first occurrence of the structure equivalent of Xpath tags[0]/tags[1]../tags[n]. If fails to find 534 * moves the reader to the closing tag of current tag 535 * 536 * @param reader StAX reader instance 537 * @param tags array of tags 538 * @return true if tag was found, false otherwise 539 * @throws XMLStreamException See {@link XMLStreamReader} 540 */ 541 private static final boolean moveReaderToTag(XMLStreamReader reader, QName[] tags) throws XMLStreamException { 542 QName stopTag = reader.getName(); 543 int currentLevel = 0; 544 QName searchTag = tags[currentLevel]; 545 QName parentTag = null; 546 QName skipTag = null; 547 548 for (int event = 0; //skip current element, so we will not skip it as a whole 549 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && stopTag.equals(reader.getName())); 550 event = reader.next()) { 551 if (event == XMLStreamReader.END_ELEMENT && skipTag != null && skipTag.equals(reader.getName())) { 552 skipTag = null; 553 } 554 if (skipTag == null) { 555 if (event == XMLStreamReader.START_ELEMENT) { 556 if (searchTag.equals(reader.getName())) { 557 currentLevel += 1; 558 if (currentLevel >= tags.length) { 559 return true; // found! 560 } 561 parentTag = searchTag; 562 searchTag = tags[currentLevel]; 563 } else { 564 skipTag = reader.getName(); 565 } 566 } 567 568 if (event == XMLStreamReader.END_ELEMENT) { 569 if (parentTag != null && parentTag.equals(reader.getName())) { 570 currentLevel -= 1; 571 searchTag = parentTag; 572 if (currentLevel >= 0) { 573 parentTag = tags[currentLevel]; 574 } else { 575 parentTag = null; 576 } 577 } 578 } 579 } 580 } 581 return false; 582 } 583 584 private static final String normalizeCapabilitiesUrl(String url) throws MalformedURLException { 285 585 URL inUrl = new URL(url); 286 586 URL ret = new URL(inUrl.getProtocol(), inUrl.getHost(), inUrl.getPort(), inUrl.getFile()); … … 288 588 } 289 589 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) { 590 private static final String crsToCode(String crsIdentifier) { 358 591 if (crsIdentifier.startsWith("urn:ogc:def:crs:")) { 359 592 return crsIdentifier.replaceFirst("urn:ogc:def:crs:([^:]*):.*:(.*)$", "$1:$2"); 360 593 } 361 594 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 595 } 385 596
Note:
See TracChangeset
for help on using the changeset viewer.