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

View File

@@ -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: {

View File

@@ -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"])
},

View File

@@ -31,7 +31,20 @@ OpenLayers.Format.WMTSCapabilities = OpenLayers.Class(OpenLayers.Format.XML, {
* Property: parser
* {<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
@@ -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);
},
/**

View File

@@ -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
* <OpenLayers.Format.WMTSCapabilities> 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));

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
* <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
@@ -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 <updateMatrixProperties> 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 <setMap> from Layer
*
* Method: moveTo
*
* 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) {
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<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 = "";
if (!this.tileFullExtent || this.tileFullExtent.intersectsBounds(bounds)) {
var res = this.map.getResolution();
var zoom = this.map.getZoom();
var center = bounds.getCenterLonLat();
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();
var center = bounds.getCenterLonLat();
var info = this.getTileInfo(center);
var matrixId = this.matrix.identifier;
if (this.requestEncoding.toUpperCase() === "REST") {
@@ -340,7 +404,8 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
}
// 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) {
url = this.selectUrl(path, this.url);
@@ -362,9 +427,9 @@ OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
LAYER: this.layer,
STYLE: this.style,
TILEMATRIXSET: this.matrixSet,
TILEMATRIX: matrixId,
TILEROW: row,
TILECOL: col,
TILEMATRIX: this.matrix.identifier,
TILEROW: info.row,
TILECOL: info.col,
FORMAT: this.format
};
url = OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this, [params]);

View File

@@ -271,7 +271,7 @@ http://schemas.opengis.net/wmts/1.0/examples/wmtsGetCapabilities_response.xml
<TileMatrix>
<ows:Identifier>1e6</ows:Identifier>
<ScaleDenominator>1e6</ScaleDenominator>
<TopLeftCorner>84 -180</TopLeftCorner>
<TopLeftCorner>-180 84</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>60000</MatrixWidth>
@@ -280,7 +280,7 @@ http://schemas.opengis.net/wmts/1.0/examples/wmtsGetCapabilities_response.xml
<TileMatrix>
<ows:Identifier>2.5e6</ows:Identifier>
<ScaleDenominator>2.5e6</ScaleDenominator>
<TopLeftCorner>84 -180</TopLeftCorner>
<TopLeftCorner>-180 84</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>9000</MatrixWidth>

View File

@@ -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 = {