import Map from '../../../../../src/ol/Map.js'; import Projection from '../../../../../src/ol/proj/Projection.js'; import Source from '../../../../../src/ol/source/Source.js'; import Tile from '../../../../../src/ol/Tile.js'; import TileDebugSource from '../../../../../src/ol/source/TileDebug.js'; import TileGrid from '../../../../../src/ol/tilegrid/TileGrid.js'; import TileLayer from '../../../../../src/ol/layer/Tile.js'; import TileRange from '../../../../../src/ol/TileRange.js'; import TileSource from '../../../../../src/ol/source/Tile.js'; import View from '../../../../../src/ol/View.js'; import {getKeyZXY} from '../../../../../src/ol/tilecoord.js'; import {get as getProjection} from '../../../../../src/ol/proj.js'; /** * Tile source for tests that uses a EPSG:4326 based grid with 4 resolutions and * 256x256 tiles. * * @param {Object} tileStates Lookup of tile key to * tile state. */ class MockTile extends TileSource { constructor(tileStates) { const tileGrid = new TileGrid({ resolutions: [360 / 256, 180 / 256, 90 / 256, 45 / 256], origin: [-180, -180], tileSize: 256, }); super({ projection: getProjection('EPSG:4326'), tileGrid: tileGrid, }); for (const key in tileStates) { this.tileCache.set(key, new Tile(key.split('/'), tileStates[key])); } } } MockTile.prototype.getTile = function (z, x, y) { const key = getKeyZXY(z, x, y); if (this.tileCache.containsKey(key)) { return /** @type {!ol.Tile} */ (this.tileCache.get(key)); } else { const tile = new Tile(key, 0); // IDLE this.tileCache.set(key, tile); return tile; } }; describe('ol/source/Tile', function () { describe('constructor', function () { it('returns a tile source', function () { const source = new TileSource({ projection: getProjection('EPSG:4326'), }); expect(source).to.be.a(Source); expect(source).to.be.a(TileSource); }); it('sets 0 as initial cache size', function () { const source = new TileSource({}); expect(source.tileCache.highWaterMark).to.be(0); }); it('grows the cache', function () { const source = new TileDebugSource(); const layer = new TileLayer({ source: source, }); const target = document.createElement('div'); target.style.width = '100px'; target.style.height = '100px'; document.body.appendChild(target); const map = new Map({ layers: [layer], view: new View({ center: [0, 0], zoom: 2, }), target: target, }); map.renderSync(); expect( source.getTileCacheForProjection(map.getView().getProjection()) .highWaterMark ).to.be(4); map.setTarget(null); document.body.removeChild(target); }); it('sets a custom cache size', function () { const projection = getProjection('EPSG:4326'); const source = new TileSource({ projection: projection, cacheSize: 442, }); expect(source.getTileCacheForProjection(projection).highWaterMark).to.be( 442 ); }); }); describe('#setKey()', function () { it('sets the source key', function () { const source = new TileSource({}); expect(source.getKey()).to.equal(''); const key = 'foo'; source.setKey(key); expect(source.getKey()).to.equal(key); }); }); describe('#getInterpolate()', function () { it('is false by default', function () { const source = new TileSource({}); expect(source.getInterpolate()).to.be(false); }); it('is true if constructed with interpolate: true', function () { const source = new TileSource({interpolate: true}); expect(source.getInterpolate()).to.be(true); }); }); describe('#setKey()', function () { it('dispatches a change event', function (done) { const source = new TileSource({}); const key = 'foo'; source.once('change', function () { done(); }); source.setKey(key); }); it('does not dispatch change if key does not change', function (done) { const source = new TileSource({}); const 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 () { let callback; beforeEach(function () { callback = sinon.spy(); }); it('does not call the callback if no tiles are loaded', function () { const source = new MockTile({}); const grid = source.getTileGrid(); const extent = [-180, -180, 180, 180]; const zoom = 3; const range = grid.getTileRangeForExtentAndZ(extent, zoom); source.forEachLoadedTile(source.getProjection(), zoom, range, callback); expect(callback.callCount).to.be(0); }); it('does not call getTile() if no tiles are loaded', function () { const source = new MockTile({}); sinon.spy(source, 'getTile'); const grid = source.getTileGrid(); const extent = [-180, -180, 180, 180]; const zoom = 3; const range = grid.getTileRangeForExtentAndZ(extent, zoom); source.forEachLoadedTile(source.getProjection(), zoom, range, callback); expect(source.getTile.callCount).to.be(0); source.getTile.restore(); }); it('calls callback for each loaded tile', function () { const source = new MockTile({ '1/0/0': 2, // LOADED '1/0/1': 2, // LOADED '1/1/0': 1, // LOADING, '1/1/1': 2, // LOADED }); const zoom = 1; const range = new TileRange(0, 1, 0, 1); source.forEachLoadedTile(source.getProjection(), zoom, range, callback); expect(callback.callCount).to.be(3); }); it('returns true if range is fully loaded', function () { // a source with no loaded tiles const source = new MockTile({ '1/0/0': 2, // LOADED, '1/0/1': 2, // LOADED, '1/1/0': 2, // LOADED, '1/1/1': 2, // LOADED }); const zoom = 1; const range = new TileRange(0, 1, 0, 1); const covered = source.forEachLoadedTile( source.getProjection(), zoom, range, function () { return true; } ); expect(covered).to.be(true); }); it('returns false if range is not fully loaded', function () { // a source with no loaded tiles const source = new MockTile({ '1/0/0': 2, // LOADED, '1/0/1': 2, // LOADED, '1/1/0': 1, // LOADING, '1/1/1': 2, // LOADED }); const zoom = 1; const range = new TileRange(0, 1, 0, 1); const covered = source.forEachLoadedTile( source.getProjection(), zoom, range, function () { return true; } ); expect(covered).to.be(false); }); it('allows callback to override loaded check', function () { // a source with no loaded tiles const source = new MockTile({ '1/0/0': 2, // LOADED, '1/0/1': 2, // LOADED, '1/1/0': 2, // LOADED, '1/1/1': 2, // LOADED }); const zoom = 1; const range = new TileRange(0, 1, 0, 1); const covered = source.forEachLoadedTile( source.getProjection(), zoom, range, function () { return false; } ); expect(covered).to.be(false); }); }); describe('#getTileCoordForTileUrlFunction()', function () { it('returns the expected tile coordinate - {wrapX: true}', function () { const tileSource = new TileSource({ projection: 'EPSG:3857', wrapX: true, }); let tileCoord = tileSource.getTileCoordForTileUrlFunction([6, -31, 22]); expect(tileCoord).to.eql([6, 33, 22]); tileCoord = tileSource.getTileCoordForTileUrlFunction([6, 33, 22]); expect(tileCoord).to.eql([6, 33, 22]); tileCoord = tileSource.getTileCoordForTileUrlFunction([6, 97, 22]); expect(tileCoord).to.eql([6, 33, 22]); }); it('returns the expected tile coordinate - {wrapX: false}', function () { const tileSource = new TileSource({ projection: 'EPSG:3857', wrapX: false, }); let tileCoord = tileSource.getTileCoordForTileUrlFunction([6, -31, 22]); expect(tileCoord).to.eql(null); tileCoord = tileSource.getTileCoordForTileUrlFunction([6, 33, 22]); expect(tileCoord).to.eql([6, 33, 22]); tileCoord = tileSource.getTileCoordForTileUrlFunction([6, 97, 22]); expect(tileCoord).to.eql(null); }); it('works with wrapX and custom projection without extent', function () { const tileSource = new TileSource({ projection: new Projection({ code: 'foo', global: true, units: 'm', }), wrapX: true, }); const tileCoord = tileSource.getTileCoordForTileUrlFunction([6, -31, 22]); expect(tileCoord).to.eql([6, 33, 22]); }); }); describe('#refresh()', function () { it('checks clearing of internal state', function () { // create a source with one loaded tile const source = new MockTile({ '1/0/0': 2, // LOADED }); // check the loaded tile is there const tile = source.getTile(1, 0, 0); expect(tile).to.be.a(Tile); // check tile cache is filled expect(source.tileCache.getCount()).to.eql(1); // refresh the source source.refresh(); // check tile cache after refresh (should be empty) expect(source.tileCache.getCount()).to.eql(0); }); }); }); describe('MockTile', function () { describe('constructor', function () { it('creates a tile source', function () { const source = new MockTile({}); expect(source).to.be.a(TileSource); expect(source).to.be.a(MockTile); }); }); describe('#getTile()', function () { it('returns a tile with state based on constructor arg', function () { const source = new MockTile({ '0/0/0': 2, // LOADED, '1/0/0': 2, // LOADED }); let tile; // check a loaded tile tile = source.getTile(0, 0, 0); expect(tile).to.be.a(Tile); expect(tile.state).to.be(2); // LOADED // check a tile that is not loaded tile = source.getTile(1, 0, -1); expect(tile).to.be.a(Tile); expect(tile.state).to.be(0); // IDLE // check another loaded tile tile = source.getTile(1, 0, 0); expect(tile).to.be.a(Tile); expect(tile.state).to.be(2); // LOADED }); }); });