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
This commit is contained in:
Tim Schaub
2010-08-03 15:21:06 +00:00
parent f89fb1f226
commit 31afaf1fca
7 changed files with 217 additions and 81 deletions
+13 -1
View File
@@ -4,7 +4,19 @@ var map, format;
function init() { 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({ OpenLayers.Request.GET({
url: "http://v2.suite.opengeo.org/geoserver/gwc/service/wmts", url: "http://v2.suite.opengeo.org/geoserver/gwc/service/wmts",
params: { params: {
@@ -51,6 +51,9 @@ OpenLayers.Format.OWSCommon.v1_1_0 = OpenLayers.Class(OpenLayers.Format.OWSCommo
}, },
"Identifier": function(node, obj) { "Identifier": function(node, obj) {
obj.identifier = this.getChildValue(node); obj.identifier = this.getChildValue(node);
},
"SupportedCRS": function(node, obj) {
obj.supportedCRS = this.getChildValue(node);
} }
}, OpenLayers.Format.OWSCommon.v1.prototype.readers["ows"]) }, OpenLayers.Format.OWSCommon.v1.prototype.readers["ows"])
}, },
+16 -3
View File
@@ -31,7 +31,20 @@ OpenLayers.Format.WMTSCapabilities = OpenLayers.Class(OpenLayers.Format.XML, {
* Property: parser * Property: parser
* {<OpenLayers.Format>} A cached versioned format used for reading. * {<OpenLayers.Format>} 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 * Constructor: OpenLayers.Format.WMTSCapabilities
@@ -70,9 +83,9 @@ OpenLayers.Format.WMTSCapabilities = OpenLayers.Class(OpenLayers.Format.XML, {
if (!constr) { if (!constr) {
throw new Error("Can't find a WMTS capabilities parser for version " + version); 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);
}, },
/** /**
@@ -33,6 +33,15 @@ OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class(
xlink: "http://www.w3.org/1999/xlink" 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
* <OpenLayers.Format.WMTSCapabilities> prototype.
*/
yx: null,
/** /**
* Property: defaultPrefix * Property: defaultPrefix
* {String} The default namespace alias for creating element nodes. * {String} The default namespace alias for creating element nodes.
@@ -50,6 +59,10 @@ OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class(
initialize: function(options) { initialize: function(options) {
OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
this.options = 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 // duck type wmts:Contents by looking for layers
if (obj.layers) { if (obj.layers) {
// TileMatrixSet as object type in schema // TileMatrixSet as object type in schema
var tileMatrixSet = {}; var tileMatrixSet = {
tileMatrixSet.matrixIds = []; matrixIds: []
};
this.readChildNodes(node, tileMatrixSet); this.readChildNodes(node, tileMatrixSet);
obj.tileMatrixSets[tileMatrixSet.identifier] = tileMatrixSet; obj.tileMatrixSets[tileMatrixSet.identifier] = tileMatrixSet;
} else { } else {
@@ -133,7 +147,9 @@ OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class(
} }
}, },
"TileMatrix": function(node, obj) { "TileMatrix": function(node, obj) {
var tileMatrix = {}; var tileMatrix = {
supportedCRS: obj.supportedCRS
};
this.readChildNodes(node, tileMatrix); this.readChildNodes(node, tileMatrix);
obj.matrixIds.push(tileMatrix); obj.matrixIds.push(tileMatrix);
}, },
@@ -143,7 +159,25 @@ OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class(
"TopLeftCorner": function(node, obj) { "TopLeftCorner": function(node, obj) {
var topLeftCorner = this.getChildValue(node); var topLeftCorner = this.getChildValue(node);
var coords = topLeftCorner.split(" "); 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) { "TileWidth": function(node, obj) {
obj.tileWidth = parseInt(this.getChildValue(node)); obj.tileWidth = parseInt(this.getChildValue(node));
+134 -69
View File
@@ -129,7 +129,26 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
* become part of the request path, with order determined by the * become part of the request path, with order determined by the
* <dimensions> list. * <dimensions> 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 <matrixIds> property. Defaults to 0 (no zoom offset).
*/
zoomOffset: 0,
/** /**
* Property: formatSuffixMap * Property: formatSuffixMap
@@ -147,6 +166,13 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
"jpg": "jpg" "jpg": "jpg"
}, },
/**
* Property: matrix
* {Object} Matrix definition for the current map resolution. Updated by
* the <updateMatrixProperties> method.
*/
matrix: null,
/** /**
* Constructor: OpenLayers.Layer.WMTS * Constructor: OpenLayers.Layer.WMTS
* Create a new WMTS layer. * 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. * Any other documented layer properties can be provided in the config object.
*/ */
initialize: function(config) { 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 // confirm required properties are supplied
var required = { var required = {
url: true, url: true,
@@ -187,11 +210,16 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
matrixSet: true matrixSet: true
}; };
for (var prop in required) { for (var prop in required) {
if (!(prop in this)) { if (!(prop in config)) {
throw new Error("Missing property '" + prop + "' in layer configuration."); 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) // determine format suffix (for REST)
if (!this.formatSuffix) { if (!this.formatSuffix) {
this.formatSuffix = this.formatSuffixMap[this.format] || this.format.split("/").pop(); 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 * 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() { updateMatrixProperties: function() {
if (this.matrixIds && this.matrixIds.length) { this.matrix = this.getMatrix();
var zoom = this.map.getZoom(); if (this.matrix) {
var matrix = this.matrixIds[zoom]; if (this.matrix.topLeftCorner) {
if (matrix) { this.tileOrigin = this.matrix.topLeftCorner;
if (matrix.topLeftCorner) { }
this.tileOrigin = matrix.topLeftCorner; if (this.matrix.tileWidth && this.matrix.tileHeight) {
} this.tileSize = new OpenLayers.Size(
if (matrix.tileWidth && matrix.tileHeight) { this.matrix.tileWidth, this.matrix.tileHeight
this.tileSize = new OpenLayers.Size( );
matrix.tileWidth, 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 * Method: moveTo
* Overwrite default <setMap> from Layer *
*
* Parameters: * Parameters:
* map - {<OpenLayers.Map>} * bound - {<OpenLayers.Bounds>}
* zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
* do some init work in that case.
* dragging - {Boolean}
*/ */
setMap: function(map) { moveTo:function(bounds, zoomChanged, dragging) {
OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); if (zoomChanged || !this.matrix) {
this.map.events.on({ this.updateMatrixProperties();
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;
} }
}, return OpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments);
/**
* Method: removeMap
*/
removeMap: function() {
if (this.map) {
this.map.events.un({
zoomend: this.updateMatrixProperties,
scope: this
});
}
OpenLayers.Layer.Grid.prototype.removeMap.apply(this, arguments);
}, },
/** /**
@@ -287,19 +309,66 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
}, },
/** /**
* Method: getMatrixId * Method: getMatrix
* Determine the appropriate matrix id for the given map resolution. * Get the appropriate matrix definition for the current map resolution.
*/ */
getMatrixId: function() { getMatrix: function() {
var id; var matrix;
var zoom = this.map.getZoom();
if (!this.matrixIds || this.matrixIds.length === 0) { if (!this.matrixIds || this.matrixIds.length === 0) {
id = zoom; matrix = {identifier: this.map.getZoom() + this.zoomOffset};
} else { } else {
// TODO: get appropriate matrix id given the map resolution // get appropriate matrix given the map scale if possible
id = this.matrixIds[zoom].identifier; 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<ii; ++i) {
delta = Math.abs(1 - (this.matrixIds[i].scaleDenominator / denom));
if (delta < diff) {
diff = delta;
matrix = this.matrixIds[i];
}
}
} else {
// fall back on zoom as index
matrix = this.matrixIds[this.map.getZoom() + this.zoomOffset];
}
} }
return id; return matrix;
},
/**
* Method: getTileInfo
* Get tile information for a given location at the current map resolution.
*
* Parameters:
* loc - {<OpenLayers.LonLat} A location in map coordinates.
*
* Returns:
* {Object} An object with "col", "row", "i", and "j" properties. The col
* and row values are zero based tile indexes from the top left. The
* i and j values are the number of pixels to the left and top
* (respectively) of the given location within the target tile.
*/
getTileInfo: function(loc) {
var res = this.map.getResolution();
var fx = (loc.lon - this.tileOrigin.lon) / (res * this.tileSize.w);
var fy = (this.tileOrigin.lat - loc.lat) / (res * this.tileSize.h);
var col = Math.floor(fx);
var row = Math.floor(fy);
return {
col: col,
row: row,
i: Math.floor((fx - col) * this.tileSize.w),
j: Math.floor((fy - row) * this.tileSize.h)
};
}, },
/** /**
@@ -316,14 +385,9 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
var url = ""; var url = "";
if (!this.tileFullExtent || this.tileFullExtent.intersectsBounds(bounds)) { if (!this.tileFullExtent || this.tileFullExtent.intersectsBounds(bounds)) {
var res = this.map.getResolution(); var center = bounds.getCenterLonLat();
var zoom = this.map.getZoom(); var info = this.getTileInfo(center);
var center = bounds.getCenterLonLat(); var matrixId = this.matrix.identifier;
var col = Math.floor((center.lon - this.tileOrigin.lon) / (res * this.tileSize.w));
var row = Math.floor((this.tileOrigin.lat - center.lat) / (res * this.tileSize.h));
var matrixId = this.getMatrixId();
if (this.requestEncoding.toUpperCase() === "REST") { if (this.requestEncoding.toUpperCase() === "REST") {
@@ -340,7 +404,8 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
} }
// append other required path elements // append other required path elements
path = path + this.matrixSet + "/" + matrixId + "/" + row + "/" + col + "." + this.formatSuffix; path = path + this.matrixSet + "/" + this.matrix.identifier +
"/" + info.row + "/" + info.col + "." + this.formatSuffix;
if (this.url instanceof Array) { if (this.url instanceof Array) {
url = this.selectUrl(path, this.url); url = this.selectUrl(path, this.url);
@@ -362,9 +427,9 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
LAYER: this.layer, LAYER: this.layer,
STYLE: this.style, STYLE: this.style,
TILEMATRIXSET: this.matrixSet, TILEMATRIXSET: this.matrixSet,
TILEMATRIX: matrixId, TILEMATRIX: this.matrix.identifier,
TILEROW: row, TILEROW: info.row,
TILECOL: col, TILECOL: info.col,
FORMAT: this.format FORMAT: this.format
}; };
url = OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this, [params]); url = OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this, [params]);
+2 -2
View File
@@ -271,7 +271,7 @@ http://schemas.opengis.net/wmts/1.0/examples/wmtsGetCapabilities_response.xml
<TileMatrix> <TileMatrix>
<ows:Identifier>1e6</ows:Identifier> <ows:Identifier>1e6</ows:Identifier>
<ScaleDenominator>1e6</ScaleDenominator> <ScaleDenominator>1e6</ScaleDenominator>
<TopLeftCorner>84 -180</TopLeftCorner> <TopLeftCorner>-180 84</TopLeftCorner>
<TileWidth>256</TileWidth> <TileWidth>256</TileWidth>
<TileHeight>256</TileHeight> <TileHeight>256</TileHeight>
<MatrixWidth>60000</MatrixWidth> <MatrixWidth>60000</MatrixWidth>
@@ -280,7 +280,7 @@ http://schemas.opengis.net/wmts/1.0/examples/wmtsGetCapabilities_response.xml
<TileMatrix> <TileMatrix>
<ows:Identifier>2.5e6</ows:Identifier> <ows:Identifier>2.5e6</ows:Identifier>
<ScaleDenominator>2.5e6</ScaleDenominator> <ScaleDenominator>2.5e6</ScaleDenominator>
<TopLeftCorner>84 -180</TopLeftCorner> <TopLeftCorner>-180 84</TopLeftCorner>
<TileWidth>256</TileWidth> <TileWidth>256</TileWidth>
<TileHeight>256</TileHeight> <TileHeight>256</TileHeight>
<MatrixWidth>9000</MatrixWidth> <MatrixWidth>9000</MatrixWidth>
+11 -2
View File
@@ -18,6 +18,8 @@
format: "image/png", format: "image/png",
isBaseLayer: false, isBaseLayer: false,
requestEncoding: "KVP", requestEncoding: "KVP",
maxResolution: 0.3521969032857032,
numZoomLevels: 7,
matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds
}); });
@@ -40,7 +42,7 @@
t.eq(layer1.tileSize.h, 512.0, "tileSize h is set correctly"); t.eq(layer1.tileSize.h, 512.0, "tileSize h is set correctly");
} }
function test_setMap(t) { function test_moveTo(t) {
t.plan(9); t.plan(9);
var xml = document.getElementById("capabilities").firstChild.nodeValue; var xml = document.getElementById("capabilities").firstChild.nodeValue;
var doc = new OpenLayers.Format.XML().read(xml); var doc = new OpenLayers.Format.XML().read(xml);
@@ -54,12 +56,16 @@
matrixSet: "arcgis-online-wgs84", matrixSet: "arcgis-online-wgs84",
format: "image/png", format: "image/png",
requestEncoding: "KVP", requestEncoding: "KVP",
maxResolution: 0.3521969032857032,
numZoomLevels: 7,
matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds
}); });
var map = new OpenLayers.Map('map'); var map = new OpenLayers.Map('map');
map.addLayer(layer0); 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 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.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"); t.ok((layer0.tileSize instanceof OpenLayers.Size), "tileSize is an instance of OpenLayers.Size");
@@ -177,6 +183,8 @@
matrixSet: "arcgis-online-wgs84", matrixSet: "arcgis-online-wgs84",
format: "image/png", format: "image/png",
requestEncoding: "KVP", requestEncoding: "KVP",
maxResolution: 0.3521969032857032,
numZoomLevels: 7,
matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds
}); });
@@ -188,7 +196,8 @@
matrixSet: "arcgis_online", matrixSet: "arcgis_online",
format: "image/jpeg", format: "image/jpeg",
tileSize: new OpenLayers.Size(512, 512), tileSize: new OpenLayers.Size(512, 512),
requestEncoding: "REST" requestEncoding: "REST",
isBaseLayer: false
}); });
var options = { var options = {