diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md
index 7db2980213..5e5e9116bb 100644
--- a/changelog/upgrade-notes.md
+++ b/changelog/upgrade-notes.md
@@ -1,5 +1,9 @@
## Upgrade notes
+#### Rendering change for tile sources
+
+Previously, if you called `source.setUrl()` on a tile source, all currently rendered tiles would be cleared before new tiles were loaded and rendered. This clearing of the map is undesirable if you are trying to smoothly update the tiles used by a source. This behavior has now changed, and calling `source.setUrl()` (or `source.setUrls()`) will *not* clear currently rendered tiles before loading and rendering new tiles. Instead, previously rendered tiles remain rendered until new tiles have loaded and can replace them. If you want to achieve the old behavior (render a blank map before loading new tiles), you can call `source.refresh()` or you can replace the old source with a new one (using `layer.setSource()`).
+
#### Move of typedefs out of code and into separate file
This change should not affect the great majority of application developers, but it's possible there are edge cases when compiling application code together with the library which cause compiler errors or warnings. In this case, please raise a GitHub issue. `goog.require`s for typedefs should not be necessary.
diff --git a/examples/reusable-source.html b/examples/reusable-source.html
new file mode 100644
index 0000000000..e93074b7f6
--- /dev/null
+++ b/examples/reusable-source.html
@@ -0,0 +1,12 @@
+---
+layout: example.html
+title: Reusable Source
+shortdesc: Updating a tile source by changing the URL.
+docs: >
+ You can call source.setUrl() to update the URL for a tile source. Note that when you change the URL for a tile source, existing tiles will not be replaced until new tiles are loaded. If you are interested instead in clearing currently rendered tiles, you can call the source.refresh() method. Alternatively, you can use two separate sources if you want to remove rendered tiles and start over loading new tiles.
+---
+
+
+
+
+
diff --git a/examples/reusable-source.js b/examples/reusable-source.js
new file mode 100644
index 0000000000..b9c4ac3ca3
--- /dev/null
+++ b/examples/reusable-source.js
@@ -0,0 +1,39 @@
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.layer.Tile');
+goog.require('ol.source.XYZ');
+
+var urls = [
+ 'https://{a-c}.tiles.mapbox.com/v3/mapbox.blue-marble-topo-jan/{z}/{x}/{y}.png',
+ 'https://{a-c}.tiles.mapbox.com/v3/mapbox.blue-marble-topo-bathy-jan/{z}/{x}/{y}.png',
+ 'https://{a-c}.tiles.mapbox.com/v3/mapbox.blue-marble-topo-jul/{z}/{x}/{y}.png',
+ 'https://{a-c}.tiles.mapbox.com/v3/mapbox.blue-marble-topo-bathy-jul/{z}/{x}/{y}.png'
+];
+
+var source = new ol.source.XYZ();
+
+var map = new ol.Map({
+ target: 'map',
+ layers: [
+ new ol.layer.Tile({
+ source: source
+ })
+ ],
+ view: new ol.View({
+ center: [0, 0],
+ zoom: 2
+ })
+});
+
+
+function updateUrl(index) {
+ source.setUrl(urls[index]);
+}
+
+var buttons = document.getElementsByClassName('switcher');
+for (var i = 0, ii = buttons.length; i < ii; ++i) {
+ var button = buttons[i];
+ button.addEventListener('click', updateUrl.bind(null, Number(button.value)));
+}
+
+updateUrl(0);
diff --git a/src/ol/source/tileimagesource.js b/src/ol/source/tileimagesource.js
index 5f623d67f4..c552c52068 100644
--- a/src/ol/source/tileimagesource.js
+++ b/src/ol/source/tileimagesource.js
@@ -279,19 +279,19 @@ ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection
ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, projection) {
var /** @type {ol.Tile} */ tile = null;
var tileCoordKey = this.getKeyZXY(z, x, y);
- var paramsKey = this.getKeyParams();
+ var key = this.getKey();
if (!this.tileCache.containsKey(tileCoordKey)) {
goog.asserts.assert(projection, 'argument projection is truthy');
- tile = this.createTile_(z, x, y, pixelRatio, projection, paramsKey);
+ tile = this.createTile_(z, x, y, pixelRatio, projection, key);
this.tileCache.set(tileCoordKey, tile);
} else {
tile = /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
- if (tile.key != paramsKey) {
+ if (tile.key != key) {
// The source's params changed. If the tile has an interim tile and if we
// can use it then we use it. Otherwise we create a new tile. In both
// cases we attempt to assign an interim tile to the new tile.
var /** @type {ol.Tile} */ interimTile = tile;
- if (tile.interimTile && tile.interimTile.key == paramsKey) {
+ if (tile.interimTile && tile.interimTile.key == key) {
goog.asserts.assert(tile.interimTile.getState() == ol.TileState.LOADED);
goog.asserts.assert(tile.interimTile.interimTile === null);
tile = tile.interimTile;
@@ -299,7 +299,7 @@ ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, pr
tile.interimTile = interimTile;
}
} else {
- tile = this.createTile_(z, x, y, pixelRatio, projection, paramsKey);
+ tile = this.createTile_(z, x, y, pixelRatio, projection, key);
if (interimTile.getState() == ol.TileState.LOADED) {
tile.interimTile = interimTile;
} else if (interimTile.interimTile &&
diff --git a/src/ol/source/tilesource.js b/src/ol/source/tilesource.js
index bc0b789691..f397a3f4f5 100644
--- a/src/ol/source/tilesource.js
+++ b/src/ol/source/tilesource.js
@@ -67,6 +67,12 @@ ol.source.Tile = function(options) {
*/
this.tmpSize = [0, 0];
+ /**
+ * @private
+ * @type {string}
+ */
+ this.key_ = '';
+
};
goog.inherits(ol.source.Tile, ol.source.Source);
@@ -138,13 +144,25 @@ ol.source.Tile.prototype.getGutter = function(projection) {
/**
- * Return the "parameters" key, a string composed of the source's
- * parameters/dimensions.
- * @return {string} The parameters key.
+ * Return the key to be used for all tiles in the source.
+ * @return {string} The key for all tiles.
* @protected
*/
-ol.source.Tile.prototype.getKeyParams = function() {
- return '';
+ol.source.Tile.prototype.getKey = function() {
+ return this.key_;
+};
+
+
+/**
+ * Set the value to be used as the key for all tiles in the source.
+ * @param {string} key The key for tiles.
+ * @protected
+ */
+ol.source.Tile.prototype.setKey = function(key) {
+ if (this.key_ !== key) {
+ this.key_ = key;
+ this.changed();
+ }
};
diff --git a/src/ol/source/tilewmssource.js b/src/ol/source/tilewmssource.js
index c74505442d..3d43f097c9 100644
--- a/src/ol/source/tilewmssource.js
+++ b/src/ol/source/tilewmssource.js
@@ -63,13 +63,6 @@ ol.source.TileWMS = function(opt_options) {
*/
this.params_ = params;
- /**
- * @private
- * @type {string}
- */
- this.paramsKey_ = '';
- this.resetParamsKey_();
-
/**
* @private
* @type {boolean}
@@ -103,6 +96,7 @@ ol.source.TileWMS = function(opt_options) {
this.tmpExtent_ = ol.extent.createEmpty();
this.updateV13_();
+ this.setKey(this.getKeyForParams_());
};
goog.inherits(ol.source.TileWMS, ol.source.TileImage);
@@ -182,14 +176,6 @@ ol.source.TileWMS.prototype.getGutterInternal = function() {
};
-/**
- * @inheritDoc
- */
-ol.source.TileWMS.prototype.getKeyParams = function() {
- return this.paramsKey_;
-};
-
-
/**
* @inheritDoc
*/
@@ -311,14 +297,15 @@ ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() {
/**
* @private
+ * @return {string} The key for the current params.
*/
-ol.source.TileWMS.prototype.resetParamsKey_ = function() {
+ol.source.TileWMS.prototype.getKeyForParams_ = function() {
var i = 0;
var res = [];
for (var key in this.params_) {
res[i++] = key + '-' + this.params_[key];
}
- this.paramsKey_ = res.join('/');
+ return res.join('/');
};
@@ -378,9 +365,8 @@ ol.source.TileWMS.prototype.fixedTileUrlFunction = function(tileCoord, pixelRati
ol.source.TileWMS.prototype.updateParams = function(params) {
ol.object.assign(this.params_, params);
this.resetCoordKeyPrefix_();
- this.resetParamsKey_();
this.updateV13_();
- this.changed();
+ this.setKey(this.getKeyForParams_());
};
diff --git a/src/ol/source/urltilesource.js b/src/ol/source/urltilesource.js
index 73a10b6a24..fd359b1c29 100644
--- a/src/ol/source/urltilesource.js
+++ b/src/ol/source/urltilesource.js
@@ -143,15 +143,16 @@ ol.source.UrlTile.prototype.setTileLoadFunction = function(tileLoadFunction) {
/**
* Set the tile URL function of the source.
* @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
+ * @param {string=} opt_key Optional new tile key for the source.
* @api
*/
-ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction) {
- // FIXME It should be possible to be more intelligent and avoid clearing the
- // FIXME cache. The tile URL function would need to be incorporated into the
- // FIXME cache key somehow.
- this.tileCache.clear();
+ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction, opt_key) {
this.tileUrlFunction = tileUrlFunction;
- this.changed();
+ if (typeof opt_key !== 'undefined') {
+ this.setKey(opt_key);
+ } else {
+ this.changed();
+ }
};
@@ -164,7 +165,7 @@ ol.source.UrlTile.prototype.setUrl = function(url) {
var urls = this.urls = ol.TileUrlFunction.expandUrl(url);
this.setTileUrlFunction(this.fixedTileUrlFunction ?
this.fixedTileUrlFunction.bind(this) :
- ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid));
+ ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), url);
};
@@ -175,9 +176,10 @@ ol.source.UrlTile.prototype.setUrl = function(url) {
*/
ol.source.UrlTile.prototype.setUrls = function(urls) {
this.urls = urls;
+ var key = urls.join('\n');
this.setTileUrlFunction(this.fixedTileUrlFunction ?
this.fixedTileUrlFunction.bind(this) :
- ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid));
+ ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), key);
};
diff --git a/src/ol/source/wmtssource.js b/src/ol/source/wmtssource.js
index 017e6f1077..d247d4615f 100644
--- a/src/ol/source/wmtssource.js
+++ b/src/ol/source/wmtssource.js
@@ -54,13 +54,6 @@ ol.source.WMTS = function(options) {
*/
this.dimensions_ = options.dimensions !== undefined ? options.dimensions : {};
- /**
- * @private
- * @type {string}
- */
- this.dimensionsKey_ = '';
- this.resetDimensionsKey_();
-
/**
* @private
* @type {string}
@@ -187,6 +180,8 @@ ol.source.WMTS = function(options) {
wrapX: options.wrapX !== undefined ? options.wrapX : false
});
+ this.setKey(this.getKeyForDimensions_());
+
};
goog.inherits(ol.source.WMTS, ol.source.TileImage);
@@ -213,14 +208,6 @@ ol.source.WMTS.prototype.getFormat = function() {
};
-/**
- * @inheritDoc
- */
-ol.source.WMTS.prototype.getKeyParams = function() {
- return this.dimensionsKey_;
-};
-
-
/**
* Return the layer of the WMTS source.
* @return {string} Layer.
@@ -273,14 +260,15 @@ ol.source.WMTS.prototype.getVersion = function() {
/**
* @private
+ * @return {string} The key for the current dimensions.
*/
-ol.source.WMTS.prototype.resetDimensionsKey_ = function() {
+ol.source.WMTS.prototype.getKeyForDimensions_ = function() {
var i = 0;
var res = [];
for (var key in this.dimensions_) {
res[i++] = key + '-' + this.dimensions_[key];
}
- this.dimensionsKey_ = res.join('/');
+ return res.join('/');
};
@@ -291,8 +279,7 @@ ol.source.WMTS.prototype.resetDimensionsKey_ = function() {
*/
ol.source.WMTS.prototype.updateDimensions = function(dimensions) {
ol.object.assign(this.dimensions_, dimensions);
- this.resetDimensionsKey_();
- this.changed();
+ this.setKey(this.getKeyForDimensions_());
};
diff --git a/src/ol/source/xyzsource.js b/src/ol/source/xyzsource.js
index 166df55699..1091ada585 100644
--- a/src/ol/source/xyzsource.js
+++ b/src/ol/source/xyzsource.js
@@ -22,10 +22,11 @@ goog.require('ol.source.TileImage');
*
* @constructor
* @extends {ol.source.TileImage}
- * @param {olx.source.XYZOptions} options XYZ options.
+ * @param {olx.source.XYZOptions=} opt_options XYZ options.
* @api stable
*/
-ol.source.XYZ = function(options) {
+ol.source.XYZ = function(opt_options) {
+ var options = opt_options || {};
var projection = options.projection !== undefined ?
options.projection : 'EPSG:3857';
diff --git a/test/spec/ol/source/tileimagesource.test.js b/test/spec/ol/source/tileimagesource.test.js
index 7d5cd3a12a..e79bbd7152 100644
--- a/test/spec/ol/source/tileimagesource.test.js
+++ b/test/spec/ol/source/tileimagesource.test.js
@@ -27,7 +27,7 @@ describe('ol.source.TileImage', function() {
beforeEach(function() {
source = createSource();
- expect(source.getKeyParams()).to.be('');
+ expect(source.getKey()).to.be('');
source.getTileInternal(0, 0, -1, 1, ol.proj.get('EPSG:3857'));
expect(source.tileCache.getCount()).to.be(1);
tile = source.tileCache.get(source.getKeyZXY(0, 0, -1));
@@ -43,7 +43,7 @@ describe('ol.source.TileImage', function() {
describe('tile is not loaded', function() {
it('returns a tile with no interim tile', function() {
- source.getKeyParams = function() {
+ source.getKey = function() {
return 'key0';
};
var returnedTile = source.getTileInternal(
@@ -56,7 +56,7 @@ describe('ol.source.TileImage', function() {
describe('tile is loaded', function() {
it('returns a tile with interim tile', function() {
- source.getKeyParams = function() {
+ source.getKey = function() {
return 'key0';
};
tile.state = ol.TileState.LOADED;
@@ -71,7 +71,7 @@ describe('ol.source.TileImage', function() {
describe('tile is not loaded but interim tile is', function() {
it('returns a tile with interim tile', function() {
var dynamicParamsKey, returnedTile;
- source.getKeyParams = function() {
+ source.getKey = function() {
return dynamicParamsKey;
};
dynamicParamsKey = 'key0';
diff --git a/test/spec/ol/source/tilesource.test.js b/test/spec/ol/source/tilesource.test.js
index 779b829dee..110524dcfe 100644
--- a/test/spec/ol/source/tilesource.test.js
+++ b/test/spec/ol/source/tilesource.test.js
@@ -12,6 +12,47 @@ describe('ol.source.Tile', function() {
});
});
+ describe('#setKey()', function() {
+ it('sets the source key', function() {
+ var source = new ol.source.Tile({});
+ expect(source.getKey()).to.equal('');
+
+ var key = 'foo';
+ source.setKey(key);
+ expect(source.getKey()).to.equal(key);
+ });
+ });
+
+ describe('#setKey()', function() {
+ it('dispatches a change event', function(done) {
+ var source = new ol.source.Tile({});
+
+ var key = 'foo';
+ source.once('change', function() {
+ done();
+ });
+ source.setKey(key);
+ });
+
+ it('does not dispatch change if key does not change', function(done) {
+ var source = new ol.source.Tile({});
+
+ var key = 'foo';
+ source.once('change', function() {
+ source.once('change', function() {
+ done(new Error('Unexpected change event after source.setKey()'));
+ });
+ setTimeout(function() {
+ done();
+ }, 10);
+ source.setKey(key); // this should not result in a change event
+ });
+
+ source.setKey(key); // this should result in a change event
+ });
+
+ });
+
describe('#forEachLoadedTile()', function() {
var callback;
diff --git a/test/spec/ol/source/urltilesource.test.js b/test/spec/ol/source/urltilesource.test.js
index edeb89ad8c..d4de378da5 100644
--- a/test/spec/ol/source/urltilesource.test.js
+++ b/test/spec/ol/source/urltilesource.test.js
@@ -3,6 +3,54 @@ goog.provide('ol.test.source.UrlTile');
describe('ol.source.UrlTile', function() {
+ describe('#setUrl()', function() {
+ it('sets the URL for the source', function() {
+ var source = new ol.source.UrlTile({});
+
+ var url = 'https://example.com/';
+ source.setUrl(url);
+
+ expect(source.getUrls()).to.eql([url]);
+ });
+
+ it('updates the key for the source', function() {
+ var source = new ol.source.UrlTile({});
+
+ var url = 'https://example.com/';
+ source.setUrl(url);
+
+ expect(source.getKey()).to.eql(url);
+ });
+ });
+
+ describe('#setUrls()', function() {
+ it('sets the URL for the source', function() {
+ var source = new ol.source.UrlTile({});
+
+ var urls = [
+ 'https://a.example.com/',
+ 'https://b.example.com/',
+ 'https://c.example.com/'
+ ];
+ source.setUrls(urls);
+
+ expect(source.getUrls()).to.eql(urls);
+ });
+
+ it('updates the key for the source', function() {
+ var source = new ol.source.UrlTile({});
+
+ var urls = [
+ 'https://a.example.com/',
+ 'https://b.example.com/',
+ 'https://c.example.com/'
+ ];
+ source.setUrls(urls);
+
+ expect(source.getKey()).to.eql(urls.join('\n'));
+ });
+ });
+
describe('url option', function() {
it('expands url template', function() {
var tileSource = new ol.source.UrlTile({
diff --git a/test/spec/ol/source/xyzsource.test.js b/test/spec/ol/source/xyzsource.test.js
index f260635dab..232ff43131 100644
--- a/test/spec/ol/source/xyzsource.test.js
+++ b/test/spec/ol/source/xyzsource.test.js
@@ -5,6 +5,14 @@ describe('ol.source.XYZ', function() {
describe('constructor', function() {
+ it('can be constructed without options', function() {
+ var source = new ol.source.XYZ();
+ expect(source).to.be.an(ol.source.XYZ);
+ expect(source).to.be.an(ol.source.TileImage);
+ expect(source).to.be.an(ol.source.UrlTile);
+ expect(source).to.be.an(ol.source.Tile);
+ });
+
it('can be constructed with a custom tile grid', function() {
var tileGrid = ol.tilegrid.createXYZ();
var tileSource = new ol.source.XYZ({
@@ -179,4 +187,7 @@ describe('ol.source.XYZ', function() {
});
+goog.require('ol.source.Tile');
+goog.require('ol.source.TileImage');
+goog.require('ol.source.UrlTile');
goog.require('ol.source.XYZ');