Adding a WMTS layer and capabilities parser to work with OGC WMTS service implementations. Thanks August Town for this nice contribution. p=august,me r=me (closes #2637)

git-svn-id: http://svn.openlayers.org/trunk/openlayers@10388 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
Tim Schaub
2010-06-12 02:41:51 +00:00
parent f7113df376
commit d1e24bdcc0
11 changed files with 2450 additions and 0 deletions

View File

@@ -124,6 +124,7 @@
"OpenLayers/Layer/WMS.js",
"OpenLayers/Layer/WMS/Untiled.js",
"OpenLayers/Layer/WMS/Post.js",
"OpenLayers/Layer/WMTS.js",
"OpenLayers/Layer/ArcIMS.js",
"OpenLayers/Layer/GeoRSS.js",
"OpenLayers/Layer/Boxes.js",
@@ -289,6 +290,8 @@
"OpenLayers/Format/SOSGetFeatureOfInterest.js",
"OpenLayers/Format/OWSContext.js",
"OpenLayers/Format/OWSContext/v0_3_1.js",
"OpenLayers/Format/WMTSCapabilities.js",
"OpenLayers/Format/WMTSCapabilities/v1_0_0.js",
"OpenLayers/Layer/WFS.js",
"OpenLayers/Control/GetFeature.js",
"OpenLayers/Control/MouseToolbar.js",

View File

@@ -48,6 +48,9 @@ OpenLayers.Format.OWSCommon.v1_1_0 = OpenLayers.Class(OpenLayers.Format.OWSCommo
},
"MaximumValue": function(node, range) {
range.maxValue = this.getChildValue(node);
},
"Identifier": function(node, obj) {
obj.identifier = this.getChildValue(node);
}
}, OpenLayers.Format.OWSCommon.v1.prototype.readers["ows"])
},

View File

@@ -0,0 +1,80 @@
/* Copyright (c) 2006-2009 MetaCarta, Inc., published under the Clear BSD
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Format/XML.js
*/
/**
* Class: OpenLayers.Format.WMTSCapabilities
* Read WMTS Capabilities.
*
* Inherits from:
* - <OpenLayers.Format.XML>
*/
OpenLayers.Format.WMTSCapabilities = OpenLayers.Class(OpenLayers.Format.XML, {
/**
* APIProperty: defaultVersion
* {String} Version number to assume if none found. Default is "1.0.0".
*/
defaultVersion: "1.0.0",
/**
* APIProperty: version
* {String} Specify a version string if one is known.
*/
version: null,
/**
* Property: parser
* {<OpenLayers.Format>} A cached versioned format used for reading.
*/
parser: null,
/**
* Constructor: OpenLayers.Format.WMTSCapabilities
* Create a new parser for WMTS capabilities.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*/
initialize: function(options) {
OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
this.options = options;
},
/**
* APIMethod: read
* Read capabilities data from a string, and return information about
* the service (offering and observedProperty mostly).
*
* Parameters:
* data - {String} or {DOMElement} data to read/parse.
*
* Returns:
* {Object} Info about the WMTS Capabilities
*/
read: function(data) {
if (typeof data == "string") {
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
}
var root = data.documentElement;
var version = this.version || root.getAttribute("version") || this.defaultVersion;
if (!this.parser || this.parser.version !== version) {
var constr = OpenLayers.Format.WMTSCapabilities[
"v" + version.replace(/\./g, "_")
];
if (!constr) {
throw new Error("Can't find a WMTS capabilities parser for version " + version);
}
var parser = new constr(this.options);
}
return parser.read(data);
},
CLASS_NAME: "OpenLayers.Format.WMTSCapabilities"
});

View File

@@ -0,0 +1,186 @@
/* Copyright (c) 2006-2009 MetaCarta, Inc., published under the Clear BSD
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Format/WMTSCapabilities.js
* @requires OpenLayers/Format/OWSCommon/v1_1_0.js
*/
/**
* Class: OpenLayers.Format.WMTSCapabilities.v1_0_0
* Read WMTS Capabilities version 1.0.0.
*
* Inherits from:
* - <OpenLayers.Format.WMTSCapabilities>
*/
OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class(
OpenLayers.Format.OWSCommon.v1_1_0, {
/**
* Property: version
* {String} The parser version ("1.0.0").
*/
version: "1.0.0",
/**
* Property: namespaces
* {Object} Mapping of namespace aliases to namespace URIs.
*/
namespaces: {
ows: "http://www.opengis.net/ows/1.1",
wmts: "http://www.opengis.net/wmts/1.0",
xlink: "http://www.w3.org/1999/xlink"
},
/**
* Property: defaultPrefix
* {String} The default namespace alias for creating element nodes.
*/
defaultPrefix: "wmts",
/**
* Constructor: OpenLayers.Format.WMTSCapabilities.v1_0_0
* Create a new parser for WMTS capabilities version 1.0.0.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*/
initialize: function(options) {
OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
this.options = options;
},
/**
* APIMethod: read
* Read capabilities data from a string, and return info about the WMTS.
*
* Parameters:
* data - {String} or {DOMElement} data to read/parse.
*
* Returns:
* {Object} Information about the SOS service.
*/
read: function(data) {
if(typeof data == "string") {
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
}
if(data && data.nodeType == 9) {
data = data.documentElement;
}
var capabilities = {};
this.readNode(data, capabilities);
capabilities.version = this.version;
return capabilities;
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"wmts": {
"Capabilities": function(node, obj) {
this.readChildNodes(node, obj);
},
"Contents": function(node, obj) {
obj.contents = {};
obj.contents.layers = [];
obj.contents.tileMatrixSets = {};
this.readChildNodes(node, obj.contents);
},
"Layer": function(node, obj) {
var layer = {
styles: [],
formats: [],
tileMatrixSetLinks: []
};
layer.layers = [];
this.readChildNodes(node, layer);
obj.layers.push(layer);
},
"Style": function(node, obj) {
var style = {};
style.isDefault = node.getAttribute("isDefault");
this.readChildNodes(node, style);
obj.styles.push(style);
},
"Format": function(node, obj) {
obj.formats.push(this.getChildValue(node));
},
"TileMatrixSetLink": function(node, obj) {
var tileMatrixSetLink = {};
this.readChildNodes(node, tileMatrixSetLink);
obj.tileMatrixSetLinks.push(tileMatrixSetLink);
},
"TileMatrixSet": function(node, obj) {
// node could be child of wmts:Contents or wmts:TileMatrixSetLink
// duck type wmts:Contents by looking for layers
if (obj.layers) {
// TileMatrixSet as object type in schema
var tileMatrixSet = {};
tileMatrixSet.matrixIds = [];
this.readChildNodes(node, tileMatrixSet);
obj.tileMatrixSets[tileMatrixSet.identifier] = tileMatrixSet;
} else {
// TileMatrixSet as string type in schema
obj.tileMatrixSet = this.getChildValue(node);
}
},
"TileMatrix": function(node, obj) {
var tileMatrix = {};
this.readChildNodes(node, tileMatrix);
obj.matrixIds.push(tileMatrix);
},
"ScaleDenominator": function(node, obj) {
obj.scaleDenominator = parseFloat(this.getChildValue(node));
},
"TopLeftCorner": function(node, obj) {
var topLeftCorner = this.getChildValue(node);
var coords = topLeftCorner.split(" ");
obj.topLeftCorner = new OpenLayers.LonLat(parseFloat(coords[1]), parseFloat(coords[0]));
},
"TileWidth": function(node, obj) {
obj.tileWidth = parseInt(this.getChildValue(node));
},
"TileHeight": function(node, obj) {
obj.tileHeight = parseInt(this.getChildValue(node));
},
"MatrixWidth": function(node, obj) {
obj.matrixWidth = parseInt(this.getChildValue(node));
},
"MatrixHeight": function(node, obj) {
obj.matrixHeight = parseInt(this.getChildValue(node));
},
// not used for now, can be added in the future though
/*"Themes": function(node, obj) {
obj.themes = [];
this.readChildNodes(node, obj.themes);
},
"Theme": function(node, obj) {
var theme = {};
this.readChildNodes(node, theme);
obj.push(theme);
},*/
"WSDL": function(node, obj) {
obj.wsdl = {};
obj.wsdl.href = node.getAttribute("xlink:href");
// TODO: other attributes of <WSDL> element
},
"ServiceMetadataURL": function(node, obj) {
obj.serviceMetadataUrl = {};
obj.serviceMetadataUrl.href = node.getAttribute("xlink:href");
// TODO: other attributes of <ServiceMetadataURL> element
}
},
"ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
},
CLASS_NAME: "OpenLayers.Format.WMTSCapabilities.v1_0_0"
});

View File

@@ -0,0 +1,394 @@
/* Copyright (c) 2006-2009 MetaCarta, Inc., published under the Clear BSD
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Layer/Grid.js
* @requires OpenLayers/Tile/Image.js
*/
/**
* Class: OpenLayers.Layer.WMTS
* Instances of the WMTS class allow viewing of tiles from a service that
* implements the OGC WMTS specification version 1.0.0.
*
* Inherits from:
* - <OpenLayers.Layer.Grid>
*/
OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
/**
* APIProperty: isBaseLayer
* {Boolean}
*/
isBaseLayer: true,
/**
* Property: version
* {String} WMTS version. Default is "1.0.0".
*/
version: "1.0.0",
/**
* APIProperty: requestEncoding
* {String} Request encoding. Can be "REST" or "KVP". Default is "KVP".
*/
requestEncoding: "KVP",
/**
* APIProperty: url
* {String} The base URL for the WMTS service.
*/
url: null,
/**
* APIProperty: layer
* {String} The layer identifier advertised by the WMTS service.
*/
layer: null,
/**
* APIProperty: matrixSet
* {String} One of the advertised matrix set identifiers. Must be provided.
*/
matrixSet: null,
/**
* APIProperty: style
* {String} One of the advertised layer styles. Must be provided.
*/
style: null,
/**
* APIProperty: format
* {String} The image MIME type. Default is "image/jpeg".
*/
format: "image/jpeg",
/**
* APIProperty: tileOrigin
* {<OpenLayers.LonLat>} The top-left corner of the tile matrix in map
* units.
*/
tileOrigin: null,
/**
* APIProperty: tileFullExtent
* {<OpenLayers.Bounds>} The full extent of the tile set. If not supplied,
* the layer's <maxExtent> property will be used.
*/
tileFullExtent: null,
/**
* APIProperty: formatSuffix
* {String} For REST request encoding, an image format suffix must be
* included in the request. If not provided, the suffix will be derived
* from the <format> property.
*/
formatSuffix: null,
/**
* APIProperty: matrixIds
* {Array} A list of tile matrix identifiers. If not provided, the matrix
* identifiers will be assumed to be integers corresponding to the
* map zoom level. If a list of strings is provided, each item should
* be the matrix identifier that corresponds to the map zoom level.
* Additionally, a list of objects can be provided. Each object should
* describe the matrix as presented in the WMTS capabilities. These
* objects should have the propertes shown below.
*
* Matrix properties:
* identifier - {String} The matrix identifier (required).
* topLeftCorner - {<OpenLayers.LonLat>} The top left corner of the
* matrix. Must be provided if different than the layer <tileOrigin>.
* tileWidth - {Number} The tile width for the matrix. Must be provided
* if different than the width given in the layer <tileSize>.
* tileHeight - {Number} The tile height for the matrix. Must be provided
* if different than the height given in the layer <tileSize>.
*/
matrixIds: null,
/**
* APIProperty: dimensions
* {Array} For RESTful request encoding, extra dimensions may be specified.
* Items in this list should be property names in the <params> object.
* Values of extra dimensions will be determined from the corresponding
* values in the <params> object.
*/
dimensions: null,
/**
* APIProperty: params
* {Object} Extra parameters to include in tile requests. For KVP
* <requestEncoding>, these properties will be encoded in the request
* query string. For REST <requestEncoding>, these properties will
* become part of the request path, with order determined by the
* <dimensions> list.
*/
params: null,
/**
* Property: formatSuffixMap
* {Object} a map between WMTS 'format' request parameter and tile image file suffix
*/
formatSuffixMap: {
"image/png": "png",
"image/png8": "png",
"image/png24": "png",
"image/png32": "png",
"png": "png",
"image/jpeg": "jpg",
"image/jpg": "jpg",
"jpeg": "jpg",
"jpg": "jpg"
},
/**
* Constructor: OpenLayers.Layer.WMTS
* Create a new WMTS layer.
*
* Parameters:
* config - {Object} Configuration properties for the layer.
*
* Required configuration properties:
* url - {String} The base url for the service. See the <url> property.
* layer - {String} The layer identifier. See the <layer> property.
* matrixSet - {String} The tile matrix set identifier. See the <matrixSet>
* property.
*
* 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,
layer: true,
style: true,
matrixSet: true
};
for (var prop in required) {
if (!(prop in this)) {
throw new Error("Missing property '" + prop + "' in layer configuration.");
}
}
// determine format suffix (for REST)
if (!this.formatSuffix) {
this.formatSuffix = this.formatSuffixMap[this.format] || this.format.split("/").pop();
}
// expand matrixIds (may be array of string or array of object)
if (this.matrixIds) {
var len = this.matrixIds.length;
if (len && typeof this.matrixIds[0] === "string") {
var ids = this.matrixIds;
this.matrixIds = new Array(len)
for (var i=0; i<len; ++i) {
this.matrixIds[i] = {identifier: ids[i]};
}
}
}
},
/**
* Method: updateMatrixProperties
* Set as a listener for zoom end to update tile 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
);
}
}
}
},
/**
* Method: setMap
* Overwrite default <setMap> from Layer
*
* Parameters:
* map - {<OpenLayers.Map>}
*/
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;
}
},
/**
* Method: removeMap
*/
removeMap: function() {
if (this.map) {
this.map.events.un({
zoomend: this.updateMatrixProperties,
scope: this
});
}
OpenLayers.Layer.Grid.prototype.removeMap.apply(this, arguments);
},
/**
* APIMethod: clone
*
* Parameters:
* obj - {Object}
*
* Returns:
* {<OpenLayers.Layer.WMTS>} An exact clone of this <OpenLayers.Layer.WMTS>
*/
clone: function(obj) {
if (obj == null) {
obj = new OpenLayers.Layer.WMTS(this.options);
}
//get all additions from superclasses
obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
// copy/set any non-init, non-simple values here
return obj;
},
/**
* Method: getMatrixId
* Determine the appropriate matrix id for the given map resolution.
*/
getMatrixId: function() {
var id;
var zoom = this.map.getZoom();
if (!this.matrixIds || this.matrixIds.length === 0) {
id = zoom;
} else {
// TODO: get appropriate matrix id given the map resolution
id = this.matrixIds[zoom].identifier;
}
return id;
},
/**
* Method: getURL
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
*
* Returns:
* {String} A URL for the tile corresponding to the given bounds.
*/
getURL: function(bounds) {
bounds = this.adjustBounds(bounds);
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();
if (this.requestEncoding.toUpperCase() === "REST") {
// include 'version', 'layer' and 'style' in tile resource url
var path = this.version + "/" + this.layer + "/" + this.style + "/";
// append optional dimension path elements
if (this.dimensions) {
for (var i=0; i<this.dimensions.length; i++) {
if (this.params[this.dimensions[i]]) {
path = path + this.params[this.dimensions[i]] + "/";
}
}
}
// append other required path elements
path = path + this.matrixSet + "/" + matrixId + "/" + row + "/" + col + "." + this.formatSuffix;
if (this.url instanceof Array) {
url = this.selectUrl(path, url)
} else {
url = this.url;
}
if (!url.match(/\/$/)) {
url = url + "/";
}
url = url + path;
} else if (this.requestEncoding.toUpperCase() === "KVP") {
// assemble all required parameters
var params = {
SERVICE: "WMTS",
REQUEST: "GetTile",
VERSION: this.version,
LAYER: this.layer,
STYLE: this.style,
TILEMATRIXSET: this.matrixSet,
TILEMATRIX: matrixId,
TILEROW: row,
TILECOL: col,
FORMAT: this.format
};
url = OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this, [params]);
}
}
return url;
},
/**
* APIMethod: mergeNewParams
* Extend the existing layer <params> with new properties. Tiles will be
* reloaded with updated params in the request.
*
* Parameters:
* newParams - {Object} Properties to extend to existing <params>.
*/
mergeNewParams: function(newParams) {
if (this.requestEncoding.toUpperCase() === "KVP") {
return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(
this, [OpenLayers.Util.upperCaseObject(newParams)]
);
}
},
/**
* Method: addTile
* Create a tile, initialize it, and add it to the layer div.
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
* position - {<OpenLayers.Pixel>}
*
* Returns:
* {<OpenLayers.Tile.Image>} The added OpenLayers.Tile.Image
*/
addTile: function(bounds,position) {
return new OpenLayers.Tile.Image(this, position, bounds,
null, this.tileSize);
},
CLASS_NAME: "OpenLayers.Layer.WMTS"
});