diff --git a/externs/olx.js b/externs/olx.js index 3d8444a6ca..bf89432715 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4027,7 +4027,8 @@ olx.source.TileImageOptions.prototype.wrapX; * tileGrid: ol.tilegrid.TileGrid, * tileUrlFunction: (ol.TileUrlFunctionType|undefined), * url: (string|undefined), - * urls: (Array.|undefined)}} + * urls: (Array.|undefined), + * wrapX: (boolean|undefined)}} * @api */ olx.source.TileVectorOptions; @@ -4090,6 +4091,16 @@ olx.source.TileVectorOptions.prototype.url; olx.source.TileVectorOptions.prototype.urls; +/** + * Wrap the world horizontally. Default is `true`. For vector editing across the + * -180° and 180° meridians to work properly, this should be set to `false`. The + * resulting geometry coordinates will then exceed the world bounds. + * @type {boolean|undefined} + * @api + */ +olx.source.TileVectorOptions.prototype.wrapX; + + /** * @typedef {{url: (string|undefined), * displayDpi: (number|undefined), diff --git a/src/ol/source/tilevectorsource.js b/src/ol/source/tilevectorsource.js index bfb56a798d..870eb6cfae 100644 --- a/src/ol/source/tilevectorsource.js +++ b/src/ol/source/tilevectorsource.js @@ -7,6 +7,7 @@ goog.require('ol.TileUrlFunction'); goog.require('ol.featureloader'); goog.require('ol.source.State'); goog.require('ol.source.Vector'); +goog.require('ol.tilecoord'); goog.require('ol.tilegrid.TileGrid'); @@ -27,7 +28,8 @@ ol.source.TileVector = function(options) { attributions: options.attributions, logo: options.logo, projection: undefined, - state: ol.source.State.READY + state: ol.source.State.READY, + wrapX: options.wrapX }); /** @@ -230,6 +232,28 @@ ol.source.TileVector.prototype.getFeaturesAtCoordinateAndResolution = ol.source.TileVector.prototype.getFeaturesInExtent = goog.abstractMethod; +/** + * Handles x-axis wrapping and returns a tile coordinate transformed from the + * internal tile scheme to the tile grid's tile scheme. When the tile coordinate + * is outside the resolution and extent range of the tile grid, `null` will be + * returned. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.proj.Projection} projection Projection. + * @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.TileVector.prototype.getTileCoordForTileUrlFunction = + function(tileCoord, projection) { + var tileGrid = this.tileGrid_; + goog.asserts.assert(!goog.isNull(tileGrid), 'tile grid needed'); + if (this.getWrapX()) { + tileCoord = ol.tilecoord.wrapX(tileCoord, tileGrid, projection); + } + return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? + tileGrid.transformTileCoord(tileCoord) : null; +}; + + /** * @param {number} z Z. * @param {number} x X. @@ -267,11 +291,12 @@ ol.source.TileVector.prototype.loadFeatures = for (y = tileRange.minY; y <= tileRange.maxY; ++y) { var tileKey = this.getTileKeyZXY_(z, x, y); if (!(tileKey in tiles)) { - tileCoord[0] = z; tileCoord[1] = x; tileCoord[2] = y; - tileGrid.transformTileCoord(tileCoord, tileCoord); - var url = tileUrlFunction(tileCoord, 1, projection); + var urlTileCoord = this.getTileCoordForTileUrlFunction( + tileCoord, projection); + var url = goog.isNull(urlTileCoord) ? undefined : + tileUrlFunction(urlTileCoord, 1, projection); if (goog.isDef(url)) { tiles[tileKey] = []; var loader = ol.featureloader.loadFeaturesXhr(url, this.format_, diff --git a/test/spec/ol/source/tilevectorsource.test.js b/test/spec/ol/source/tilevectorsource.test.js index 32c83c346d..87a34db4de 100644 --- a/test/spec/ol/source/tilevectorsource.test.js +++ b/test/spec/ol/source/tilevectorsource.test.js @@ -9,7 +9,6 @@ describe('ol.source.TileVector', function() { var tileCoords = []; var source = new ol.source.TileVector({ format: new ol.format.TopoJSON(), - projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 19 }), @@ -18,8 +17,9 @@ describe('ol.source.TileVector', function() { return null; } }); + var projection = ol.proj.get('EPSG:3857'); source.loadFeatures( - [-8238854, 4969777, -8237854, 4970777], 4.8, source.getProjection()); + [-8238854, 4969777, -8237854, 4970777], 4.8, projection); expect(tileCoords[0]).to.eql([15, 9647, 12320]); expect(tileCoords[1]).to.eql([15, 9647, 12319]); expect(tileCoords[2]).to.eql([15, 9648, 12320]); @@ -28,8 +28,58 @@ describe('ol.source.TileVector', function() { }); + describe('#getTileCoordForTileUrlFunction()', function() { + + it('returns the expected tile coordinate - {wrapX: true}', function() { + var tileSource = new ol.source.TileVector({ + format: new ol.format.TopoJSON(), + tileGrid: ol.tilegrid.createXYZ({ + maxZoom: 19 + }), + wrapX: true + }); + var projection = ol.proj.get('EPSG:3857'); + + var tileCoord = tileSource.getTileCoordForTileUrlFunction( + [6, -31, 41], projection); + expect(tileCoord).to.eql([6, 33, 22]); + + tileCoord = tileSource.getTileCoordForTileUrlFunction( + [6, 33, 41], projection); + expect(tileCoord).to.eql([6, 33, 22]); + + tileCoord = tileSource.getTileCoordForTileUrlFunction( + [6, 97, 41], projection); + expect(tileCoord).to.eql([6, 33, 22]); + }); + + it('returns the expected tile coordinate - {wrapX: false}', function() { + var tileSource = new ol.source.TileVector({ + format: new ol.format.TopoJSON(), + tileGrid: ol.tilegrid.createXYZ({ + maxZoom: 19 + }), + wrapX: false + }); + var projection = ol.proj.get('EPSG:3857'); + + var tileCoord = tileSource.getTileCoordForTileUrlFunction( + [6, -31, 41], projection); + expect(tileCoord).to.eql(null); + + tileCoord = tileSource.getTileCoordForTileUrlFunction( + [6, 33, 41], projection); + expect(tileCoord).to.eql([6, 33, 22]); + + tileCoord = tileSource.getTileCoordForTileUrlFunction( + [6, 97, 41], projection); + expect(tileCoord).to.eql(null); + }); + }); + }); goog.require('ol.format.TopoJSON'); +goog.require('ol.proj'); goog.require('ol.source.TileVector');