diff --git a/examples/wmts-from-capabilities.html b/examples/wmts-from-capabilities.html
new file mode 100644
index 0000000000..5d090fff56
--- /dev/null
+++ b/examples/wmts-from-capabilities.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+ WMTS from capabilities example
+
+
+
+
+
+
+
+
+
+
+
+
+
WMTS from capabilities example
+
Example of a WMTS source built from a WMTS getCapabilities response.
+
+
wmts
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/wmts-from-capabilities.js b/examples/wmts-from-capabilities.js
new file mode 100644
index 0000000000..9858e676e5
--- /dev/null
+++ b/examples/wmts-from-capabilities.js
@@ -0,0 +1,47 @@
+goog.require('ol.Coordinate');
+goog.require('ol.Extent');
+goog.require('ol.Map');
+goog.require('ol.RendererHints');
+goog.require('ol.View2D');
+goog.require('ol.layer.TileLayer');
+goog.require('ol.parser.ogc.WMTSCapabilities');
+goog.require('ol.projection');
+goog.require('ol.source.WMTS');
+
+
+var map, capabilities;
+var parser = new ol.parser.ogc.WMTSCapabilities();
+var projection = ol.projection.configureProj4jsProjection({
+ code: 'EPSG:21781',
+ extent: new ol.Extent(485869.5728, 76443.1884, 837076.5648, 299941.7864)
+});
+
+var xhr = new XMLHttpRequest();
+xhr.open('GET', 'http://wmts.geo.admin.ch/1.0.0/WMTSCapabilities.xml', true);
+
+
+/**
+ * onload handler for the XHR request.
+ */
+xhr.onload = function() {
+ if (xhr.status == 200) {
+ capabilities = parser.read(xhr.responseXML);
+ var wmtsOptions = ol.source.WMTS.optionsFromCapabilities(
+ capabilities, 'ch.swisstopo.pixelkarte-farbe');
+ map = new ol.Map({
+ layers: [
+ new ol.layer.TileLayer({
+ source: new ol.source.WMTS(wmtsOptions)
+ })
+ ],
+ renderers: ol.RendererHints.createFromQueryData(),
+ target: 'map',
+ view: new ol.View2D({
+ center: projection.getExtent().getCenter(),
+ projection: projection,
+ zoom: 1
+ })
+ });
+ }
+};
+xhr.send();
diff --git a/examples/wmts.html b/examples/wmts.html
new file mode 100644
index 0000000000..07d0f69eda
--- /dev/null
+++ b/examples/wmts.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+ WMTS example
+
+
+
+
+
+
+
+
+
+
+
+
+
WMTS example
+
Example of a WMTS source.
+
+
wmts
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/wmts.js b/examples/wmts.js
new file mode 100644
index 0000000000..9ac2d31a1b
--- /dev/null
+++ b/examples/wmts.js
@@ -0,0 +1,54 @@
+goog.require('ol.Coordinate');
+goog.require('ol.Extent');
+goog.require('ol.Map');
+goog.require('ol.RendererHint');
+goog.require('ol.View2D');
+goog.require('ol.layer.TileLayer');
+goog.require('ol.projection');
+goog.require('ol.source.OpenStreetMap');
+goog.require('ol.source.WMTS');
+goog.require('ol.tilegrid.WMTS');
+
+
+var projection = ol.projection.get('EPSG:900913');
+var projectionExtent = projection.getExtent();
+var size = projectionExtent.getWidth() / 256;
+var resolutions = new Array(26);
+var matrixIds = new Array(26);
+for (var z = 0; z < 26; ++z) {
+ // generate resolutions and matrixIds arrays for this WMTS
+ resolutions[z] = size / Math.pow(2, z);
+ matrixIds[z] = 'EPSG:900913:' + z;
+}
+
+var map = new ol.Map({
+ layers: [
+ new ol.layer.TileLayer({
+ source: new ol.source.OpenStreetMap(),
+ opacity: 0.7
+ }),
+ new ol.layer.TileLayer({
+ source: new ol.source.WMTS({
+ url: 'http://v2.suite.opengeo.org/geoserver/gwc/service/wmts/',
+ layer: 'medford:buildings',
+ matrixSet: 'EPSG:900913',
+ format: 'image/png',
+ projection: projection,
+ tileGrid: new ol.tilegrid.WMTS({
+ origin: projectionExtent.getTopLeft(),
+ resolutions: resolutions,
+ matrixIds: matrixIds
+ }),
+ style: '_null',
+ crossOrigin: null, // FIXME: this should be the default
+ extent: new ol.Extent(-13682835, 5204068, -13667473, 5221690)
+ })
+ })
+ ],
+ renderer: ol.RendererHint.CANVAS,
+ target: 'map',
+ view: new ol.View2D({
+ center: new ol.Coordinate(-13677832, 5213272),
+ zoom: 13
+ })
+});
diff --git a/src/objectliterals.exports b/src/objectliterals.exports
index 2e348ad82d..b2a4019bce 100644
--- a/src/objectliterals.exports
+++ b/src/objectliterals.exports
@@ -150,6 +150,22 @@
@exportObjectLiteralProperty ol.source.TiledWMSOptions.url string|undefined
@exportObjectLiteralProperty ol.source.TiledWMSOptions.urls Array.|undefined
+@exportObjectLiteral ol.source.WMTSOptions
+@exportObjectLiteralProperty ol.source.WMTSOptions.attributions Array.|undefined
+@exportObjectLiteralProperty ol.source.WMTSOptions.crossOrigin string|null|undefined
+@exportObjectLiteralProperty ol.source.WMTSOptions.extent ol.Extent|undefined
+@exportObjectLiteralProperty ol.source.WMTSOptions.tileGrid ol.tilegrid.WMTS
+@exportObjectLiteralProperty ol.source.WMTSOptions.projection ol.Projection|undefined
+@exportObjectLiteralProperty ol.source.WMTSOptions.requestEncoding ol.source.WMTSRequestEncoding|undefined
+@exportObjectLiteralProperty ol.source.WMTSOptions.layer string
+@exportObjectLiteralProperty ol.source.WMTSOptions.style string
+@exportObjectLiteralProperty ol.source.WMTSOptions.format string|undefined
+@exportObjectLiteralProperty ol.source.WMTSOptions.matrixSet string
+@exportObjectLiteralProperty ol.source.WMTSOptions.dimensions Object|undefined
+@exportObjectLiteralProperty ol.source.WMTSOptions.url string|undefined
+@exportObjectLiteralProperty ol.source.WMTSOptions.maxZoom number|undefined
+@exportObjectLiteralProperty ol.source.WMTSOptions.urls Array.|undefined
+
@exportObjectLiteral ol.tilegrid.TileGridOptions
@exportObjectLiteralProperty ol.tilegrid.TileGridOptions.origin ol.Coordinate|undefined
@exportObjectLiteralProperty ol.tilegrid.TileGridOptions.origins Array.|undefined
@@ -157,6 +173,14 @@
@exportObjectLiteralProperty ol.tilegrid.TileGridOptions.tileSize ol.Size|undefined
@exportObjectLiteralProperty ol.tilegrid.TileGridOptions.tileSizes Array.|undefined
+@exportObjectLiteral ol.tilegrid.WMTSOptions
+@exportObjectLiteralProperty ol.tilegrid.WMTSOptions.origin ol.Coordinate|undefined
+@exportObjectLiteralProperty ol.tilegrid.WMTSOptions.origins Array.|undefined
+@exportObjectLiteralProperty ol.tilegrid.WMTSOptions.resolutions !Array.
+@exportObjectLiteralProperty ol.tilegrid.WMTSOptions.matrixIds !Array.
+@exportObjectLiteralProperty ol.tilegrid.WMTSOptions.tileSize ol.Size|undefined
+@exportObjectLiteralProperty ol.tilegrid.WMTSOptions.tileSizes Array.|undefined
+
@exportObjectLiteral ol.tilegrid.XYZOptions
@exportObjectLiteralProperty ol.tilegrid.XYZOptions.maxZoom number
diff --git a/src/ol/coordinate.exports b/src/ol/coordinate.exports
index 1dec787cd7..c538ed312c 100644
--- a/src/ol/coordinate.exports
+++ b/src/ol/coordinate.exports
@@ -1,2 +1,5 @@
@exportSymbol ol.Coordinate
-@exportProperty ol.Coordinate.toStringHDMS
+@exportSymbol ol.Coordinate.createStringXY
+@exportSymbol ol.Coordinate.toStringHDMS
+@exportSymbol ol.Coordinate.toStringXY
+@exportSymbol ol.Coordinate.fromProjectedArray
diff --git a/src/ol/extent.exports b/src/ol/extent.exports
index b52ee8007a..9ba8337eaa 100644
--- a/src/ol/extent.exports
+++ b/src/ol/extent.exports
@@ -1,3 +1,11 @@
@exportSymbol ol.Extent
+@exportProperty ol.Extent.prototype.getCenter
@exportProperty ol.Extent.prototype.getHeight
@exportProperty ol.Extent.prototype.getWidth
+@exportProperty ol.Extent.prototype.containsCoordinate
+@exportProperty ol.Extent.prototype.containsExtent
+@exportProperty ol.Extent.prototype.getBottomLeft
+@exportProperty ol.Extent.prototype.getBottomRight
+@exportProperty ol.Extent.prototype.getTopLeft
+@exportProperty ol.Extent.prototype.getTopRight
+@exportProperty ol.Extent.prototype.transform
diff --git a/src/ol/parser/ogc/wmtscapabilities_v1_0_0.js b/src/ol/parser/ogc/wmtscapabilities_v1_0_0.js
index 4c88cdd59f..11cf87949d 100644
--- a/src/ol/parser/ogc/wmtscapabilities_v1_0_0.js
+++ b/src/ol/parser/ogc/wmtscapabilities_v1_0_0.js
@@ -67,7 +67,7 @@ ol.parser.ogc.WMTSCapabilities_v1_0_0 = function() {
},
'TileMatrix': function(node, obj) {
var tileMatrix = {
- 'supportedCRS': obj.supportedCRS
+ 'supportedCRS': obj['supportedCRS']
};
this.readChildNodes(node, tileMatrix);
obj['matrixIds'].push(tileMatrix);
diff --git a/src/ol/source/wmtssource.exports b/src/ol/source/wmtssource.exports
new file mode 100644
index 0000000000..ef840a8124
--- /dev/null
+++ b/src/ol/source/wmtssource.exports
@@ -0,0 +1,2 @@
+@exportClass ol.source.WMTS ol.source.WMTSOptions
+@exportSymbol ol.source.WMTS.optionsFromCapabilities
diff --git a/src/ol/source/wmtssource.js b/src/ol/source/wmtssource.js
new file mode 100644
index 0000000000..cd5b1b8a85
--- /dev/null
+++ b/src/ol/source/wmtssource.js
@@ -0,0 +1,260 @@
+goog.provide('ol.source.WMTS');
+goog.provide('ol.source.WMTSRequestEncoding');
+
+goog.require('ol.Attribution');
+goog.require('ol.Extent');
+goog.require('ol.Projection');
+goog.require('ol.TileCoord');
+goog.require('ol.TileUrlFunction');
+goog.require('ol.TileUrlFunctionType');
+goog.require('ol.projection');
+goog.require('ol.source.ImageTileSource');
+goog.require('ol.tilegrid.WMTS');
+
+
+/**
+ * @enum {string}
+ */
+ol.source.WMTSRequestEncoding = {
+ KVP: 'KVP', // see spec §8
+ REST: 'REST' // see spec §10
+};
+
+
+
+/**
+ * @constructor
+ * @extends {ol.source.ImageTileSource}
+ * @param {ol.source.WMTSOptions} wmtsOptions WMTS options.
+ */
+ol.source.WMTS = function(wmtsOptions) {
+
+ // TODO: add support for TileMatrixLimits
+
+ var version = goog.isDef(wmtsOptions.version) ?
+ wmtsOptions.version : '1.0.0';
+ var format = goog.isDef(wmtsOptions.format) ?
+ wmtsOptions.format : 'image/jpeg';
+ var dimensions = wmtsOptions.dimensions || {};
+
+ // FIXME: should we guess this requestEncoding from wmtsOptions.url(s)
+ // structure? that would mean KVP only if a template is not provided.
+ var requestEncoding = goog.isDef(wmtsOptions.requestEncoding) ?
+ wmtsOptions.requestEncoding : ol.source.WMTSRequestEncoding.KVP;
+
+ // FIXME: should we create a default tileGrid?
+ // we could issue a getCapabilities xhr to retrieve missing configuration
+ var tileGrid = wmtsOptions.tileGrid;
+
+ var context = {
+ 'Layer': wmtsOptions.layer,
+ 'style': wmtsOptions.style,
+ 'TileMatrixSet': wmtsOptions.matrixSet
+ };
+ goog.object.extend(context, dimensions);
+ var kvpParams;
+ if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
+ kvpParams = {
+ 'Service': 'WMTS',
+ 'Request': 'GetTile',
+ 'Version': version,
+ 'Format': format,
+ 'TileMatrix': '{TileMatrix}',
+ 'TileRow': '{TileRow}',
+ 'TileCol': '{TileCol}'
+ };
+ goog.object.extend(kvpParams, context);
+ }
+
+ // TODO: factorize the code below so that it is usable by all sources
+ var urls = wmtsOptions.urls;
+ if (!goog.isDef(urls)) {
+ urls = [];
+ var url = wmtsOptions.url;
+ goog.asserts.assert(goog.isDef(url));
+ var match = /\{(\d)-(\d)\}/.exec(url) || /\{([a-z])-([a-z])\}/.exec(url);
+ if (match) {
+ var startCharCode = match[1].charCodeAt(0);
+ var stopCharCode = match[2].charCodeAt(0);
+ var charCode;
+ for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) {
+ urls.push(url.replace(match[0], String.fromCharCode(charCode)));
+ }
+ } else {
+ urls.push(url);
+ }
+ }
+
+ /**
+ * @param {string} template Template.
+ * @return {ol.TileUrlFunctionType} Tile URL function.
+ */
+ function createFromWMTSTemplate(template) {
+ return function(tileCoord) {
+ if (goog.isNull(tileCoord)) {
+ return undefined;
+ } else {
+ var localContext = {
+ 'TileMatrix': tileGrid.getMatrixId(tileCoord.z),
+ 'TileCol': tileCoord.x,
+ 'TileRow': tileCoord.y
+ };
+ if (requestEncoding != ol.source.WMTSRequestEncoding.KVP) {
+ goog.object.extend(localContext, context);
+ }
+ var url = template;
+ for (var key in localContext) {
+ // FIXME: should we filter properties with hasOwnProperty?
+ url = url.replace('{' + key + '}', localContext[key])
+ .replace('%7B' + key + '%7D', localContext[key]);
+ }
+ return url;
+ }
+ };
+ }
+
+ var tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions(
+ goog.array.map(urls, function(url) {
+ if (goog.isDef(kvpParams)) {
+ // TODO: we may want to create our own appendParams function
+ // so that params order conforms to wmts spec guidance,
+ // and so that we can avoid to escape special template params
+ url = goog.uri.utils.appendParamsFromMap(url, kvpParams);
+ }
+ return createFromWMTSTemplate(url);
+ }));
+
+ tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform(
+ function(tileCoord, tileGrid, projection) {
+ if (tileGrid.getResolutions().length <= tileCoord.z) {
+ return null;
+ }
+ var x = tileCoord.x;
+ var y = -tileCoord.y - 1;
+ var tileExtent = tileGrid.getTileCoordExtent(tileCoord);
+ var projectionExtent = projection.getExtent();
+ var extent = goog.isDef(wmtsOptions.extent) ?
+ wmtsOptions.extent : projectionExtent;
+
+ if (!goog.isNull(extent) && projection.isGlobal() &&
+ extent.minX === projectionExtent.minX &&
+ extent.maxX === projectionExtent.maxX) {
+ var numCols = Math.ceil(
+ (extent.maxX - extent.minX) /
+ (tileExtent.maxX - tileExtent.minX));
+ x = goog.math.modulo(x, numCols);
+ tileExtent = tileGrid.getTileCoordExtent(
+ new ol.TileCoord(tileCoord.z, x, tileCoord.y));
+ }
+ if (!tileExtent.intersects(extent)) {
+ return null;
+ }
+ return new ol.TileCoord(tileCoord.z, x, y);
+ },
+ tileUrlFunction);
+
+ goog.base(this, {
+ attributions: wmtsOptions.attributions,
+ crossOrigin: wmtsOptions.crossOrigin,
+ extent: wmtsOptions.extent,
+ projection: wmtsOptions.projection,
+ tileGrid: tileGrid,
+ tileUrlFunction: tileUrlFunction
+ });
+
+};
+goog.inherits(ol.source.WMTS, ol.source.ImageTileSource);
+
+
+/**
+ * @param {Object} wmtsCap An object representing the capabilities document.
+ * @param {string} layer The layer identifier.
+ * @return {ol.source.WMTSOptions} WMTS source options object.
+ */
+ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, layer) {
+
+ // TODO: add support for TileMatrixLimits
+
+ var layers = wmtsCap['contents']['layers'];
+ var l = goog.array.find(layers, function(elt, index, array) {
+ return elt['identifier'] == layer;
+ });
+ goog.asserts.assert(!goog.isNull(l));
+ goog.asserts.assert(l['tileMatrixSetLinks'].length > 0);
+ var matrixSet = /** @type {string} */
+ (l['tileMatrixSetLinks'][0]['tileMatrixSet']);
+ var format = /** @type {string} */ (l['formats'][0]);
+ var idx = goog.array.findIndex(l['styles'], function(elt, index, array) {
+ return elt['isDefault'];
+ });
+ if (idx < 0) {
+ idx = 0;
+ }
+ var style = /** @type {string} */ (l['styles'][idx]['identifier']);
+
+ var dimensions = {};
+ goog.array.forEach(l['dimensions'], function(elt, index, array) {
+ var key = elt['identifier'];
+ var value = elt['default'];
+ if (goog.isDef(value)) {
+ goog.asserts.assert(goog.array.indexOf(elt['values'], value) >= 0);
+ } else {
+ value = elt['values'][0];
+ }
+ goog.asserts.assert(goog.isDef(value));
+ dimensions[key] = value;
+ });
+
+ var matrixSets = wmtsCap['contents']['tileMatrixSets'];
+ goog.asserts.assert(matrixSet in matrixSets);
+ var matrixSetObj = matrixSets[matrixSet];
+
+ var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet(
+ matrixSetObj);
+
+ var projection = ol.projection.get(matrixSetObj['supportedCRS']);
+
+ var gets = wmtsCap['operationsMetadata']['GetTile']['dcp']['http']['get'];
+ var encodings = goog.object.getKeys(
+ gets[0]['constraints']['GetEncoding']['allowedValues']);
+ goog.asserts.assert(encodings.length > 0);
+ var requestEncoding = /** @type {ol.source.WMTSRequestEncoding} */
+ (encodings[0]);
+ // TODO: arcgis support, quote from ol2:
+ // The OGC documentation is not clear if we should use REST or RESTful,
+ // ArcGis use RESTful, and OpenLayers use REST.
+
+ var urls;
+ switch (requestEncoding) {
+ case ol.source.WMTSRequestEncoding.REST:
+ goog.asserts.assert(l['resourceUrls'].hasOwnProperty('tile'));
+ goog.asserts.assert(l['resourceUrls']['tile'].hasOwnProperty(format));
+ urls = /** @type {Array.} */
+ (l['resourceUrls']['tile'][format]);
+ break;
+ case ol.source.WMTSRequestEncoding.KVP:
+ urls = [];
+ goog.array.forEach(gets, function(elt, index, array) {
+ if (elt['constraints']['GetEncoding']['allowedValues'].hasOwnProperty(
+ ol.source.WMTSRequestEncoding.KVP)) {
+ urls.push(/** @type {string} */ (elt['url']));
+ }
+ });
+ goog.asserts.assert(urls.length > 0);
+ break;
+ default:
+ goog.asserts.assert(false);
+ }
+
+ return {
+ urls: urls,
+ layer: layer,
+ matrixSet: matrixSet,
+ format: format,
+ projection: projection,
+ requestEncoding: requestEncoding,
+ tileGrid: tileGrid,
+ style: style,
+ dimensions: dimensions
+ };
+};
diff --git a/src/ol/tilegrid/wmtstilegrid.exports b/src/ol/tilegrid/wmtstilegrid.exports
new file mode 100644
index 0000000000..bf723f3d17
--- /dev/null
+++ b/src/ol/tilegrid/wmtstilegrid.exports
@@ -0,0 +1 @@
+@exportClass ol.tilegrid.WMTS ol.tilegrid.WMTSOptions
diff --git a/src/ol/tilegrid/wmtstilegrid.js b/src/ol/tilegrid/wmtstilegrid.js
new file mode 100644
index 0000000000..b939795662
--- /dev/null
+++ b/src/ol/tilegrid/wmtstilegrid.js
@@ -0,0 +1,83 @@
+goog.provide('ol.tilegrid.WMTS');
+
+goog.require('ol.Size');
+goog.require('ol.projection');
+goog.require('ol.tilegrid.TileGrid');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.tilegrid.TileGrid}
+ * @param {ol.tilegrid.WMTSOptions} wmtsOptions WMTS options.
+ */
+ol.tilegrid.WMTS = function(wmtsOptions) {
+
+ goog.asserts.assert(
+ wmtsOptions.resolutions.length == wmtsOptions.matrixIds.length);
+
+ /**
+ * @private
+ * @type {!Array.}
+ */
+ this.matrixIds_ = wmtsOptions.matrixIds;
+ // FIXME: should the matrixIds become optionnal?
+
+ goog.base(this, {
+ origin: wmtsOptions.origin,
+ origins: wmtsOptions.origins,
+ resolutions: wmtsOptions.resolutions,
+ tileSize: wmtsOptions.tileSize,
+ tileSizes: wmtsOptions.tileSizes
+ });
+
+};
+goog.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid);
+
+
+/**
+ * @param {number} z Z.
+ * @return {string} MatrixId..
+ */
+ol.tilegrid.WMTS.prototype.getMatrixId = function(z) {
+ goog.asserts.assert(0 <= z && z < this.matrixIds_.length);
+ return this.matrixIds_[z];
+};
+
+
+/**
+ * @return {Array.} MatrixIds.
+ */
+ol.tilegrid.WMTS.prototype.getMatrixIds = function() {
+ return this.matrixIds_;
+};
+
+
+/**
+ * @param {Object} matrixSet An object representing a matrixSet in the
+ * capabilities document.
+ * @return {ol.tilegrid.WMTS} WMTS tileGrid instance.
+ */
+ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet =
+ function(matrixSet) {
+
+ var resolutions = [];
+ var matrixIds = [];
+ var origins = [];
+ var tileSizes = [];
+ var projection = ol.projection.get(matrixSet['supportedCRS']);
+ var metersPerUnit = projection.getMetersPerUnit();
+ goog.array.forEach(matrixSet['matrixIds'], function(elt, index, array) {
+ matrixIds.push(elt['identifier']);
+ origins.push(elt['topLeftCorner']);
+ resolutions.push(elt['scaleDenominator'] * 0.28E-3 / metersPerUnit);
+ tileSizes.push(new ol.Size(elt['tileWidth'], elt['tileHeight']));
+ });
+
+ return new ol.tilegrid.WMTS({
+ origins: origins,
+ resolutions: resolutions,
+ matrixIds: matrixIds,
+ tileSizes: tileSizes
+ });
+};
diff --git a/src/ol/tileurlfunction.js b/src/ol/tileurlfunction.js
index 3c270b80c6..209febe19c 100644
--- a/src/ol/tileurlfunction.js
+++ b/src/ol/tileurlfunction.js
@@ -60,6 +60,9 @@ ol.TileUrlFunction.createFromTemplates = function(templates) {
* @return {ol.TileUrlFunctionType} Tile URL function.
*/
ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) {
+ if (tileUrlFunctions.length === 1) {
+ return tileUrlFunctions[0];
+ }
return function(tileCoord, tileGrid, projection) {
if (goog.isNull(tileCoord)) {
return undefined;