From 31afaf1fca87483f9bc5dd558f484061648c9ad2 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 3 Aug 2010 15:21:06 +0000 Subject: [PATCH] Fixes for the WMTS layer to properly update matrix related properties. Determining the best matrix in the set based on current map resolution. Properly parsing TopLeftCorner coordinates and providing a way to specify identifiers for backwards CRS. r=ahocevar (closes #2677) git-svn-id: http://svn.openlayers.org/trunk/openlayers@10570 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/wmts-capabilities.js | 14 +- lib/OpenLayers/Format/OWSCommon/v1_1_0.js | 3 + lib/OpenLayers/Format/WMTSCapabilities.js | 19 +- .../Format/WMTSCapabilities/v1_0_0.js | 42 +++- lib/OpenLayers/Layer/WMTS.js | 203 ++++++++++++------ tests/Format/WMTSCapabilities/v1_0_0.html | 4 +- tests/Layer/WMTS.html | 13 +- 7 files changed, 217 insertions(+), 81 deletions(-) diff --git a/examples/wmts-capabilities.js b/examples/wmts-capabilities.js index adc4f9fcd9..7f138b4d1a 100644 --- a/examples/wmts-capabilities.js +++ b/examples/wmts-capabilities.js @@ -4,7 +4,19 @@ var map, format; function init() { - format = new OpenLayers.Format.WMTSCapabilities(); + format = new OpenLayers.Format.WMTSCapabilities({ + /** + * This particular service is not in compliance with the WMTS spec and + * is providing coordinates in y, x order regardless of the CRS. To + * work around this, we can provide the format a table of CRS URN that + * should be considered y, x order. These will extend the defaults on + * the format. + */ + yx: { + "urn:ogc:def:crs:EPSG::900913": true + } + }); + OpenLayers.Request.GET({ url: "http://v2.suite.opengeo.org/geoserver/gwc/service/wmts", params: { diff --git a/lib/OpenLayers/Format/OWSCommon/v1_1_0.js b/lib/OpenLayers/Format/OWSCommon/v1_1_0.js index 3539735102..3cc55d716b 100644 --- a/lib/OpenLayers/Format/OWSCommon/v1_1_0.js +++ b/lib/OpenLayers/Format/OWSCommon/v1_1_0.js @@ -51,6 +51,9 @@ OpenLayers.Format.OWSCommon.v1_1_0 = OpenLayers.Class(OpenLayers.Format.OWSCommo }, "Identifier": function(node, obj) { obj.identifier = this.getChildValue(node); + }, + "SupportedCRS": function(node, obj) { + obj.supportedCRS = this.getChildValue(node); } }, OpenLayers.Format.OWSCommon.v1.prototype.readers["ows"]) }, diff --git a/lib/OpenLayers/Format/WMTSCapabilities.js b/lib/OpenLayers/Format/WMTSCapabilities.js index f5f42f6202..38bf28d7df 100644 --- a/lib/OpenLayers/Format/WMTSCapabilities.js +++ b/lib/OpenLayers/Format/WMTSCapabilities.js @@ -31,7 +31,20 @@ OpenLayers.Format.WMTSCapabilities = OpenLayers.Class(OpenLayers.Format.XML, { * Property: parser * {} A cached versioned format used for reading. */ - parser: null, + parser: null, + + /** + * APIProperty: yx + * {Object} Members in the yx object are used to determine if a CRS URN + * corresponds to a CRS with y,x axis order. Member names are CRS URNs + * and values are boolean. By default, the following CRS URN are + * assumed to correspond to a CRS with y,x axis order: + * + * * urn:ogc:def:crs:EPSG::4326 + */ + yx: { + "urn:ogc:def:crs:EPSG::4326": true + }, /** * Constructor: OpenLayers.Format.WMTSCapabilities @@ -70,9 +83,9 @@ OpenLayers.Format.WMTSCapabilities = OpenLayers.Class(OpenLayers.Format.XML, { if (!constr) { throw new Error("Can't find a WMTS capabilities parser for version " + version); } - var parser = new constr(this.options); + this.parser = new constr(this.options); } - return parser.read(data); + return this.parser.read(data); }, /** diff --git a/lib/OpenLayers/Format/WMTSCapabilities/v1_0_0.js b/lib/OpenLayers/Format/WMTSCapabilities/v1_0_0.js index 1217805118..9bbd0f3f3a 100644 --- a/lib/OpenLayers/Format/WMTSCapabilities/v1_0_0.js +++ b/lib/OpenLayers/Format/WMTSCapabilities/v1_0_0.js @@ -33,6 +33,15 @@ OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class( xlink: "http://www.w3.org/1999/xlink" }, + /** + * Property: yx + * {Object} Members in the yx object are used to determine if a CRS URN + * corresponds to a CRS with y,x axis order. Member names are CRS URNs + * and values are boolean. Defaults come from the + * prototype. + */ + yx: null, + /** * Property: defaultPrefix * {String} The default namespace alias for creating element nodes. @@ -50,6 +59,10 @@ OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class( initialize: function(options) { OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); this.options = options; + var yx = OpenLayers.Util.extend( + {}, OpenLayers.Format.WMTSCapabilities.prototype.yx + ); + this.yx = OpenLayers.Util.extend(yx, this.yx); }, /** @@ -123,8 +136,9 @@ OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class( // duck type wmts:Contents by looking for layers if (obj.layers) { // TileMatrixSet as object type in schema - var tileMatrixSet = {}; - tileMatrixSet.matrixIds = []; + var tileMatrixSet = { + matrixIds: [] + }; this.readChildNodes(node, tileMatrixSet); obj.tileMatrixSets[tileMatrixSet.identifier] = tileMatrixSet; } else { @@ -133,7 +147,9 @@ OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class( } }, "TileMatrix": function(node, obj) { - var tileMatrix = {}; + var tileMatrix = { + supportedCRS: obj.supportedCRS + }; this.readChildNodes(node, tileMatrix); obj.matrixIds.push(tileMatrix); }, @@ -143,7 +159,25 @@ OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class( "TopLeftCorner": function(node, obj) { var topLeftCorner = this.getChildValue(node); var coords = topLeftCorner.split(" "); - obj.topLeftCorner = new OpenLayers.LonLat(parseFloat(coords[1]), parseFloat(coords[0])); + // decide on axis order for the given CRS + var yx; + if (obj.supportedCRS) { + // extract out version from URN + var crs = obj.supportedCRS.replace( + /urn:ogc:def:crs:(\w+):.+:(\w+)$/, + "urn:ogc:def:crs:$1::$2" + ); + yx = !!this.yx[crs]; + } + if (yx) { + obj.topLeftCorner = new OpenLayers.LonLat( + coords[1], coords[0] + ); + } else { + obj.topLeftCorner = new OpenLayers.LonLat( + coords[0], coords[1] + ); + } }, "TileWidth": function(node, obj) { obj.tileWidth = parseInt(this.getChildValue(node)); diff --git a/lib/OpenLayers/Layer/WMTS.js b/lib/OpenLayers/Layer/WMTS.js index b4e21ae578..5239782178 100644 --- a/lib/OpenLayers/Layer/WMTS.js +++ b/lib/OpenLayers/Layer/WMTS.js @@ -129,7 +129,26 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, { * become part of the request path, with order determined by the * list. */ - params: null, + params: null, + + /** + * APIProperty: zoomOffset + * {Number} If your cache has more levels than you want to provide + * access to with this layer, supply a zoomOffset. This zoom offset + * is added to the current map zoom level to determine the level + * for a requested tile. For example, if you supply a zoomOffset + * of 3, when the map is at the zoom 0, tiles will be requested from + * level 3 of your cache. Default is 0 (assumes cache level and map + * zoom are equivalent). Additionally, if this layer is to be used + * as an overlay and the cache has fewer zoom levels than the base + * layer, you can supply a negative zoomOffset. For example, if a + * map zoom level of 1 corresponds to your cache level zero, you would + * supply a -1 zoomOffset (and set the maxResolution of the layer + * appropriately). The zoomOffset value has no effect if complete + * matrix definitions (including scaleDenominator) are supplied in + * the property. Defaults to 0 (no zoom offset). + */ + zoomOffset: 0, /** * Property: formatSuffixMap @@ -147,6 +166,13 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, { "jpg": "jpg" }, + /** + * Property: matrix + * {Object} Matrix definition for the current map resolution. Updated by + * the method. + */ + matrix: null, + /** * Constructor: OpenLayers.Layer.WMTS * Create a new WMTS layer. @@ -175,10 +201,7 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, { * Any other documented layer properties can be provided in the config object. */ initialize: function(config) { - config.params = OpenLayers.Util.upperCaseObject(config.params); - var args = [config.name, config.url, config.params, config]; - OpenLayers.Layer.Grid.prototype.initialize.apply(this, args); - + // confirm required properties are supplied var required = { url: true, @@ -187,11 +210,16 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, { matrixSet: true }; for (var prop in required) { - if (!(prop in this)) { + if (!(prop in config)) { throw new Error("Missing property '" + prop + "' in layer configuration."); } } + config.params = OpenLayers.Util.upperCaseObject(config.params); + var args = [config.name, config.url, config.params, config]; + OpenLayers.Layer.Grid.prototype.initialize.apply(this, args); + + // determine format suffix (for REST) if (!this.formatSuffix) { this.formatSuffix = this.formatSuffixMap[this.format] || this.format.split("/").pop(); @@ -211,60 +239,54 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, { }, + /** + * Method: setMap + */ + setMap: function() { + OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); + this.updateMatrixProperties(); + }, + /** * Method: updateMatrixProperties - * Set as a listener for zoom end to update tile matrix related properties. + * Called when map resolution changes to update matrix related properties. */ updateMatrixProperties: function() { - if (this.matrixIds && this.matrixIds.length) { - var zoom = this.map.getZoom(); - var matrix = this.matrixIds[zoom]; - if (matrix) { - if (matrix.topLeftCorner) { - this.tileOrigin = matrix.topLeftCorner; - } - if (matrix.tileWidth && matrix.tileHeight) { - this.tileSize = new OpenLayers.Size( - matrix.tileWidth, matrix.tileHeight - ); - } + this.matrix = this.getMatrix(); + if (this.matrix) { + if (this.matrix.topLeftCorner) { + this.tileOrigin = this.matrix.topLeftCorner; + } + if (this.matrix.tileWidth && this.matrix.tileHeight) { + this.tileSize = new OpenLayers.Size( + this.matrix.tileWidth, this.matrix.tileHeight + ); + } + if (!this.tileOrigin) { + this.tileOrigin = new OpenLayers.LonLat( + this.maxExtent.left, this.maxExtent.top + ); + } + if (!this.tileFullExtent) { + this.tileFullExtent = this.maxExtent; } } }, /** - * Method: setMap - * Overwrite default from Layer - * + * Method: moveTo + * * Parameters: - * map - {} + * bound - {} + * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to + * do some init work in that case. + * dragging - {Boolean} */ - setMap: function(map) { - OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); - this.map.events.on({ - zoomend: this.updateMatrixProperties, - scope: this - }); - this.updateMatrixProperties(); - if (!this.tileOrigin) { - this.tileOrigin = new OpenLayers.LonLat(this.maxExtent.left, this.maxExtent.top); - } - if (!this.tileFullExtent) { - this.tileFullExtent = this.maxExtent; + moveTo:function(bounds, zoomChanged, dragging) { + if (zoomChanged || !this.matrix) { + this.updateMatrixProperties(); } - }, - - /** - * Method: removeMap - */ - removeMap: function() { - if (this.map) { - this.map.events.un({ - zoomend: this.updateMatrixProperties, - scope: this - }); - } - OpenLayers.Layer.Grid.prototype.removeMap.apply(this, arguments); + return OpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments); }, /** @@ -287,19 +309,66 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, { }, /** - * Method: getMatrixId - * Determine the appropriate matrix id for the given map resolution. + * Method: getMatrix + * Get the appropriate matrix definition for the current map resolution. */ - getMatrixId: function() { - var id; - var zoom = this.map.getZoom(); + getMatrix: function() { + var matrix; if (!this.matrixIds || this.matrixIds.length === 0) { - id = zoom; + matrix = {identifier: this.map.getZoom() + this.zoomOffset}; } else { - // TODO: get appropriate matrix id given the map resolution - id = this.matrixIds[zoom].identifier; + // get appropriate matrix given the map scale if possible + if ("scaleDenominator" in this.matrixIds[0]) { + // scale denominator calculation based on WMTS spec + var denom = + OpenLayers.METERS_PER_INCH * + OpenLayers.INCHES_PER_UNIT[this.units] * + this.map.getResolution() / 0.28E-3; + var diff = Number.POSITIVE_INFINITY; + var delta; + for (var i=0, ii=this.matrixIds.length; i 1e6 1e6 - 84 -180 + -180 84 256 256 60000 @@ -280,7 +280,7 @@ http://schemas.opengis.net/wmts/1.0/examples/wmtsGetCapabilities_response.xml 2.5e6 2.5e6 - 84 -180 + -180 84 256 256 9000 diff --git a/tests/Layer/WMTS.html b/tests/Layer/WMTS.html index 0c275da019..b733bac2b7 100644 --- a/tests/Layer/WMTS.html +++ b/tests/Layer/WMTS.html @@ -18,6 +18,8 @@ format: "image/png", isBaseLayer: false, requestEncoding: "KVP", + maxResolution: 0.3521969032857032, + numZoomLevels: 7, matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds }); @@ -40,7 +42,7 @@ t.eq(layer1.tileSize.h, 512.0, "tileSize h is set correctly"); } - function test_setMap(t) { + function test_moveTo(t) { t.plan(9); var xml = document.getElementById("capabilities").firstChild.nodeValue; var doc = new OpenLayers.Format.XML().read(xml); @@ -54,12 +56,16 @@ matrixSet: "arcgis-online-wgs84", format: "image/png", requestEncoding: "KVP", + maxResolution: 0.3521969032857032, + numZoomLevels: 7, matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds }); var map = new OpenLayers.Map('map'); map.addLayer(layer0); + map.setCenter(new OpenLayers.LonLat(-97, 38), 1); + t.ok((layer0.tileOrigin instanceof OpenLayers.LonLat), "tileOrigin is an instance of OpenLayers.LonLat"); t.ok((layer0.tileOrigin.lon == -180 && layer0.tileOrigin.lat == 90), "tileOrigin is set correctly"); t.ok((layer0.tileSize instanceof OpenLayers.Size), "tileSize is an instance of OpenLayers.Size"); @@ -177,6 +183,8 @@ matrixSet: "arcgis-online-wgs84", format: "image/png", requestEncoding: "KVP", + maxResolution: 0.3521969032857032, + numZoomLevels: 7, matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds }); @@ -188,7 +196,8 @@ matrixSet: "arcgis_online", format: "image/jpeg", tileSize: new OpenLayers.Size(512, 512), - requestEncoding: "REST" + requestEncoding: "REST", + isBaseLayer: false }); var options = {