From 700903ca5c37147ab0a61ef639b8f3d2a7c87c9f Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 28 Apr 2015 20:03:56 +0200 Subject: [PATCH 1/5] Add tests for ol.tilegrid.TileGrid These tests are taken from ol.tilegrid.XYZ, to make sure that ol.tilegrid.TileGrid works the same way. The additional tests show that the #getTileCoordForXYAndResolution_() method do not handle coordinates at tile boundaries properly, so this is fixed. --- src/ol/tilegrid/tilegrid.js | 7 +- test/spec/ol/tilegrid/tilegrid.test.js | 145 ++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 3 deletions(-) diff --git a/src/ol/tilegrid/tilegrid.js b/src/ol/tilegrid/tilegrid.js index 738b8cbd90..9c08e6ee9c 100644 --- a/src/ol/tilegrid/tilegrid.js +++ b/src/ol/tilegrid/tilegrid.js @@ -365,8 +365,11 @@ ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function( var origin = this.getOrigin(z); var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); - var tileCoordX = scale * (x - origin[0]) / (resolution * tileSize[0]); - var tileCoordY = scale * (y - origin[1]) / (resolution * tileSize[1]); + var adjust = reverseIntersectionPolicy ? 0.5 : 0; + var xFromOrigin = ((x - origin[0]) / resolution + adjust) | 0; + var yFromOrigin = ((y - origin[1]) / resolution + adjust) | 0; + var tileCoordX = scale * xFromOrigin / tileSize[0]; + var tileCoordY = scale * yFromOrigin / tileSize[1]; if (reverseIntersectionPolicy) { tileCoordX = Math.ceil(tileCoordX) - 1; diff --git a/test/spec/ol/tilegrid/tilegrid.test.js b/test/spec/ol/tilegrid/tilegrid.test.js index a8b53c8860..14f8b14d83 100644 --- a/test/spec/ol/tilegrid/tilegrid.test.js +++ b/test/spec/ol/tilegrid/tilegrid.test.js @@ -269,7 +269,150 @@ describe('ol.tilegrid.TileGrid', function() { }); - describe('getTileCoordFromCoordAndZ', function() { + describe('#getTileCoordChildTileRange()', function() { + + var tileGrid; + beforeEach(function() { + tileGrid = ol.tilegrid.createForExtent( + ol.proj.get('EPSG:3857').getExtent(), 22); + }); + + it('returns the tile range for one zoom level deeper', function() { + var range; + + range = tileGrid.getTileCoordChildTileRange([0, 0, 0]); + expect(range.minX).to.be(0); + expect(range.maxX).to.be(1); + expect(range.minY).to.be(0); + expect(range.maxY).to.be(1); + + range = tileGrid.getTileCoordChildTileRange([0, 1, 0]); + expect(range.minX).to.be(2); + expect(range.maxX).to.be(3); + expect(range.minY).to.be(0); + expect(range.maxY).to.be(1); + + range = tileGrid.getTileCoordChildTileRange([0, 0, 1]); + expect(range.minX).to.be(0); + expect(range.maxX).to.be(1); + expect(range.minY).to.be(2); + expect(range.maxY).to.be(3); + + range = tileGrid.getTileCoordChildTileRange([0, -1, 0]); + expect(range.minX).to.be(-2); + expect(range.maxX).to.be(-1); + expect(range.minY).to.be(0); + expect(range.maxY).to.be(1); + + range = tileGrid.getTileCoordChildTileRange([0, 0, -1]); + expect(range.minX).to.be(0); + expect(range.maxX).to.be(1); + expect(range.minY).to.be(-2); + expect(range.maxY).to.be(-1); + }); + + it('returns null for z > maxZoom', function() { + var max = tileGrid.maxZoom; + var range = tileGrid.getTileCoordChildTileRange([max + 1, 0, 0]); + expect(range).to.be(null); + }); + + }); + + describe('#forEachTileCoordParentTileRange()', function() { + + var tileGrid; + beforeEach(function() { + tileGrid = ol.tilegrid.createForExtent( + ol.proj.get('EPSG:3857').getExtent(), 22); + }); + + it('iterates as expected', function() { + + var tileCoord = [5, 11, 21]; + var zs = [], tileRanges = []; + tileGrid.forEachTileCoordParentTileRange( + tileCoord, + function(z, tileRange) { + zs.push(z); + tileRanges.push(new ol.TileRange( + tileRange.minX, tileRange.maxX, + tileRange.minY, tileRange.maxY)); + return false; + }); + + expect(zs.length).to.eql(5); + expect(tileRanges.length).to.eql(5); + + expect(zs[0]).to.eql(4); + expect(tileRanges[0].minX).to.eql(5); + expect(tileRanges[0].maxX).to.eql(5); + expect(tileRanges[0].minY).to.eql(10); + expect(tileRanges[0].maxY).to.eql(10); + + expect(zs[1]).to.eql(3); + expect(tileRanges[1].minX).to.eql(2); + expect(tileRanges[1].maxX).to.eql(2); + expect(tileRanges[1].minY).to.eql(5); + expect(tileRanges[1].maxY).to.eql(5); + + expect(zs[2]).to.eql(2); + expect(tileRanges[2].minX).to.eql(1); + expect(tileRanges[2].maxX).to.eql(1); + expect(tileRanges[2].minY).to.eql(2); + expect(tileRanges[2].maxY).to.eql(2); + + expect(zs[3]).to.eql(1); + expect(tileRanges[3].minX).to.eql(0); + expect(tileRanges[3].maxX).to.eql(0); + expect(tileRanges[3].minY).to.eql(1); + expect(tileRanges[3].maxY).to.eql(1); + + expect(zs[4]).to.eql(0); + expect(tileRanges[4].minX).to.eql(0); + expect(tileRanges[4].maxX).to.eql(0); + expect(tileRanges[4].minY).to.eql(0); + expect(tileRanges[4].maxY).to.eql(0); + + }); + + }); + + describe('getResolution', function() { + + var tileGrid; + beforeEach(function() { + tileGrid = ol.tilegrid.createForExtent( + ol.proj.get('EPSG:3857').getExtent(), 22); + }); + + it('returns the correct resolution at the equator', function() { + // @see http://msdn.microsoft.com/en-us/library/aa940990.aspx + expect(tileGrid.getResolution(0)).to.roughlyEqual(156543.04, 1e-2); + expect(tileGrid.getResolution(1)).to.roughlyEqual(78271.52, 1e-2); + expect(tileGrid.getResolution(2)).to.roughlyEqual(39135.76, 1e-2); + expect(tileGrid.getResolution(3)).to.roughlyEqual(19567.88, 1e-2); + expect(tileGrid.getResolution(4)).to.roughlyEqual(9783.94, 1e-2); + expect(tileGrid.getResolution(5)).to.roughlyEqual(4891.97, 1e-2); + expect(tileGrid.getResolution(6)).to.roughlyEqual(2445.98, 1e-2); + expect(tileGrid.getResolution(7)).to.roughlyEqual(1222.99, 1e-2); + expect(tileGrid.getResolution(8)).to.roughlyEqual(611.50, 1e-2); + expect(tileGrid.getResolution(9)).to.roughlyEqual(305.75, 1e-2); + expect(tileGrid.getResolution(10)).to.roughlyEqual(152.87, 1e-2); + expect(tileGrid.getResolution(11)).to.roughlyEqual(76.44, 1e-2); + expect(tileGrid.getResolution(12)).to.roughlyEqual(38.22, 1e-2); + expect(tileGrid.getResolution(13)).to.roughlyEqual(19.11, 1e-2); + expect(tileGrid.getResolution(14)).to.roughlyEqual(9.55, 1e-2); + expect(tileGrid.getResolution(15)).to.roughlyEqual(4.78, 1e-2); + expect(tileGrid.getResolution(16)).to.roughlyEqual(2.39, 1e-2); + expect(tileGrid.getResolution(17)).to.roughlyEqual(1.19, 1e-2); + expect(tileGrid.getResolution(18)).to.roughlyEqual(0.60, 1e-2); + expect(tileGrid.getResolution(19)).to.roughlyEqual(0.30, 1e-2); + }); + + }); + + describe('#getTileCoordFromCoordAndZ()', function() { describe('Y North, X East', function() { it('returns the expected TileCoord', function() { From a116878a57ac1d24ce852e5bdce81efe10d969fd Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 28 Apr 2015 23:14:08 +0200 Subject: [PATCH 2/5] Allow extents to restrict tile ranges requested from the server The addition of full extent tile ranges also allows us to simplify wrapX handling for tile layers. By limiting wrapX to true and false as possible values, we can remove a lot of guessing logic. --- examples/wms-custom-tilegrid-512x256.js | 3 +- examples/wms-tiled-wrap-180.js | 3 +- externs/olx.js | 92 +++++++--- src/ol/attribution.js | 4 +- src/ol/source/source.js | 4 +- src/ol/source/tileimagesource.js | 3 +- src/ol/source/tilesource.js | 24 ++- src/ol/source/tilewmssource.js | 2 +- src/ol/source/wmtssource.js | 42 ++--- src/ol/tilecoord.js | 41 +++-- src/ol/tilegrid/tilegrid.js | 153 ++++++++++------ src/ol/tilegrid/wmtstilegrid.js | 27 +-- test/spec/ol/source/tilesource.test.js | 29 +-- test/spec/ol/source/xyzsource.test.js | 21 ++- test/spec/ol/tilecoord.test.js | 72 ++++++++ test/spec/ol/tilegrid/tilegrid.test.js | 198 ++++++++++++++++++++- test/spec/ol/tilegrid/wmtstilegrid.test.js | 16 +- 17 files changed, 535 insertions(+), 199 deletions(-) diff --git a/examples/wms-custom-tilegrid-512x256.js b/examples/wms-custom-tilegrid-512x256.js index 8a6226d030..5c7d86644a 100644 --- a/examples/wms-custom-tilegrid-512x256.js +++ b/examples/wms-custom-tilegrid-512x256.js @@ -15,7 +15,7 @@ for (var i = 0, ii = resolutions.length; i < ii; ++i) { resolutions[i] = startResolution / Math.pow(2, i); } var tileGrid = new ol.tilegrid.TileGrid({ - origin: ol.extent.getBottomLeft(projExtent), + extent: [-13884991, 2870341, -7455066, 6338219], resolutions: resolutions, tileSize: [512, 256] }); @@ -25,7 +25,6 @@ var layers = [ source: new ol.source.MapQuest({layer: 'sat'}) }), new ol.layer.Tile({ - extent: [-13884991, 2870341, -7455066, 6338219], source: new ol.source.TileWMS({ url: 'http://demo.boundlessgeo.com/geoserver/wms', params: {'LAYERS': 'topp:states', 'TILED': true}, diff --git a/examples/wms-tiled-wrap-180.js b/examples/wms-tiled-wrap-180.js index fdf864b7e8..bf606e013f 100644 --- a/examples/wms-tiled-wrap-180.js +++ b/examples/wms-tiled-wrap-180.js @@ -13,8 +13,7 @@ var layers = [ source: new ol.source.TileWMS({ url: 'http://demo.boundlessgeo.com/geoserver/ne/wms', params: {'LAYERS': 'ne:ne_10m_admin_0_countries', 'TILED': true}, - serverType: 'geoserver', - wrapX: true + serverType: 'geoserver' }) }) ]; diff --git a/externs/olx.js b/externs/olx.js index f258393c28..ed472c0581 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4942,11 +4942,10 @@ olx.source.TileWMSOptions.prototype.urls; /** - * Whether to wrap the world horizontally. The default, `undefined`, is to - * request out-of-bounds tiles from the server. This works well in e.g. - * GeoServer. When set to `false`, only one world will be rendered. When set to - * `true`, tiles will be requested for one world only, but they will be wrapped - * horizontally to render multiple worlds. + * Whether to wrap the world horizontally. When set to `false`, only one world + * will be rendered. When `true`, tiles will be requested for one world only, + * but they will be wrapped horizontally to render multiple worlds. The default + * is `true`. * @type {boolean|undefined} * @api */ @@ -6014,18 +6013,30 @@ olx.tilegrid; /** - * @typedef {{minZoom: (number|undefined), + * @typedef {{extent: (ol.Extent|undefined), + * minZoom: (number|undefined), * origin: (ol.Coordinate|undefined), * origins: (Array.|undefined), * resolutions: !Array., + * sizes: (Array.|undefined), * tileSize: (number|ol.Size|undefined), - * tileSizes: (Array.|undefined), - * widths: (Array.|undefined)}} + * tileSizes: (Array.|undefined)}} * @api */ olx.tilegrid.TileGridOptions; +/** + * Extent for the tile grid. No tiles outside this extent will be requested by + * {@link ol.source.Tile} sources. When no `origin` or `origins` are + * configured, the `origin` will be set to the bottom-left corner of the extent. + * When no `sizes` are configured, they will be calculated from the extent. + * @type {ol.Extent|undefined} + * @api + */ +olx.tilegrid.TileGridOptions.prototype.extent; + + /** * Minimum zoom. Default is 0. * @type {number|undefined} @@ -6035,7 +6046,7 @@ olx.tilegrid.TileGridOptions.prototype.minZoom; /** - * Origin. Default is null. + * Origin, i.e. the bottom-left corner of the grid. Default is null. * @type {ol.Coordinate|undefined} * @api stable */ @@ -6043,8 +6054,9 @@ olx.tilegrid.TileGridOptions.prototype.origin; /** - * Origins. If given, the array length should match the length of the - * `resolutions` array, i.e. each resolution can have a different origin. + * Origins, i.e. the bottom-left corners of the grid for each zoom level. If + * given, the array length should match the length of the `resolutions` array, + * i.e. each resolution can have a different origin. * @type {Array.|undefined} * @api stable */ @@ -6061,6 +6073,17 @@ olx.tilegrid.TileGridOptions.prototype.origins; olx.tilegrid.TileGridOptions.prototype.resolutions; +/** + * Number of tile rows and columns of the grid for each zoom level. This setting + * is only needed for tile coordinate transforms that need to work with origins + * other than the bottom-left corner of the grid. No tiles outside this range + * will be requested by sources. If an `extent` is also configured, it takes + * precedence. + * @type {Array.|undefined} + */ +olx.tilegrid.TileGridOptions.prototype.sizes; + + /** * Tile size. Default is `[256, 256]`. * @type {number|ol.Size|undefined} @@ -6079,32 +6102,32 @@ olx.tilegrid.TileGridOptions.prototype.tileSizes; /** - * Number of tile columns that cover the grid's extent for each zoom level. Only - * required when used with a source that has `wrapX` set to `true`, and only - * when the grid's origin differs from the one of the projection's extent. The - * array length has to match the length of the `resolutions` array, i.e. each - * resolution will have a matching entry here. - * @type {Array.|undefined} - * @api - */ -olx.tilegrid.TileGridOptions.prototype.widths; - - -/** - * @typedef {{origin: (ol.Coordinate|undefined), + * @typedef {{extent: (ol.Extent|undefined), + * origin: (ol.Coordinate|undefined), * origins: (Array.|undefined), * resolutions: !Array., * matrixIds: !Array., + * sizes: (Array.|undefined), * tileSize: (number|ol.Size|undefined), - * tileSizes: (Array.|undefined), - * widths: (Array.|undefined)}} + * tileSizes: (Array.|undefined)}} * @api */ olx.tilegrid.WMTSOptions; /** - * Origin. + * Extent for the tile grid. No tiles outside this extent will be requested by + * {@link ol.source.WMTS} sources. When no `origin` or `origins` are + * configured, the `origin` will be calculated from the extent. + * When no `sizes` are configured, they will be calculated from the extent. + * @type {ol.Extent|undefined} + * @api + */ +olx.tilegrid.WMTSOptions.prototype.extent; + + +/** + * Origin, i.e. the top-left corner of the grid. * @type {ol.Coordinate|undefined} * @api */ @@ -6112,7 +6135,8 @@ olx.tilegrid.WMTSOptions.prototype.origin; /** - * Origins. The length of this array needs to match the length of the + * Origins, i.e. the top-left corners of the grid for each zoom level. The + * length of this array needs to match the length of the * `resolutions` array. * @type {Array.|undefined} * @api @@ -6139,6 +6163,18 @@ olx.tilegrid.WMTSOptions.prototype.resolutions; olx.tilegrid.WMTSOptions.prototype.matrixIds; +/** + * Number of tile rows and columns of the grid for each zoom level. The values + * here are the `TileMatrixWidth` and `TileMatrixHeight` advertised in the + * GetCapabilities response of the WMTS, and define the grid's extent together + * with the `origin`. An `extent` can be configured in addition, and will + * further limit the extent for which tile requests are made by sources. + * @type {Array.|undefined} + * @api + */ +olx.tilegrid.WMTSOptions.prototype.sizes; + + /** * Tile size. * @type {number|ol.Size|undefined} diff --git a/src/ol/attribution.js b/src/ol/attribution.js index 4963fa08d6..9f069828c6 100644 --- a/src/ol/attribution.js +++ b/src/ol/attribution.js @@ -78,8 +78,8 @@ ol.Attribution.prototype.intersectsAnyTileRange = if (testTileRange.intersects(tileRange)) { return true; } - var extentTileRange = tileGrid.getTileRange( - parseInt(zKey, 10), projection); + var extentTileRange = tileGrid.getTileRangeForExtentAndZ( + projection.getExtent(), parseInt(zKey, 10)); var width = extentTileRange.getWidth(); if (tileRange.minX < extentTileRange.minX || tileRange.maxX > extentTileRange.maxX) { diff --git a/src/ol/source/source.js b/src/ol/source/source.js index e215cb40e9..a6aeab177c 100644 --- a/src/ol/source/source.js +++ b/src/ol/source/source.js @@ -75,9 +75,9 @@ ol.source.Source = function(options) { /** * @private - * @type {boolean|undefined} + * @type {boolean} */ - this.wrapX_ = options.wrapX; + this.wrapX_ = goog.isDef(options.wrapX) ? options.wrapX : false; }; goog.inherits(ol.source.Source, ol.Object); diff --git a/src/ol/source/tileimagesource.js b/src/ol/source/tileimagesource.js index 0b549a1f9c..1701f19e86 100644 --- a/src/ol/source/tileimagesource.js +++ b/src/ol/source/tileimagesource.js @@ -93,7 +93,8 @@ ol.source.TileImage.prototype.getTile = } else { goog.asserts.assert(projection, 'argument projection is truthy'); var tileCoord = [z, x, y]; - var urlTileCoord = this.getWrapXTileCoord(tileCoord, projection); + var urlTileCoord = this.getTileCoordForTileUrlFunction( + tileCoord, projection); var tileUrl = goog.isNull(urlTileCoord) ? undefined : this.tileUrlFunction(urlTileCoord, pixelRatio, projection); var tile = new this.tileClass( diff --git a/src/ol/source/tilesource.js b/src/ol/source/tilesource.js index e81a4261b3..a0179d5ad6 100644 --- a/src/ol/source/tilesource.js +++ b/src/ol/source/tilesource.js @@ -2,6 +2,7 @@ goog.provide('ol.source.Tile'); goog.provide('ol.source.TileEvent'); goog.provide('ol.source.TileOptions'); +goog.require('goog.asserts'); goog.require('goog.events.Event'); goog.require('ol.Attribution'); goog.require('ol.Extent'); @@ -216,28 +217,23 @@ ol.source.Tile.prototype.getTilePixelSize = /** - * Handles x-axis wrapping. When `wrapX` is `undefined` or the projection is not - * a global projection, `tileCoord` will be returned unaltered. When `wrapX` is - * `true`, the tile coordinate will be wrapped horizontally. - * When `wrapX` is `false`, `null` will be returned for tiles that are - * outside the projection extent. + * Handles x-axis wrapping and returns a tile coordinate when it is within + * the resolution and extent range. * @param {ol.TileCoord} tileCoord Tile coordinate. * @param {ol.proj.Projection=} opt_projection Projection. - * @return {ol.TileCoord} Tile coordinate. + * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or + * null if no tile URL should be created for the passed `tileCoord`. */ -ol.source.Tile.prototype.getWrapXTileCoord = +ol.source.Tile.prototype.getTileCoordForTileUrlFunction = function(tileCoord, opt_projection) { var projection = goog.isDef(opt_projection) ? opt_projection : this.getProjection(); var tileGrid = this.getTileGridForProjection(projection); - var wrapX = this.getWrapX(); - if (goog.isDef(wrapX) && tileGrid.isGlobal(tileCoord[0], projection)) { - return wrapX ? - ol.tilecoord.wrapX(tileCoord, tileGrid, projection) : - ol.tilecoord.clipX(tileCoord, tileGrid, projection); - } else { - return tileCoord; + goog.asserts.assert(!goog.isNull(tileGrid), 'tile grid needed'); + if (this.getWrapX()) { + tileCoord = ol.tilecoord.wrapX(tileCoord, tileGrid, projection); } + return ol.tilecoord.restrictByExtentAndZ(tileCoord, tileGrid); }; diff --git a/src/ol/source/tilewmssource.js b/src/ol/source/tilewmssource.js index d655a2e179..b2fa897e0a 100644 --- a/src/ol/source/tilewmssource.js +++ b/src/ol/source/tilewmssource.js @@ -49,7 +49,7 @@ ol.source.TileWMS = function(opt_options) { tileGrid: options.tileGrid, tileLoadFunction: options.tileLoadFunction, tileUrlFunction: goog.bind(this.tileUrlFunction_, this), - wrapX: options.wrapX + wrapX: goog.isDef(options.wrapX) ? options.wrapX : true }); var urls = options.urls; diff --git a/src/ol/source/wmtssource.js b/src/ol/source/wmtssource.js index 53eb1f3bcb..7eb25cdb2b 100644 --- a/src/ol/source/wmtssource.js +++ b/src/ol/source/wmtssource.js @@ -11,7 +11,6 @@ goog.require('ol.TileUrlFunctionType'); goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.source.TileImage'); -goog.require('ol.tilecoord'); goog.require('ol.tilegrid.WMTS'); @@ -181,31 +180,8 @@ ol.source.WMTS = function(options) { goog.array.map(this.urls_, createFromWMTSTemplate)) : ol.TileUrlFunction.nullTileUrlFunction; - var tmpExtent = ol.extent.createEmpty(); tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform( - /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.proj.Projection} projection Projection. - * @param {ol.TileCoord=} opt_tileCoord Tile coordinate. - * @return {ol.TileCoord} Tile coordinate. - */ - function(tileCoord, projection, opt_tileCoord) { - goog.asserts.assert(!goog.isNull(tileGrid), - 'tileGrid must not be null'); - if (tileGrid.getResolutions().length <= tileCoord[0]) { - return null; - } - var x = tileCoord[1]; - var y = -tileCoord[2] - 1; - var tileExtent = tileGrid.getTileCoordExtent(tileCoord, tmpExtent); - var extent = projection.getExtent(); - - if (!ol.extent.intersects(tileExtent, extent) || - ol.extent.touches(tileExtent, extent)) { - return null; - } - return ol.tilecoord.createOrUpdate(tileCoord[0], x, y, opt_tileCoord); - }, + ol.tilegrid.createOriginTopLeftTileCoordTransform(tileGrid), tileUrlFunction); goog.base(this, { @@ -450,9 +426,6 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) { goog.asserts.assert(!goog.isNull(matrixSetObj), 'found matrixSet in Contents/TileMatrixSet'); - var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet( - matrixSetObj); - var projection; if (goog.isDef(config['projection'])) { projection = ol.proj.get(config['projection']); @@ -461,6 +434,19 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) { /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')); } + var projectionExtent = projection.getExtent(); + var extent; + if (!goog.isNull(projectionExtent)) { + var projectionExtentWgs84 = ol.proj.transformExtent( + projectionExtent, projection, 'EPSG:4326'); + if (ol.extent.containsExtent(projectionExtentWgs84, wgs84BoundingBox)) { + extent = ol.proj.transformExtent( + wgs84BoundingBox, 'EPSG:4326', projection); + } + } + var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet( + matrixSetObj, extent); + /** @type {!Array.} */ var urls = []; var requestEncoding = config['requestEncoding']; diff --git a/src/ol/tilecoord.js b/src/ol/tilecoord.js index 09f7da088f..ebc1044add 100644 --- a/src/ol/tilecoord.js +++ b/src/ol/tilecoord.js @@ -3,7 +3,7 @@ goog.provide('ol.tilecoord'); goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.math'); +goog.require('ol.extent'); /** @@ -149,25 +149,42 @@ ol.tilecoord.toString = function(tileCoord) { */ ol.tilecoord.wrapX = function(tileCoord, tileGrid, projection) { var z = tileCoord[0]; - var x = tileCoord[1]; - var tileRange = tileGrid.getTileRange(z, projection); - if (x < tileRange.minX || x > tileRange.maxX) { - x = goog.math.modulo(x, tileRange.getWidth()); - return [z, x, tileCoord[2]]; + var center = tileGrid.getTileCoordCenter(tileCoord); + var projectionExtent = ol.tilegrid.extentFromProjection(projection); + if (!ol.extent.containsCoordinate(projectionExtent, center)) { + var worldWidth = ol.extent.getWidth(projectionExtent); + var worldsAway = Math.ceil((projectionExtent[0] - center[0]) / worldWidth); + center[0] += worldWidth * worldsAway; + return tileGrid.getTileCoordForCoordAndZ(center, z); + } else { + return tileCoord; } - return tileCoord; }; /** * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. - * @param {ol.proj.Projection} projection Projection. + * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid. * @return {ol.TileCoord} Tile coordinate. */ -ol.tilecoord.clipX = function(tileCoord, tileGrid, projection) { +ol.tilecoord.restrictByExtentAndZ = function(tileCoord, tileGrid) { var z = tileCoord[0]; var x = tileCoord[1]; - var tileRange = tileGrid.getTileRange(z, projection); - return (x < tileRange.minX || x > tileRange.maxX) ? null : tileCoord; + var y = tileCoord[2]; + + if (tileGrid.getMinZoom() > z || z > tileGrid.getMaxZoom()) { + return null; + } + var extent = tileGrid.getExtent(); + var tileRange; + if (goog.isNull(extent)) { + tileRange = tileGrid.getFullTileRange(z); + } else { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + } + if (goog.isNull(tileRange)) { + return tileCoord; + } else { + return tileRange.containsXY(x, y) ? tileCoord : null; + } }; diff --git a/src/ol/tilegrid/tilegrid.js b/src/ol/tilegrid/tilegrid.js index 9c08e6ee9c..507fbbf614 100644 --- a/src/ol/tilegrid/tilegrid.js +++ b/src/ol/tilegrid/tilegrid.js @@ -4,6 +4,7 @@ goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.functions'); goog.require('goog.math'); +goog.require('goog.object'); goog.require('ol'); goog.require('ol.Coordinate'); goog.require('ol.TileCoord'); @@ -69,6 +70,10 @@ ol.tilegrid.TileGrid = function(options) { goog.asserts.assert(this.origins_.length == this.resolutions_.length, 'number of origins and resolutions must be equal'); } + if (goog.isNull(this.origins_) && goog.isNull(this.origin_) && + goog.isDef(options.extent)) { + this.origin_ = ol.extent.getBottomLeft(options.extent); + } goog.asserts.assert( (goog.isNull(this.origin_) && !goog.isNull(this.origins_)) || (!goog.isNull(this.origin_) && goog.isNull(this.origins_)), @@ -97,6 +102,51 @@ ol.tilegrid.TileGrid = function(options) { (!goog.isNull(this.tileSize_) && goog.isNull(this.tileSizes_)), 'either tileSize or tileSizes must be configured, never both'); + var extent = options.extent; + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = goog.isDef(extent) ? extent : null; + + /** + * @private + * @type {Array.} + */ + this.fullTileRanges_ = null; + + if (goog.isDef(options.sizes)) { + goog.asserts.assert(options.sizes.length == this.resolutions_.length, + 'number of sizes and resolutions must be equal'); + this.fullTileRanges_ = goog.array.map(options.sizes, function(size, z) { + goog.asserts.assert(size[0] > 0, 'width must be > 0'); + goog.asserts.assert(size[1] !== 0, 'height must not be 0'); + var tileRange = new ol.TileRange(0, size[0] - 1, 0, size[1] - 1); + if (tileRange.maxY < tileRange.minY) { + tileRange.minY = size[1]; + tileRange.maxY = -1; + } + if (this.minZoom <= z && z <= this.maxZoom && goog.isDef(extent)) { + goog.asserts.assert(tileRange.containsTileRange( + this.getTileRangeForExtentAndZ(extent, z)), + 'extent tile range must not exceed tilegrid width and height'); + } + return tileRange; + }, this); + } else if (goog.isDef(extent)) { + var extentWidth = ol.extent.getWidth(extent); + var extentHeight = ol.extent.getHeight(extent); + var fullTileRanges = new Array(this.resolutions_.length); + var tileSize; + for (var z = 0, zz = fullTileRanges.length; z < zz; ++z) { + tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); + fullTileRanges[z] = new ol.TileRange( + 0, Math.ceil(extentWidth / tileSize[0] / this.resolutions_[z]) - 1, + 0, Math.ceil(extentHeight / tileSize[1] / this.resolutions_[z]) - 1); + } + this.fullTileRanges_ = fullTileRanges; + } /** * @private @@ -104,17 +154,6 @@ ol.tilegrid.TileGrid = function(options) { */ this.tmpSize_ = [0, 0]; - /** - * @private - * @type {Array.} - */ - this.widths_ = null; - if (goog.isDef(options.widths)) { - this.widths_ = options.widths; - goog.asserts.assert(this.widths_.length == this.resolutions_.length, - 'number of widths and resolutions must be equal'); - } - }; @@ -161,6 +200,15 @@ ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange = }; +/** + * Get the extent for this tile grid, if it was configured. + * @return {ol.Extent} Extent. + */ +ol.tilegrid.TileGrid.prototype.getExtent = function() { + return this.extent_; +}; + + /** * Get the maximum zoom level for the grid. * @return {number} Max zoom. @@ -412,25 +460,6 @@ ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) { }; -/** - * @param {number} z Zoom level. - * @param {ol.proj.Projection} projection Projection. - * @param {ol.TileRange=} opt_tileRange Tile range. - * @return {ol.TileRange} Tile range. - */ -ol.tilegrid.TileGrid.prototype.getTileRange = - function(z, projection, opt_tileRange) { - var projectionExtentTileRange = this.getTileRangeForExtentAndZ( - ol.tilegrid.extentFromProjection(projection), z); - var width = this.getWidth(z); - if (!goog.isDef(width)) { - width = projectionExtentTileRange.getWidth(); - } - return ol.TileRange.createOrUpdate( - 0, width - 1, 0, projectionExtentTileRange.getHeight(), opt_tileRange); -}; - - /** * Get the tile size for a zoom level. The type of the return value matches the * `tileSize` or `tileSizes` that the tile grid was configured with. To always @@ -455,15 +484,16 @@ ol.tilegrid.TileGrid.prototype.getTileSize = function(z) { /** * @param {number} z Zoom level. - * @return {number|undefined} Width for the specified zoom level or `undefined` - * if unknown. + * @return {ol.TileRange} Extent tile range for the specified zoom level. */ -ol.tilegrid.TileGrid.prototype.getWidth = function(z) { - if (!goog.isNull(this.widths_)) { +ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) { + if (goog.isNull(this.fullTileRanges_)) { + return null; + } else { goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, 'z is not in allowed range (%s <= %s <= %s', this.minZoom, z, this.maxZoom); - return this.widths_[z]; + return this.fullTileRanges_[z]; } }; @@ -478,26 +508,6 @@ ol.tilegrid.TileGrid.prototype.getZForResolution = function(resolution) { }; -/** - * @param {number} z Zoom level. - * @param {ol.proj.Projection} projection Projection. - * @return {boolean} Whether the tile grid is defined for the whole globe when - * used with the provided `projection` at zoom level `z`. - */ -ol.tilegrid.TileGrid.prototype.isGlobal = function(z, projection) { - var width = this.getWidth(z); - if (goog.isDef(width)) { - var projTileGrid = ol.tilegrid.getForProjection(projection); - var projExtent = projection.getExtent(); - return ol.size.toSize(this.getTileSize(z), this.tmpSize_)[0] * width == - projTileGrid.getTileSize(z) * - projTileGrid.getTileRangeForExtentAndZ(projExtent, z).getWidth(); - } else { - return projection.isGlobal(); - } -}; - - /** * @param {ol.proj.Projection} projection Projection. * @return {ol.tilegrid.TileGrid} Default tile grid for the passed projection. @@ -611,3 +621,36 @@ ol.tilegrid.extentFromProjection = function(projection) { } return extent; }; + + +/** + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {function(ol.TileCoord, ol.proj.Projection, ol.TileCoord=): + * ol.TileCoord} Tile coordinate transform. + */ +ol.tilegrid.createOriginTopLeftTileCoordTransform = function(tileGrid) { + goog.asserts.assert(!goog.isNull(tileGrid), 'tileGrid required'); + return ( + /** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.proj.Projection} projection Projection. + * @param {ol.TileCoord=} opt_tileCoord Destination tile coordinate. + * @return {ol.TileCoord} Tile coordinate. + */ + function(tileCoord, projection, opt_tileCoord) { + if (goog.isNull(tileCoord)) { + return null; + } + var z = tileCoord[0]; + var fullTileRange = tileGrid.getFullTileRange(z); + var height; + if (goog.isNull(fullTileRange) || fullTileRange.minY < 0) { + height = 0; + } else { + height = fullTileRange.getHeight(); + } + return ol.tilecoord.createOrUpdate( + z, tileCoord[1], height - tileCoord[2] - 1, opt_tileCoord); + } + ); +}; diff --git a/src/ol/tilegrid/wmtstilegrid.js b/src/ol/tilegrid/wmtstilegrid.js index 6770358784..74838aa2e9 100644 --- a/src/ol/tilegrid/wmtstilegrid.js +++ b/src/ol/tilegrid/wmtstilegrid.js @@ -32,12 +32,13 @@ ol.tilegrid.WMTS = function(options) { // FIXME: should the matrixIds become optionnal? goog.base(this, { + extent: options.extent, origin: options.origin, origins: options.origins, resolutions: options.resolutions, tileSize: options.tileSize, tileSizes: options.tileSizes, - widths: options.widths + sizes: options.sizes }); }; @@ -69,11 +70,13 @@ ol.tilegrid.WMTS.prototype.getMatrixIds = function() { * Create a tile grid from a WMTS capabilities matrix set. * @param {Object} matrixSet An object representing a matrixSet in the * capabilities document. + * @param {ol.Extent=} opt_extent An optional extent to restrict the tile + * ranges the server provides. * @return {ol.tilegrid.WMTS} WMTS tileGrid instance. * @api */ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = - function(matrixSet) { + function(matrixSet, opt_extent) { /** @type {!Array.} */ var resolutions = []; @@ -83,8 +86,8 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = var origins = []; /** @type {!Array.} */ var tileSizes = []; - /** @type {!Array.} */ - var widths = []; + /** @type {!Array.} */ + var sizes = []; var supportedCRSPropName = 'SupportedCRS'; var matrixIdsPropName = 'TileMatrix'; @@ -108,26 +111,30 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = goog.array.forEach(matrixSet[matrixIdsPropName], function(elt, index, array) { matrixIds.push(elt[identifierPropName]); + var resolution = elt[scaleDenominatorPropName] * 0.28E-3 / + metersPerUnit; + var tileWidth = elt[tileWidthPropName]; + var tileHeight = elt[tileHeightPropName]; + var matrixHeight = elt['MatrixHeight']; if (switchOriginXY) { origins.push([elt[topLeftCornerPropName][1], elt[topLeftCornerPropName][0]]); } else { origins.push(elt[topLeftCornerPropName]); } - resolutions.push(elt[scaleDenominatorPropName] * 0.28E-3 / - metersPerUnit); - var tileWidth = elt[tileWidthPropName]; - var tileHeight = elt[tileHeightPropName]; + resolutions.push(resolution); tileSizes.push(tileWidth == tileHeight ? tileWidth : [tileWidth, tileHeight]); - widths.push(elt['MatrixWidth']); + // top-left origin, so height is negative + sizes.push([elt['MatrixWidth'], -matrixHeight]); }); return new ol.tilegrid.WMTS({ + extent: opt_extent, origins: origins, resolutions: resolutions, matrixIds: matrixIds, tileSizes: tileSizes, - widths: widths + sizes: sizes }); }; diff --git a/test/spec/ol/source/tilesource.test.js b/test/spec/ol/source/tilesource.test.js index ddd2b610b1..b6c2a99030 100644 --- a/test/spec/ol/source/tilesource.test.js +++ b/test/spec/ol/source/tilesource.test.js @@ -115,22 +115,7 @@ describe('ol.source.Tile', function() { }); - describe('#getWrapXTileCoord()', function() { - - it('returns the expected tile coordinate - {wrapX: undefined}', function() { - var tileSource = new ol.source.Tile({ - projection: 'EPSG:3857' - }); - - var tileCoord = tileSource.getWrapXTileCoord([6, -31, 22]); - expect(tileCoord).to.eql([6, -31, 22]); - - tileCoord = tileSource.getWrapXTileCoord([6, 33, 22]); - expect(tileCoord).to.eql([6, 33, 22]); - - tileCoord = tileSource.getWrapXTileCoord([6, 97, 22]); - expect(tileCoord).to.eql([6, 97, 22]); - }); + describe('#getTileCoordForTileUrlFunction()', function() { it('returns the expected tile coordinate - {wrapX: true}', function() { var tileSource = new ol.source.Tile({ @@ -138,13 +123,13 @@ describe('ol.source.Tile', function() { wrapX: true }); - var tileCoord = tileSource.getWrapXTileCoord([6, -31, 22]); + var tileCoord = tileSource.getTileCoordForTileUrlFunction([6, -31, 22]); expect(tileCoord).to.eql([6, 33, 22]); - tileCoord = tileSource.getWrapXTileCoord([6, 33, 22]); + tileCoord = tileSource.getTileCoordForTileUrlFunction([6, 33, 22]); expect(tileCoord).to.eql([6, 33, 22]); - tileCoord = tileSource.getWrapXTileCoord([6, 97, 22]); + tileCoord = tileSource.getTileCoordForTileUrlFunction([6, 97, 22]); expect(tileCoord).to.eql([6, 33, 22]); }); @@ -154,13 +139,13 @@ describe('ol.source.Tile', function() { wrapX: false }); - var tileCoord = tileSource.getWrapXTileCoord([6, -31, 22]); + var tileCoord = tileSource.getTileCoordForTileUrlFunction([6, -31, 22]); expect(tileCoord).to.eql(null); - tileCoord = tileSource.getWrapXTileCoord([6, 33, 22]); + tileCoord = tileSource.getTileCoordForTileUrlFunction([6, 33, 22]); expect(tileCoord).to.eql([6, 33, 22]); - tileCoord = tileSource.getWrapXTileCoord([6, 97, 22]); + tileCoord = tileSource.getTileCoordForTileUrlFunction([6, 97, 22]); expect(tileCoord).to.eql(null); }); }); diff --git a/test/spec/ol/source/xyzsource.test.js b/test/spec/ol/source/xyzsource.test.js index e5d7cab36c..866f3c20b5 100644 --- a/test/spec/ol/source/xyzsource.test.js +++ b/test/spec/ol/source/xyzsource.test.js @@ -66,15 +66,18 @@ describe('ol.source.XYZ', function() { it('returns the expected URL', function() { var projection = xyzTileSource.getProjection(); var tileUrl = xyzTileSource.tileUrlFunction( - xyzTileSource.getWrapXTileCoord([6, -31, -23], projection)); + xyzTileSource.getTileCoordForTileUrlFunction( + [6, -31, 41], projection)); expect(tileUrl).to.eql('6/33/22'); tileUrl = xyzTileSource.tileUrlFunction( - xyzTileSource.getWrapXTileCoord([6, 33, -23], projection)); + xyzTileSource.getTileCoordForTileUrlFunction( + [6, 33, 41], projection)); expect(tileUrl).to.eql('6/33/22'); tileUrl = xyzTileSource.tileUrlFunction( - xyzTileSource.getWrapXTileCoord([6, 97, -23], projection)); + xyzTileSource.getTileCoordForTileUrlFunction( + [6, 97, 41], projection)); expect(tileUrl).to.eql('6/33/22'); }); @@ -83,14 +86,20 @@ describe('ol.source.XYZ', function() { describe('crop y', function() { it('returns the expected URL', function() { + var projection = xyzTileSource.getProjection(); var tileUrl = xyzTileSource.tileUrlFunction( - [6, 33, -87]); + xyzTileSource.getTileCoordForTileUrlFunction( + [6, 33, 150], projection)); expect(tileUrl).to.be(undefined); - tileUrl = xyzTileSource.tileUrlFunction([6, 33, -23]); + tileUrl = xyzTileSource.tileUrlFunction( + xyzTileSource.getTileCoordForTileUrlFunction( + [6, 33, 41], projection)); expect(tileUrl).to.eql('6/33/22'); - tileUrl = xyzTileSource.tileUrlFunction([6, 33, 41]); + tileUrl = xyzTileSource.tileUrlFunction( + xyzTileSource.getTileCoordForTileUrlFunction( + [6, 33, -23], projection)); expect(tileUrl).to.be(undefined); }); diff --git a/test/spec/ol/tilecoord.test.js b/test/spec/ol/tilecoord.test.js index d46c3483c8..f2bd42411d 100644 --- a/test/spec/ol/tilecoord.test.js +++ b/test/spec/ol/tilecoord.test.js @@ -46,7 +46,79 @@ describe('ol.TileCoord', function() { ol.tilecoord.hash(tileCoord2)); }); }); + + describe('restrictByExtentAndZ', function() { + + it('restricts by z', function() { + var tileGrid = new ol.tilegrid.TileGrid({ + extent: [10, 20, 30, 40], + tileSize: 10, + resolutions: [2, 1], + minZoom: 1 + }); + expect(ol.tilecoord.restrictByExtentAndZ([0, 0, 0], tileGrid)) + .to.equal(null); + expect(ol.tilecoord.restrictByExtentAndZ([1, 0, 0], tileGrid)) + .to.eql([1, 0, 0]); + expect(ol.tilecoord.restrictByExtentAndZ([2, 0, 0], tileGrid)) + .to.equal(null); + }); + + it('restricts by extent when extent defines tile ranges', function() { + var tileGrid = new ol.tilegrid.TileGrid({ + extent: [10, 20, 30, 40], + sizes: [[3, 3]], + tileSize: 10, + resolutions: [1] + }); + expect(ol.tilecoord.restrictByExtentAndZ([0, 1, 1], tileGrid)) + .to.eql([0, 1, 1]); + expect(ol.tilecoord.restrictByExtentAndZ([0, 2, 0], tileGrid)) + .to.equal(null); + expect(ol.tilecoord.restrictByExtentAndZ([0, 0, 2], tileGrid)) + .to.equal(null); + }); + + it('restricts by extent when sizes define tile ranges', function() { + var tileGrid = new ol.tilegrid.TileGrid({ + origin: [10, 20], + sizes: [[3, 3]], + tileSize: 10, + resolutions: [1] + }); + expect(ol.tilecoord.restrictByExtentAndZ([0, 0, 0], tileGrid)) + .to.eql([0, 0, 0]); + expect(ol.tilecoord.restrictByExtentAndZ([0, -1, 0], tileGrid)) + .to.equal(null); + expect(ol.tilecoord.restrictByExtentAndZ([0, 0, -1], tileGrid)) + .to.equal(null); + expect(ol.tilecoord.restrictByExtentAndZ([0, 2, 2], tileGrid)) + .to.eql([0, 2, 2]); + expect(ol.tilecoord.restrictByExtentAndZ([0, 3, 0], tileGrid)) + .to.equal(null); + expect(ol.tilecoord.restrictByExtentAndZ([0, 0, 3], tileGrid)) + .to.equal(null); + }); + + it('does not restrict by extent with no extent or sizes', function() { + var tileGrid = new ol.tilegrid.TileGrid({ + origin: [10, 20], + tileSize: 10, + resolutions: [1] + }); + expect(ol.tilecoord.restrictByExtentAndZ([0, Infinity, 0], tileGrid)) + .to.eql([0, Infinity, 0]); + expect(ol.tilecoord.restrictByExtentAndZ([0, 0, Infinity], tileGrid)) + .to.eql([0, 0, Infinity]); + expect(ol.tilecoord.restrictByExtentAndZ([0, -Infinity, 0], tileGrid)) + .to.eql([0, -Infinity, 0]); + expect(ol.tilecoord.restrictByExtentAndZ([0, 0, Infinity], tileGrid)) + .to.eql([0, 0, Infinity]); + }); + }); + }); goog.require('ol.TileCoord'); goog.require('ol.tilecoord'); +goog.require('ol.tilegrid.TileGrid'); diff --git a/test/spec/ol/tilegrid/tilegrid.test.js b/test/spec/ol/tilegrid/tilegrid.test.js index 14f8b14d83..e476a58726 100644 --- a/test/spec/ol/tilegrid/tilegrid.test.js +++ b/test/spec/ol/tilegrid/tilegrid.test.js @@ -150,6 +150,129 @@ describe('ol.tilegrid.TileGrid', function() { }); }); + describe('create with extent exceeding tile ranges', function() { + it('throws an exception', function() { + expect(function() { + return new ol.tilegrid.TileGrid({ + extent: [10, 20, 30, 40], + sizes: [[1, 1]], + tileSize: 10, + resolutions: [1] + }); + }).to.throwException(); + expect(function() { + return new ol.tilegrid.TileGrid({ + extent: [10, 20, 30, 40], + origin: [10, 40], // top-left origin + sizes: [[3, 3]], // would have to be [[3, -3]] for this to not throw + tileSize: 10, + resolutions: [1] + }); + }).to.throwException(); + }); + }); + + describe('create with origin', function() { + var tileGrid; + beforeEach(function() { + tileGrid = new ol.tilegrid.TileGrid({ + origin: [10, 20], + tileSize: 10, + resolutions: [1] + }); + }); + + it('returns the configured origin', function() { + expect(tileGrid.getOrigin()).to.eql([10, 20]); + }); + + it('returns null for an unknown extent', function() { + expect(tileGrid.getExtent()).to.equal(null); + }); + + it('returns null for an unknown full tile range', function() { + expect(tileGrid.getFullTileRange(0)).to.equal(null); + }); + }); + + describe('create with extent', function() { + var tileGrid; + beforeEach(function() { + tileGrid = new ol.tilegrid.TileGrid({ + extent: [10, 20, 30, 40], + tileSize: 10, + resolutions: [1] + }); + }); + + it('assumes bottom left corner of extent as origin', function() { + expect(tileGrid.getOrigin()).to.eql([10, 20]); + }); + + it('calculates full tile ranges from extent', function() { + var fullTileRange = tileGrid.getFullTileRange(0); + expect(fullTileRange.minX).to.equal(0); + expect(fullTileRange.maxX).to.equal(1); + expect(fullTileRange.minY).to.equal(0); + expect(fullTileRange.maxY).to.equal(1); + }); + }); + + describe('create with extent and sizes', function() { + var tileGrid; + beforeEach(function() { + tileGrid = new ol.tilegrid.TileGrid({ + extent: [10, 20, 30, 40], + sizes: [[3, 3]], + tileSize: 10, + resolutions: [1] + }); + }); + + it('returns the configured extent', function() { + expect(tileGrid.getExtent()).to.eql([10, 20, 30, 40]); + }); + + it('calculates full tile ranges from sizes', function() { + var fullTileRange = tileGrid.getFullTileRange(0); + expect(fullTileRange.minX).to.equal(0); + expect(fullTileRange.maxX).to.equal(2); + expect(fullTileRange.minY).to.equal(0); + expect(fullTileRange.maxY).to.equal(2); + }); + }); + + describe('create with top-left origin and sizes', function() { + var tileGrid; + beforeEach(function() { + tileGrid = new ol.tilegrid.TileGrid({ + origin: [10, 40], + sizes: [[3, -3]], + tileSize: 10, + resolutions: [1] + }); + }); + + it('calculates correct minX and maxX for negative heights', function() { + var fullTileRange = tileGrid.getFullTileRange(0); + expect(fullTileRange.minY).to.equal(-3); + expect(fullTileRange.maxY).to.equal(-1); + }); + }); + + describe('create with extent and origin', function() { + it('uses both origin and extent', function() { + var tileGrid = new ol.tilegrid.TileGrid({ + origin: [0, 0], + extent: [10, 20, 30, 40], + tileSize: 10, + resolutions: [1] + }); + expect(tileGrid.getOrigin()).to.eql([0, 0]); + expect(tileGrid.getExtent()).to.eql([10, 20, 30, 40]); + }); + }); + describe('createForExtent', function() { it('allows creation of tile grid from extent', function() { var extent = ol.extent.createOrUpdate(-100, -100, 100, 100); @@ -247,6 +370,78 @@ describe('ol.tilegrid.TileGrid', function() { }); + describe('createOriginTopLeftTileCoordTransform', function() { + + it('transforms y to -y-1 for top-left origins', function() { + var tileGrid = new ol.tilegrid.TileGrid({ + origin: [10, 40], + sizes: [[2, -2], [4, -4]], + resolutions: [1, 0.5], + tileSize: 10 + }); + var transformFn = + ol.tilegrid.createOriginTopLeftTileCoordTransform(tileGrid); + expect(transformFn([0, 0, -2])).to.eql([0, 0, 1]); + expect(transformFn([0, 0, -1])).to.eql([0, 0, 0]); + expect(transformFn([1, 0, -4])).to.eql([1, 0, 3]); + expect(transformFn([1, 0, -1])).to.eql([1, 0, 0]); + }); + + it('transforms y to -y-1 when origin corner is not specified', function() { + var tileGrid1 = new ol.tilegrid.TileGrid({ + origin: [10, 20], + resolutions: [1, 0.5], + tileSize: 10 + }); + var tileGrid = new ol.tilegrid.TileGrid({ + origin: [10, 40], + resolutions: [1, 0.5], + tileSize: 10 + }); + var transformFn1 = + ol.tilegrid.createOriginTopLeftTileCoordTransform(tileGrid); + var transformFn2 = + ol.tilegrid.createOriginTopLeftTileCoordTransform(tileGrid1); + expect(transformFn1([0, 0, -2])).to.eql([0, 0, 1]); + expect(transformFn2([0, 0, -2])).to.eql([0, 0, 1]); + expect(transformFn1([0, 0, -1])).to.eql([0, 0, 0]); + expect(transformFn2([0, 0, -1])).to.eql([0, 0, 0]); + expect(transformFn1([1, 0, -4])).to.eql([1, 0, 3]); + expect(transformFn2([1, 0, -4])).to.eql([1, 0, 3]); + expect(transformFn1([1, 0, -1])).to.eql([1, 0, 0]); + expect(transformFn2([1, 0, -1])).to.eql([1, 0, 0]); + }); + + it('transforms y to height-y-1 for bottom-left origins', function() { + var tileGrid1 = new ol.tilegrid.TileGrid({ + extent: [10, 20, 30, 40], + resolutions: [1, 0.5], + tileSize: 10 + }); + var tileGrid2 = new ol.tilegrid.TileGrid({ + origin: [10, 20], + sizes: [[2, 2], [4, 4]], + resolutions: [1, 0.5], + tileSize: 10 + }); + var transformFn1 = + ol.tilegrid.createOriginTopLeftTileCoordTransform(tileGrid1); + var transformFn2 = + ol.tilegrid.createOriginTopLeftTileCoordTransform(tileGrid2); + expect(tileGrid1.getFullTileRange(0).getHeight()).to.equal(2); + expect(transformFn1([0, 0, 0])).to.eql([0, 0, 1]); + expect(transformFn2([0, 0, 0])).to.eql([0, 0, 1]); + expect(transformFn1([0, 0, 1])).to.eql([0, 0, 0]); + expect(transformFn2([0, 0, 1])).to.eql([0, 0, 0]); + expect(tileGrid1.getFullTileRange(1).getHeight()).to.equal(4); + expect(transformFn1([1, 0, 0])).to.eql([1, 0, 3]); + expect(transformFn2([1, 0, 0])).to.eql([1, 0, 3]); + expect(transformFn1([1, 0, 3])).to.eql([1, 0, 0]); + expect(transformFn2([1, 0, 3])).to.eql([1, 0, 0]); + }); + + }); + describe('getForProjection', function() { it('gets the default tile grid for a projection', function() { @@ -839,7 +1034,7 @@ describe('ol.tilegrid.TileGrid', function() { }); }); - describe('getZForResolution (approcimate)', function() { + describe('getZForResolution (approximate)', function() { it('returns the expected z value', function() { var tileGrid = new ol.tilegrid.TileGrid({ resolutions: resolutions, @@ -874,4 +1069,5 @@ goog.require('ol.proj'); goog.require('ol.proj.EPSG3857'); goog.require('ol.proj.Projection'); goog.require('ol.proj.Units'); +goog.require('ol.TileRange'); goog.require('ol.tilegrid.TileGrid'); diff --git a/test/spec/ol/tilegrid/wmtstilegrid.test.js b/test/spec/ol/tilegrid/wmtstilegrid.test.js index 1e03436c23..966dacb59e 100644 --- a/test/spec/ol/tilegrid/wmtstilegrid.test.js +++ b/test/spec/ol/tilegrid/wmtstilegrid.test.js @@ -42,27 +42,17 @@ describe('ol.tilegrid.WMTS', function() { expect(tileGrid.origins_).to.be.an('array'); expect(tileGrid.origins_).to.have.length(20); expect(tileGrid.origins_).to.eql( - [[-20037508.3428, 20037508.3428], [-20037508.3428, 20037508.3428], - [-20037508.3428, 20037508.3428], [-20037508.3428, 20037508.3428], - [-20037508.3428, 20037508.3428], [-20037508.3428, 20037508.3428], - [-20037508.3428, 20037508.3428], [-20037508.3428, 20037508.3428], - [-20037508.3428, 20037508.3428], [-20037508.3428, 20037508.3428], - [-20037508.3428, 20037508.3428], [-20037508.3428, 20037508.3428], - [-20037508.3428, 20037508.3428], [-20037508.3428, 20037508.3428], - [-20037508.3428, 20037508.3428], [-20037508.3428, 20037508.3428], - [-20037508.3428, 20037508.3428], [-20037508.3428, 20037508.3428], - [-20037508.3428, 20037508.3428], [-20037508.3428, 20037508.3428] - ]); + goog.array.repeat([-20037508.3428, 20037508.3428], 20)); expect(tileGrid.tileSizes_).to.be.an('array'); expect(tileGrid.tileSizes_).to.have.length(20); expect(tileGrid.tileSizes_).to.eql( - [256, 256, 256, 256, 256, 256, 256, 256, 256, 256, - 256, 256, 256, 256, 256, 256, 256, 256, 256, 256]); + goog.array.repeat(256, 20)); }); }); }); +goog.require('goog.array'); goog.require('ol.format.WMTSCapabilities'); goog.require('ol.tilegrid.WMTS'); From b05193fa454a963b33e6c5965181abde9b45b7fa Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 28 Apr 2015 23:26:35 +0200 Subject: [PATCH 3/5] Replace ol.tilegrid.XYZ with an ol.tilegrid.createXYZ function --- examples/canvas-tiles.js | 5 +- examples/tile-vector.js | 9 +- examples/vector-esri-edit.js | 3 +- examples/vector-esri.js | 3 +- examples/vector-osm.js | 3 +- examples/vector-wfs.js | 3 +- examples/xyz-esri-4326-512.js | 2 +- src/ol/source/bingmapssource.js | 13 +- src/ol/source/tilejsonsource.js | 3 +- src/ol/source/tileutfgridsource.js | 3 +- src/ol/source/xyzsource.js | 6 +- src/ol/tilegrid/tilegrid.js | 50 +++--- src/ol/tilegrid/xyztilegrid.js | 130 ---------------- test/spec/ol/tilegrid/tilegrid.test.js | 26 ++++ test/spec/ol/tilegrid/xyztilegrid.test.js | 168 --------------------- test_rendering/spec/ol/layer/image.test.js | 5 +- 16 files changed, 79 insertions(+), 353 deletions(-) delete mode 100644 src/ol/tilegrid/xyztilegrid.js delete mode 100644 test/spec/ol/tilegrid/xyztilegrid.test.js diff --git a/examples/canvas-tiles.js b/examples/canvas-tiles.js index 1f46f51e0f..a3bf7004bb 100644 --- a/examples/canvas-tiles.js +++ b/examples/canvas-tiles.js @@ -5,7 +5,6 @@ goog.require('ol.layer.Tile'); goog.require('ol.proj'); goog.require('ol.source.OSM'); goog.require('ol.source.TileDebug'); -goog.require('ol.tilegrid.XYZ'); var map = new ol.Map({ @@ -16,9 +15,7 @@ var map = new ol.Map({ new ol.layer.Tile({ source: new ol.source.TileDebug({ projection: 'EPSG:3857', - tileGrid: new ol.tilegrid.XYZ({ - maxZoom: 22 - }) + tileGrid: ol.tilegrid.createXYZ({maxZoom: 22}) }) }) ], diff --git a/examples/tile-vector.js b/examples/tile-vector.js index 245e8db64c..40644545fd 100644 --- a/examples/tile-vector.js +++ b/examples/tile-vector.js @@ -7,13 +7,12 @@ goog.require('ol.source.TileVector'); goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); -goog.require('ol.tilegrid.XYZ'); var waterLayer = new ol.layer.Vector({ source: new ol.source.TileVector({ format: new ol.format.TopoJSON(), projection: 'EPSG:3857', - tileGrid: new ol.tilegrid.XYZ({ + tileGrid: ol.tilegrid.createXYZ({ maxZoom: 19 }), url: 'http://{a-c}.tile.openstreetmap.us/' + @@ -31,7 +30,7 @@ var roadLayer = new ol.layer.Vector({ source: new ol.source.TileVector({ format: new ol.format.TopoJSON(), projection: 'EPSG:3857', - tileGrid: new ol.tilegrid.XYZ({ + tileGrid: ol.tilegrid.createXYZ({ maxZoom: 19 }), url: 'http://{a-c}.tile.openstreetmap.us/' + @@ -87,7 +86,7 @@ var buildingLayer = new ol.layer.Vector({ defaultProjection: 'EPSG:4326' }), projection: 'EPSG:3857', - tileGrid: new ol.tilegrid.XYZ({ + tileGrid: ol.tilegrid.createXYZ({ maxZoom: 19 }), url: 'http://{a-c}.tile.openstreetmap.us/' + @@ -106,7 +105,7 @@ var landuseLayer = new ol.layer.Vector({ defaultProjection: 'EPSG:4326' }), projection: 'EPSG:3857', - tileGrid: new ol.tilegrid.XYZ({ + tileGrid: ol.tilegrid.createXYZ({ maxZoom: 19 }), url: 'http://{a-c}.tile.openstreetmap.us/' + diff --git a/examples/vector-esri-edit.js b/examples/vector-esri-edit.js index 5ebb58bf7f..d646f18b26 100644 --- a/examples/vector-esri-edit.js +++ b/examples/vector-esri-edit.js @@ -12,7 +12,6 @@ goog.require('ol.loadingstrategy'); goog.require('ol.proj'); goog.require('ol.source.Vector'); goog.require('ol.source.XYZ'); -goog.require('ol.tilegrid.XYZ'); var serviceUrl = 'http://services.arcgis.com/rOo16HdIMeOBI4Mb/arcgis/rest/' + @@ -45,7 +44,7 @@ var vectorSource = new ol.source.Vector({ } }}); }, - strategy: ol.loadingstrategy.tile(new ol.tilegrid.XYZ({ + strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({ tileSize: 512 })) }); diff --git a/examples/vector-esri.js b/examples/vector-esri.js index 0ba8581813..655062f372 100644 --- a/examples/vector-esri.js +++ b/examples/vector-esri.js @@ -11,7 +11,6 @@ goog.require('ol.source.XYZ'); goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); -goog.require('ol.tilegrid.XYZ'); var serviceUrl = 'http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/' + @@ -91,7 +90,7 @@ var vectorSource = new ol.source.Vector({ } }}); }, - strategy: ol.loadingstrategy.tile(new ol.tilegrid.XYZ({ + strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({ tileSize: 512 })) }); diff --git a/examples/vector-osm.js b/examples/vector-osm.js index eb1f5659a1..30526090f5 100644 --- a/examples/vector-osm.js +++ b/examples/vector-osm.js @@ -12,7 +12,6 @@ goog.require('ol.style.Circle'); goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); -goog.require('ol.tilegrid.XYZ'); var styles = { 'amenity': { @@ -102,7 +101,7 @@ var vectorSource = new ol.source.Vector({ vectorSource.addFeatures(features); }); }, - strategy: ol.loadingstrategy.tile(new ol.tilegrid.XYZ({ + strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({ maxZoom: 19 })) }); diff --git a/examples/vector-wfs.js b/examples/vector-wfs.js index 47386c71d7..4604537bf9 100644 --- a/examples/vector-wfs.js +++ b/examples/vector-wfs.js @@ -8,7 +8,6 @@ goog.require('ol.source.BingMaps'); goog.require('ol.source.Vector'); goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); -goog.require('ol.tilegrid.XYZ'); // format used to parse WFS GetFeature responses @@ -24,7 +23,7 @@ var vectorSource = new ol.source.Vector({ // parameter to the URL $.ajax({url: url, dataType: 'jsonp', jsonp: false}); }, - strategy: ol.loadingstrategy.tile(new ol.tilegrid.XYZ({ + strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({ maxZoom: 19 })) }); diff --git a/examples/xyz-esri-4326-512.js b/examples/xyz-esri-4326-512.js index c10c5d270b..45cc0fa316 100644 --- a/examples/xyz-esri-4326-512.js +++ b/examples/xyz-esri-4326-512.js @@ -35,7 +35,7 @@ var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ - /* ol.source.XYZ and ol.tilegrid.XYZ have no resolutions config */ + /* ol.source.XYZ and ol.tilegrid.TileGrid have no resolutions config */ source: new ol.source.TileImage({ attributions: [attribution], tileUrlFunction: function(tileCoord, pixelRatio, projection) { diff --git a/src/ol/source/bingmapssource.js b/src/ol/source/bingmapssource.js index 76656e9fec..e756fb8b11 100644 --- a/src/ol/source/bingmapssource.js +++ b/src/ol/source/bingmapssource.js @@ -12,7 +12,6 @@ goog.require('ol.proj'); goog.require('ol.source.State'); goog.require('ol.source.TileImage'); goog.require('ol.tilecoord'); -goog.require('ol.tilegrid.XYZ'); @@ -103,18 +102,20 @@ ol.source.BingMaps.prototype.handleImageryMetadataResponse = var maxZoom = this.maxZoom_ == -1 ? resource.zoomMax : this.maxZoom_; var sourceProjection = this.getProjection(); - var tileGrid = new ol.tilegrid.XYZ({ - extent: ol.tilegrid.extentFromProjection(sourceProjection), + var extent = ol.tilegrid.extentFromProjection(sourceProjection); + var tileSize = resource.imageWidth == resource.imageHeight ? + resource.imageWidth : [resource.imageWidth, resource.imageHeight]; + var tileGrid = ol.tilegrid.createXYZ({ + extent: extent, minZoom: resource.zoomMin, maxZoom: maxZoom, - tileSize: resource.imageWidth == resource.imageHeight ? - resource.imageWidth : [resource.imageWidth, resource.imageHeight] + tileSize: tileSize }); this.tileGrid = tileGrid; var culture = this.culture_; this.tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform( - tileGrid.createTileCoordTransform(), + ol.tilegrid.createOriginTopLeftTileCoordTransform(tileGrid), ol.TileUrlFunction.createFromTileUrlFunctions( goog.array.map( resource.imageUrlSubdomains, diff --git a/src/ol/source/tilejsonsource.js b/src/ol/source/tilejsonsource.js index 609ce6267f..37dc0f63a0 100644 --- a/src/ol/source/tilejsonsource.js +++ b/src/ol/source/tilejsonsource.js @@ -17,7 +17,6 @@ goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.source.State'); goog.require('ol.source.TileImage'); -goog.require('ol.tilegrid.XYZ'); @@ -69,7 +68,7 @@ ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) { } var minZoom = tileJSON.minzoom || 0; var maxZoom = tileJSON.maxzoom || 22; - var tileGrid = new ol.tilegrid.XYZ({ + var tileGrid = ol.tilegrid.createXYZ({ extent: ol.tilegrid.extentFromProjection(sourceProjection), maxZoom: maxZoom, minZoom: minZoom diff --git a/src/ol/source/tileutfgridsource.js b/src/ol/source/tileutfgridsource.js index 9e3bcee79f..549ad82d5c 100644 --- a/src/ol/source/tileutfgridsource.js +++ b/src/ol/source/tileutfgridsource.js @@ -13,7 +13,6 @@ goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.source.State'); goog.require('ol.source.Tile'); -goog.require('ol.tilegrid.XYZ'); @@ -122,7 +121,7 @@ ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) { } var minZoom = tileJSON.minzoom || 0; var maxZoom = tileJSON.maxzoom || 22; - var tileGrid = new ol.tilegrid.XYZ({ + var tileGrid = ol.tilegrid.createXYZ({ extent: ol.tilegrid.extentFromProjection(sourceProjection), maxZoom: maxZoom, minZoom: minZoom diff --git a/src/ol/source/xyzsource.js b/src/ol/source/xyzsource.js index afdeaeb7eb..2c60a6178e 100644 --- a/src/ol/source/xyzsource.js +++ b/src/ol/source/xyzsource.js @@ -3,7 +3,6 @@ goog.provide('ol.source.XYZ'); goog.require('ol.Attribution'); goog.require('ol.TileUrlFunction'); goog.require('ol.source.TileImage'); -goog.require('ol.tilegrid.XYZ'); @@ -20,7 +19,7 @@ ol.source.XYZ = function(options) { var projection = goog.isDef(options.projection) ? options.projection : 'EPSG:3857'; - var tileGrid = new ol.tilegrid.XYZ({ + var tileGrid = ol.tilegrid.createXYZ({ extent: ol.tilegrid.extentFromProjection(projection), maxZoom: options.maxZoom, tileSize: options.tileSize @@ -42,7 +41,8 @@ ol.source.XYZ = function(options) { * @private * @type {ol.TileCoordTransformType} */ - this.tileCoordTransform_ = tileGrid.createTileCoordTransform(); + this.tileCoordTransform_ = + ol.tilegrid.createOriginTopLeftTileCoordTransform(tileGrid); if (goog.isDef(options.tileUrlFunction)) { this.setTileUrlFunction(options.tileUrlFunction); diff --git a/src/ol/tilegrid/tilegrid.js b/src/ol/tilegrid/tilegrid.js index 507fbbf614..0dcbafc964 100644 --- a/src/ol/tilegrid/tilegrid.js +++ b/src/ol/tilegrid/tilegrid.js @@ -534,36 +534,48 @@ ol.tilegrid.getForProjection = function(projection) { */ ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_corner) { - var tileSize = goog.isDef(opt_tileSize) ? - ol.size.toSize(opt_tileSize) : ol.size.toSize(ol.DEFAULT_TILE_SIZE); - var corner = goog.isDef(opt_corner) ? opt_corner : ol.extent.Corner.BOTTOM_LEFT; var resolutions = ol.tilegrid.resolutionsFromExtent( - extent, opt_maxZoom, ol.size.toSize(tileSize)); - - var widths = new Array(resolutions.length); - var extentWidth = ol.extent.getWidth(extent); - for (var z = resolutions.length - 1; z >= 0; --z) { - widths[z] = extentWidth / tileSize[0] / resolutions[z]; - } + extent, opt_maxZoom, opt_tileSize); return new ol.tilegrid.TileGrid({ + extent: extent, origin: ol.extent.getCorner(extent, corner), resolutions: resolutions, - tileSize: goog.isDef(opt_tileSize) ? opt_tileSize : ol.DEFAULT_TILE_SIZE, - widths: widths + tileSize: opt_tileSize }); }; +/** + * Creates a tile grid with a standard XYZ tiling scheme. + * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options. + * @return {ol.tilegrid.TileGrid} Tile grid instance. + * @api + */ +ol.tilegrid.createXYZ = function(opt_options) { + var options = /** @type {olx.tilegrid.TileGridOptions} */ ({}); + goog.object.extend(options, goog.isDef(opt_options) ? + opt_options : /** @type {olx.tilegrid.XYZOptions} */ ({})); + if (!goog.isDef(options.extent)) { + options.extent = ol.proj.get('EPSG:3857').getExtent(); + } + options.resolutions = ol.tilegrid.resolutionsFromExtent( + options.extent, options.maxZoom, options.tileSize); + delete options.maxZoom; + return new ol.tilegrid.TileGrid(options); +}; + + /** * Create a resolutions array from an extent. A zoom factor of 2 is assumed. * @param {ol.Extent} extent Extent. * @param {number=} opt_maxZoom Maximum zoom level (default is * ol.DEFAULT_MAX_ZOOM). - * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). * @return {!Array.} Resolutions array. */ ol.tilegrid.resolutionsFromExtent = @@ -574,8 +586,8 @@ ol.tilegrid.resolutionsFromExtent = var height = ol.extent.getHeight(extent); var width = ol.extent.getWidth(extent); - var tileSize = goog.isDef(opt_tileSize) ? - opt_tileSize : ol.size.toSize(ol.DEFAULT_TILE_SIZE); + var tileSize = ol.size.toSize(goog.isDef(opt_tileSize) ? + opt_tileSize : ol.DEFAULT_TILE_SIZE); var maxResolution = Math.max( width / tileSize[0], height / tileSize[1]); @@ -643,12 +655,8 @@ ol.tilegrid.createOriginTopLeftTileCoordTransform = function(tileGrid) { } var z = tileCoord[0]; var fullTileRange = tileGrid.getFullTileRange(z); - var height; - if (goog.isNull(fullTileRange) || fullTileRange.minY < 0) { - height = 0; - } else { - height = fullTileRange.getHeight(); - } + var height = (goog.isNull(fullTileRange) || fullTileRange.minY < 0) ? + 0 : fullTileRange.getHeight(); return ol.tilecoord.createOrUpdate( z, tileCoord[1], height - tileCoord[2] - 1, opt_tileCoord); } diff --git a/src/ol/tilegrid/xyztilegrid.js b/src/ol/tilegrid/xyztilegrid.js deleted file mode 100644 index b10a398c67..0000000000 --- a/src/ol/tilegrid/xyztilegrid.js +++ /dev/null @@ -1,130 +0,0 @@ -goog.provide('ol.tilegrid.XYZ'); - -goog.require('goog.math'); -goog.require('ol'); -goog.require('ol.TileCoord'); -goog.require('ol.TileRange'); -goog.require('ol.extent'); -goog.require('ol.extent.Corner'); -goog.require('ol.proj'); -goog.require('ol.proj.EPSG3857'); -goog.require('ol.size'); -goog.require('ol.tilecoord'); -goog.require('ol.tilegrid.TileGrid'); - - - -/** - * @classdesc - * Set the grid pattern for sources accessing XYZ tiled-image servers. - * - * @constructor - * @extends {ol.tilegrid.TileGrid} - * @param {olx.tilegrid.XYZOptions} options XYZ options. - * @struct - * @api - */ -ol.tilegrid.XYZ = function(options) { - var extent = goog.isDef(options.extent) ? - options.extent : ol.proj.EPSG3857.EXTENT; - var tileSize; - if (goog.isDef(options.tileSize)) { - tileSize = ol.size.toSize(options.tileSize); - } - var resolutions = ol.tilegrid.resolutionsFromExtent( - extent, options.maxZoom, tileSize); - - goog.base(this, { - minZoom: options.minZoom, - origin: ol.extent.getCorner(extent, ol.extent.Corner.TOP_LEFT), - resolutions: resolutions, - tileSize: options.tileSize - }); - -}; -goog.inherits(ol.tilegrid.XYZ, ol.tilegrid.TileGrid); - - -/** - * @inheritDoc - */ -ol.tilegrid.XYZ.prototype.createTileCoordTransform = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; - var minZ = this.minZoom; - var maxZ = this.maxZoom; - /** @type {Array.} */ - var tileRangeByZ = null; - if (goog.isDef(options.extent)) { - tileRangeByZ = new Array(maxZ + 1); - var z; - for (z = 0; z <= maxZ; ++z) { - if (z < minZ) { - tileRangeByZ[z] = null; - } else { - tileRangeByZ[z] = this.getTileRangeForExtentAndZ(options.extent, z); - } - } - } - return ( - /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.proj.Projection} projection Projection. - * @param {ol.TileCoord=} opt_tileCoord Destination tile coordinate. - * @return {ol.TileCoord} Tile coordinate. - */ - function(tileCoord, projection, opt_tileCoord) { - var z = tileCoord[0]; - if (z < minZ || maxZ < z) { - return null; - } - var n = Math.pow(2, z); - var x = tileCoord[1]; - var y = tileCoord[2]; - if (y < -n || -1 < y) { - return null; - } - if (!goog.isNull(tileRangeByZ)) { - if (!tileRangeByZ[z].containsXY(x, y)) { - return null; - } - } - return ol.tilecoord.createOrUpdate(z, x, -y - 1, opt_tileCoord); - }); -}; - - -/** - * @inheritDoc - */ -ol.tilegrid.XYZ.prototype.getTileCoordChildTileRange = - function(tileCoord, opt_tileRange) { - if (tileCoord[0] < this.maxZoom) { - var doubleX = 2 * tileCoord[1]; - var doubleY = 2 * tileCoord[2]; - return ol.TileRange.createOrUpdate( - doubleX, doubleX + 1, - doubleY, doubleY + 1, - opt_tileRange); - } else { - return null; - } -}; - - -/** - * @inheritDoc - */ -ol.tilegrid.XYZ.prototype.forEachTileCoordParentTileRange = - function(tileCoord, callback, opt_this, opt_tileRange) { - var tileRange = ol.TileRange.createOrUpdate( - 0, tileCoord[1], 0, tileCoord[2], opt_tileRange); - var z; - for (z = tileCoord[0] - 1; z >= this.minZoom; --z) { - tileRange.minX = tileRange.maxX >>= 1; - tileRange.minY = tileRange.maxY >>= 1; - if (callback.call(opt_this, z, tileRange)) { - return true; - } - } - return false; -}; diff --git a/test/spec/ol/tilegrid/tilegrid.test.js b/test/spec/ol/tilegrid/tilegrid.test.js index e476a58726..757959a982 100644 --- a/test/spec/ol/tilegrid/tilegrid.test.js +++ b/test/spec/ol/tilegrid/tilegrid.test.js @@ -442,6 +442,32 @@ describe('ol.tilegrid.TileGrid', function() { }); + describe('createXYZ()', function() { + + it('uses defaults', function() { + var tileGrid = ol.tilegrid.createXYZ(); + expect(tileGrid.getExtent()).to.eql( + ol.proj.get('EPSG:3857').getExtent()); + expect(tileGrid.getMinZoom()).to.equal(0); + expect(tileGrid.getMaxZoom()).to.equal(ol.DEFAULT_MAX_ZOOM); + expect(tileGrid.getTileSize()).to.equal(ol.DEFAULT_TILE_SIZE); + }); + + it('respects configuration options', function() { + var tileGrid = ol.tilegrid.createXYZ({ + extent: [10, 20, 30, 40], + minZoom: 1, + maxZoom: 2, + tileSize: 128 + }); + expect(tileGrid.getExtent()).to.eql([10, 20, 30, 40]); + expect(tileGrid.getMinZoom()).to.equal(1); + expect(tileGrid.getMaxZoom()).to.equal(2); + expect(tileGrid.getTileSize()).to.equal(128); + }); + + }); + describe('getForProjection', function() { it('gets the default tile grid for a projection', function() { diff --git a/test/spec/ol/tilegrid/xyztilegrid.test.js b/test/spec/ol/tilegrid/xyztilegrid.test.js deleted file mode 100644 index b82079d354..0000000000 --- a/test/spec/ol/tilegrid/xyztilegrid.test.js +++ /dev/null @@ -1,168 +0,0 @@ -goog.provide('ol.test.tilegrid.XYZ'); - - -describe('ol.tilegrid.XYZ', function() { - - var xyzTileGrid; - beforeEach(function() { - xyzTileGrid = new ol.tilegrid.XYZ({ - maxZoom: 22 - }); - }); - - describe('#getTileCoordChildTileRange()', function() { - - it('returns the tile range for one zoom level deeper', function() { - var range; - - range = xyzTileGrid.getTileCoordChildTileRange([0, 0, 0]); - expect(range.minX).to.be(0); - expect(range.maxX).to.be(1); - expect(range.minY).to.be(0); - expect(range.maxY).to.be(1); - - range = xyzTileGrid.getTileCoordChildTileRange([0, 1, 0]); - expect(range.minX).to.be(2); - expect(range.maxX).to.be(3); - expect(range.minY).to.be(0); - expect(range.maxY).to.be(1); - - range = xyzTileGrid.getTileCoordChildTileRange([0, 0, 1]); - expect(range.minX).to.be(0); - expect(range.maxX).to.be(1); - expect(range.minY).to.be(2); - expect(range.maxY).to.be(3); - - range = xyzTileGrid.getTileCoordChildTileRange([0, -1, 0]); - expect(range.minX).to.be(-2); - expect(range.maxX).to.be(-1); - expect(range.minY).to.be(0); - expect(range.maxY).to.be(1); - - range = xyzTileGrid.getTileCoordChildTileRange([0, 0, -1]); - expect(range.minX).to.be(0); - expect(range.maxX).to.be(1); - expect(range.minY).to.be(-2); - expect(range.maxY).to.be(-1); - }); - - it('returns null for z > maxZoom', function() { - var max = xyzTileGrid.maxZoom; - var range = xyzTileGrid.getTileCoordChildTileRange([max + 1, 0, 0]); - expect(range).to.be(null); - }); - - it('is like ol.tilegrid.TileGrid#getTileCoordChildTileRange()', function() { - var superMethod = ol.tilegrid.TileGrid.prototype - .getTileCoordChildTileRange.bind(xyzTileGrid); - - var coord, selfRange, superRange; - - coord = [0, 0, 0]; - selfRange = xyzTileGrid.getTileCoordChildTileRange(coord); - superRange = superMethod(coord); - expect(selfRange.minX).to.be(superRange.minX); - expect(selfRange.maxX).to.be(superRange.maxX); - expect(selfRange.minY).to.be(superRange.minY); - expect(selfRange.maxY).to.be(superRange.maxY); - - coord = [1, 2, 3]; - selfRange = xyzTileGrid.getTileCoordChildTileRange(coord); - superRange = superMethod(coord); - expect(selfRange.minX).to.be(superRange.minX); - expect(selfRange.maxX).to.be(superRange.maxX); - expect(selfRange.minY).to.be(superRange.minY); - expect(selfRange.maxY).to.be(superRange.maxY); - - }); - - }); - - describe('forEachTileCoordParentTileRange', function() { - - it('iterates as expected', function() { - - var tileCoord = [5, 11, 21]; - var zs = [], tileRanges = []; - xyzTileGrid.forEachTileCoordParentTileRange( - tileCoord, - function(z, tileRange) { - zs.push(z); - tileRanges.push(new ol.TileRange( - tileRange.minX, tileRange.maxX, - tileRange.minY, tileRange.maxY)); - return false; - }); - - expect(zs.length).to.eql(5); - expect(tileRanges.length).to.eql(5); - - expect(zs[0]).to.eql(4); - expect(tileRanges[0].minX).to.eql(5); - expect(tileRanges[0].maxX).to.eql(5); - expect(tileRanges[0].minY).to.eql(10); - expect(tileRanges[0].maxY).to.eql(10); - - expect(zs[1]).to.eql(3); - expect(tileRanges[1].minX).to.eql(2); - expect(tileRanges[1].maxX).to.eql(2); - expect(tileRanges[1].minY).to.eql(5); - expect(tileRanges[1].maxY).to.eql(5); - - expect(zs[2]).to.eql(2); - expect(tileRanges[2].minX).to.eql(1); - expect(tileRanges[2].maxX).to.eql(1); - expect(tileRanges[2].minY).to.eql(2); - expect(tileRanges[2].maxY).to.eql(2); - - expect(zs[3]).to.eql(1); - expect(tileRanges[3].minX).to.eql(0); - expect(tileRanges[3].maxX).to.eql(0); - expect(tileRanges[3].minY).to.eql(1); - expect(tileRanges[3].maxY).to.eql(1); - - expect(zs[4]).to.eql(0); - expect(tileRanges[4].minX).to.eql(0); - expect(tileRanges[4].maxX).to.eql(0); - expect(tileRanges[4].minY).to.eql(0); - expect(tileRanges[4].maxY).to.eql(0); - - }); - - }); - - describe('getResolution', function() { - - it('returns the correct resolution at the equator', function() { - // @see http://msdn.microsoft.com/en-us/library/aa940990.aspx - expect(xyzTileGrid.getResolution(0)).to.roughlyEqual(156543.04, 1e-2); - expect(xyzTileGrid.getResolution(1)).to.roughlyEqual(78271.52, 1e-2); - expect(xyzTileGrid.getResolution(2)).to.roughlyEqual(39135.76, 1e-2); - expect(xyzTileGrid.getResolution(3)).to.roughlyEqual(19567.88, 1e-2); - expect(xyzTileGrid.getResolution(4)).to.roughlyEqual(9783.94, 1e-2); - expect(xyzTileGrid.getResolution(5)).to.roughlyEqual(4891.97, 1e-2); - expect(xyzTileGrid.getResolution(6)).to.roughlyEqual(2445.98, 1e-2); - expect(xyzTileGrid.getResolution(7)).to.roughlyEqual(1222.99, 1e-2); - expect(xyzTileGrid.getResolution(8)).to.roughlyEqual(611.50, 1e-2); - expect(xyzTileGrid.getResolution(9)).to.roughlyEqual(305.75, 1e-2); - expect(xyzTileGrid.getResolution(10)).to.roughlyEqual(152.87, 1e-2); - expect(xyzTileGrid.getResolution(11)).to.roughlyEqual(76.44, 1e-2); - expect(xyzTileGrid.getResolution(12)).to.roughlyEqual(38.22, 1e-2); - expect(xyzTileGrid.getResolution(13)).to.roughlyEqual(19.11, 1e-2); - expect(xyzTileGrid.getResolution(14)).to.roughlyEqual(9.55, 1e-2); - expect(xyzTileGrid.getResolution(15)).to.roughlyEqual(4.78, 1e-2); - expect(xyzTileGrid.getResolution(16)).to.roughlyEqual(2.39, 1e-2); - expect(xyzTileGrid.getResolution(17)).to.roughlyEqual(1.19, 1e-2); - expect(xyzTileGrid.getResolution(18)).to.roughlyEqual(0.60, 1e-2); - expect(xyzTileGrid.getResolution(19)).to.roughlyEqual(0.30, 1e-2); - }); - - }); - -}); - - -goog.require('ol.TileCoord'); -goog.require('ol.TileRange'); -goog.require('ol.tilegrid.TileGrid'); -goog.require('ol.tilegrid.XYZ'); diff --git a/test_rendering/spec/ol/layer/image.test.js b/test_rendering/spec/ol/layer/image.test.js index 692090e015..4ba4c44eb0 100644 --- a/test_rendering/spec/ol/layer/image.test.js +++ b/test_rendering/spec/ol/layer/image.test.js @@ -55,8 +55,8 @@ describe('ol.rendering.layer.Image', function() { beforeEach(function() { source = new ol.source.ImageStatic({ url: 'spec/ol/data/tiles/osm/5/5/12.png', - imageExtent: new ol.tilegrid.XYZ({}).getTileCoordExtent( - [5, 5, -12 - 1]), + imageExtent: ol.tilegrid.createXYZ().getTileCoordExtent( + [5, 5, 32 - 12 - 1]), projection: ol.proj.get('EPSG:3857') }); }); @@ -91,4 +91,3 @@ goog.require('ol.Map'); goog.require('ol.View'); goog.require('ol.layer.Image'); goog.require('ol.source.ImageStatic'); -goog.require('ol.tilegrid.XYZ'); From 0650a97371b7b69ddcf1581410852248eb2d53b1 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 29 Apr 2015 08:49:31 +0200 Subject: [PATCH 4/5] Explain tilegrid and xyz source changes --- changelog/upgrade-notes.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 530f30dabe..7279cac26c 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -1,5 +1,15 @@ ## Upgrade notes +### v3.6.0 + +#### `ol.tilegrid` changes + +* The `ol.tilegrid.XYZ` constructor has been replaced by a static `ol.tilegrid.createXYZ()` function. The `ol.tilegrid.createXYZ()` function takes the same arguments as the previous `ol.tilegrid.XYZ` constructor, but returns an `ol.tilegrid.TileGrid` instance. +* The internal tile coordinate scheme for XYZ sources has been changed. Previously, the `y` of tile coordinates was transformed to the coordinates used by sources by calculating `-y-1`. Now, it is transformed by calculating `height-y-1`, where height is the number of rows of the tile grid at the zoom level of the tile coordinate. +* The `widths` constructor option of `ol.tilegrid.TileGrid` and subclasses is no longer available, and it is no longer necessary to get proper wrapping at the 180° meridian. However, for `ol.tilegrid.WMTS`, there is a new option `sizes`, where each entry is an `ol.Size` with the `width` ('TileMatrixWidth' in WMTS capabilities) as first and the `height` ('TileMatrixHeight') as second entry of the array. For other tile grids, users can +now specify an `extent` instead of `widths`. These settings are used to restrict the range of tiles that sources will request. +* For `ol.source.TileWMS`, the default value of `warpX` used to be `undefined`, meaning that WMS requests with out-of-extent tile BBOXes would be sent. Now `wrapX` can only be `true` or `false`, and the new default is `true`. No application code changes should be required, but the resulting WMS requests for out-of-extent tiles will uses BBOXes that are shifted to real world coordinates, and no more out-of-extent BBOXes. + ### v3.5.0 #### `ol.Object` and `bindTo` From 32efd9939748ded9d50cf1b6ebc192d835901e2d Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 29 Apr 2015 10:35:18 +0200 Subject: [PATCH 5/5] Do not require projection extent for WMTS bbox validity check --- src/ol/source/wmtssource.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/ol/source/wmtssource.js b/src/ol/source/wmtssource.js index 7eb25cdb2b..d815e849b1 100644 --- a/src/ol/source/wmtssource.js +++ b/src/ol/source/wmtssource.js @@ -355,7 +355,7 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) { goog.asserts.assert(l['TileMatrixSetLink'].length > 0, 'layer has TileMatrixSetLink'); - var idx, matrixSet, wrapX; + var idx, matrixSet; if (l['TileMatrixSetLink'].length > 1) { idx = goog.array.findIndex(l['TileMatrixSetLink'], function(elt, index, array) { @@ -380,13 +380,6 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) { goog.asserts.assert(!goog.isNull(matrixSet), 'TileMatrixSet must not be null'); - var wgs84BoundingBox = l['WGS84BoundingBox']; - if (goog.isDef(wgs84BoundingBox)) { - var wgs84ProjectionExtent = ol.proj.get('EPSG:4326').getExtent(); - wrapX = (wgs84BoundingBox[0] == wgs84ProjectionExtent[0] && - wgs84BoundingBox[2] == wgs84ProjectionExtent[2]); - } - var format = /** @type {string} */ (l['Format'][0]); if (goog.isDef(config['format'])) { format = config['format']; @@ -434,16 +427,24 @@ ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) { /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')); } - var projectionExtent = projection.getExtent(); - var extent; - if (!goog.isNull(projectionExtent)) { - var projectionExtentWgs84 = ol.proj.transformExtent( - projectionExtent, projection, 'EPSG:4326'); - if (ol.extent.containsExtent(projectionExtentWgs84, wgs84BoundingBox)) { - extent = ol.proj.transformExtent( - wgs84BoundingBox, 'EPSG:4326', projection); + var wgs84BoundingBox = l['WGS84BoundingBox']; + var extent, wrapX; + if (goog.isDef(wgs84BoundingBox)) { + var wgs84ProjectionExtent = ol.proj.get('EPSG:4326').getExtent(); + wrapX = (wgs84BoundingBox[0] == wgs84ProjectionExtent[0] && + wgs84BoundingBox[2] == wgs84ProjectionExtent[2]); + extent = ol.proj.transformExtent( + wgs84BoundingBox, 'EPSG:4326', projection); + var projectionExtent = projection.getExtent(); + if (!goog.isNull(projectionExtent)) { + // If possible, do a sanity check on the extent - it should never be + // bigger than the validity extent of the projection of a matrix set. + if (!ol.extent.containsExtent(projectionExtent, extent)) { + extent = undefined; + } } } + var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet( matrixSetObj, extent);