Merge pull request #5364 from tschaub/less-flashy

Make it so things don't flash so much when you change the tile source URL.
This commit is contained in:
Tim Schaub
2016-05-19 09:26:42 -06:00
13 changed files with 211 additions and 62 deletions

View File

@@ -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.

View File

@@ -0,0 +1,12 @@
---
layout: example.html
title: Reusable Source
shortdesc: Updating a tile source by changing the URL.
docs: >
You can call <code>source.setUrl()</code> 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 <code>source.refresh()</code> method. Alternatively, you can use two separate sources if you want to remove rendered tiles and start over loading new tiles.
---
<div id="map" class="map"></div>
<button class="switcher" value="0">January</button>
<button class="switcher" value="1">January (with bathymetry)</button>
<button class="switcher" value="2">July</button>
<button class="switcher" value="3">July (with bathymetry)</button>

View File

@@ -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);

View File

@@ -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 &&

View File

@@ -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();
}
};

View File

@@ -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_());
};

View File

@@ -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);
};

View File

@@ -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_());
};

View File

@@ -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';

View File

@@ -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';

View File

@@ -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;

View File

@@ -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({

View File

@@ -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');