Merge pull request #13212 from ahocevar/multisource-webgl

Support multiple sources for WebGL tile layers
This commit is contained in:
Andreas Hocevar
2022-01-19 08:43:22 +01:00
committed by GitHub
20 changed files with 345 additions and 68 deletions
+3 -3
View File
@@ -4,9 +4,9 @@ title: GeoTIFF tile pyramid
shortdesc: Rendering a COG tile pyramid as layer group. shortdesc: Rendering a COG tile pyramid as layer group.
docs: > docs: >
Data from a Cloud Optimized GeoTIFF (COG) tile pyramid can be rendered as a set of layers. In this Data from a Cloud Optimized GeoTIFF (COG) tile pyramid can be rendered as a set of layers. In this
example, a pyramid of 3-band GeoTIFFs is used to render RGB data. For each tile of the pyramid, a example, a pyramid of 3-band GeoTIFFs is used to render RGB data. The `ol/source.sourcesFromTileGrid`
separate layer is created on demand. The lowest resolution layer serves as preview while higher resolutions are helper function creates sources from this pyramid on demand. The GeoTIFFs used by those sources have
loading. a resolution range that matches the range of a single z of the pyramid tile grid.
tags: "cog, tilepyramid, stac" tags: "cog, tilepyramid, stac"
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>
+14 -39
View File
@@ -1,16 +1,15 @@
import GeoTIFF from '../src/ol/source/GeoTIFF.js'; import GeoTIFF from '../src/ol/source/GeoTIFF.js';
import LayerGroup from '../src/ol/layer/Group.js';
import Map from '../src/ol/Map.js'; import Map from '../src/ol/Map.js';
import TileGrid from '../src/ol/tilegrid/TileGrid.js'; import TileGrid from '../src/ol/tilegrid/TileGrid.js';
import View from '../src/ol/View.js'; import View from '../src/ol/View.js';
import WebGLTileLayer from '../src/ol/layer/WebGLTile.js'; import WebGLTileLayer from '../src/ol/layer/WebGLTile.js';
import {getIntersection} from '../src/ol/extent.js'; import {sourcesFromTileGrid} from '../src/ol/source.js';
// Metadata from https://s2downloads.eox.at/demo/EOxCloudless/2019/rgb/2019_EOxCloudless_rgb.json // Metadata from https://s2downloads.eox.at/demo/EOxCloudless/2019/rgb/2019_EOxCloudless_rgb.json
// Tile grid of the GeoTIFF pyramid layout // Tile grid of the GeoTIFF pyramid layout
const tileGrid = new TileGrid({ const tileGrid = new TileGrid({
origin: [-180, 90], extent: [-180, -90, 180, 90],
resolutions: [0.703125, 0.3515625, 0.17578125, 8.7890625e-2, 4.39453125e-2], resolutions: [0.703125, 0.3515625, 0.17578125, 8.7890625e-2, 4.39453125e-2],
tileSizes: [ tileSizes: [
[512, 256], [512, 256],
@@ -21,30 +20,19 @@ const tileGrid = new TileGrid({
], ],
}); });
const pyramid = new LayerGroup(); const pyramid = new WebGLTileLayer({
const layerForUrl = {}; sources: sourcesFromTileGrid(
const zs = tileGrid.getResolutions().length; tileGrid,
([z, x, y]) =>
function useLayer(z, x, y) { new GeoTIFF({
const url = `https://s2downloads.eox.at/demo/EOxCloudless/2019/rgb/${z}/${y}/${x}.tif`; sources: [
if (!(url in layerForUrl)) { {
pyramid.getLayers().push( url: `https://s2downloads.eox.at/demo/EOxCloudless/2019/rgb/${z}/${y}/${x}.tif`,
new WebGLTileLayer({ },
minZoom: z, ],
maxZoom: z === 0 || z === zs - 1 ? undefined : z + 1,
extent: tileGrid.getTileCoordExtent([z, x, y]),
source: new GeoTIFF({
sources: [
{
url: url,
},
],
}),
}) })
); ),
layerForUrl[url] = true; });
}
}
const map = new Map({ const map = new Map({
target: 'map', target: 'map',
@@ -56,16 +44,3 @@ const map = new Map({
showFullExtent: true, showFullExtent: true,
}), }),
}); });
// Add overview layer
useLayer(0, 0, 0);
// Add layer for specific extent on demand
map.on('moveend', () => {
const view = map.getView();
tileGrid.forEachTileCoord(
getIntersection([-180, -90, 180, 90], view.calculateExtent()),
tileGrid.getZForResolution(view.getResolution()),
([z, x, y]) => useLayer(z, x, y)
);
});
-1
View File
@@ -163,7 +163,6 @@ class BaseLayer extends BaseObject {
}); });
const zIndex = this.getZIndex(); const zIndex = this.getZIndex();
state.opacity = clamp(Math.round(this.getOpacity() * 100) / 100, 0, 1); state.opacity = clamp(Math.round(this.getOpacity() * 100) / 100, 0, 1);
state.sourceState = this.getSourceState();
state.visible = this.getVisible(); state.visible = this.getVisible();
state.extent = this.getExtent(); state.extent = this.getExtent();
state.zIndex = zIndex === undefined && !state.managed ? Infinity : zIndex; state.zIndex = zIndex === undefined && !state.managed ? Infinity : zIndex;
+8 -1
View File
@@ -57,7 +57,7 @@ import {listen, unlistenByKey} from '../events.js';
* @typedef {Object} State * @typedef {Object} State
* @property {import("./Layer.js").default} layer Layer. * @property {import("./Layer.js").default} layer Layer.
* @property {number} opacity Opacity, the value is rounded to two digits to appear after the decimal point. * @property {number} opacity Opacity, the value is rounded to two digits to appear after the decimal point.
* @property {import("../source/State.js").default} sourceState SourceState. * @property {import("../source/Source.js").default|undefined} source Source being rendered (only for multi-source layers).
* @property {boolean} visible Visible. * @property {boolean} visible Visible.
* @property {boolean} managed Managed. * @property {boolean} managed Managed.
* @property {import("../extent.js").Extent} [extent] Extent. * @property {import("../extent.js").Extent} [extent] Extent.
@@ -196,6 +196,13 @@ class Layer extends BaseLayer {
return /** @type {SourceType} */ (this.get(LayerProperty.SOURCE)) || null; return /** @type {SourceType} */ (this.get(LayerProperty.SOURCE)) || null;
} }
/**
* @return {SourceType} The source being rendered.
*/
getRenderSource() {
return this.getSource();
}
/** /**
* @return {import("../source/State.js").default} Source state. * @return {import("../source/State.js").default} Source state.
*/ */
+113
View File
@@ -3,6 +3,7 @@
*/ */
import BaseTileLayer from './BaseTile.js'; import BaseTileLayer from './BaseTile.js';
import LayerProperty from '../layer/Property.js'; import LayerProperty from '../layer/Property.js';
import SourceState from '../source/State.js';
import WebGLTileLayerRenderer, { import WebGLTileLayerRenderer, {
Attributes, Attributes,
Uniforms, Uniforms,
@@ -64,6 +65,11 @@ import {assign} from '../obj.js';
* @property {number} [preload=0] Preload. Load low-resolution tiles up to `preload` levels. `0` * @property {number} [preload=0] Preload. Load low-resolution tiles up to `preload` levels. `0`
* means no preloading. * means no preloading.
* @property {SourceType} [source] Source for this layer. * @property {SourceType} [source] Source for this layer.
* @property {Array<SourceType>|function(import("../extent.js").Extent, number):Array<SourceType>} [sources] Array
* of sources for this layer. Takes precedence over `source`. Can either be an array of sources, or a function that
* expects an extent and a resolution (in view projection units per pixel) and returns an array of sources. See
* {@link module:ol/source.sourcesFromTileGrid} for a helper function to generate sources that are organized in a
* pyramid following the same pattern as a tile grid.
* @property {import("../PluggableMap.js").default} [map] Sets the layer as overlay on a map. The map will not manage * @property {import("../PluggableMap.js").default} [map] Sets the layer as overlay on a map. The map will not manage
* this layer in its layers collection, and the layer will be rendered on top. This is useful for * this layer in its layers collection, and the layer will be rendered on top. This is useful for
* temporary layers. The standard way to add a layer to a map and have it managed by the map is to * temporary layers. The standard way to add a layer to a map and have it managed by the map is to
@@ -291,6 +297,18 @@ class WebGLTileLayer extends BaseTileLayer {
super(options); super(options);
/**
* @type {Array<SourceType>|function(import("../extent.js").Extent, number):Array<SourceType>}
* @private
*/
this.sources_ = options.sources;
/**
* @type {number}
* @private
*/
this.renderedResolution_ = NaN;
/** /**
* @type {Style} * @type {Style}
* @private * @private
@@ -312,6 +330,41 @@ class WebGLTileLayer extends BaseTileLayer {
this.addChangeListener(LayerProperty.SOURCE, this.handleSourceUpdate_); this.addChangeListener(LayerProperty.SOURCE, this.handleSourceUpdate_);
} }
/**
* Gets the sources for this layer, for a given extent and resolution.
* @param {import("../extent.js").Extent} extent Extent.
* @param {number} resolution Resolution.
* @return {Array<SourceType>} Sources.
*/
getSources(extent, resolution) {
const source = this.getSource();
return this.sources_
? typeof this.sources_ === 'function'
? this.sources_(extent, resolution)
: this.sources_
: source
? [source]
: [];
}
/**
* @return {SourceType} The source being rendered.
*/
getRenderSource() {
return (
/** @type {SourceType} */ (this.getLayerState().source) ||
this.getSource()
);
}
/**
* @return {import("../source/State.js").default} Source state.
*/
getSourceState() {
const source = this.getRenderSource();
return source ? source.getState() : SourceState.UNDEFINED;
}
/** /**
* @private * @private
*/ */
@@ -340,6 +393,66 @@ class WebGLTileLayer extends BaseTileLayer {
}); });
} }
/**
* @param {import("../PluggableMap").FrameState} frameState Frame state.
* @param {Array<SourceType>} sources Sources.
* @return {HTMLElement} Canvas.
*/
renderSources(frameState, sources) {
const layerRenderer = this.getRenderer();
let canvas;
for (let i = 0, ii = sources.length; i < ii; ++i) {
this.getLayerState().source = sources[i];
if (layerRenderer.prepareFrame(frameState)) {
canvas = layerRenderer.renderFrame(frameState);
}
}
return canvas;
}
/**
* @param {?import("../PluggableMap.js").FrameState} frameState Frame state.
* @param {HTMLElement} target Target which the renderer may (but need not) use
* for rendering its content.
* @return {HTMLElement} The rendered element.
*/
render(frameState, target) {
const viewState = frameState.viewState;
const sources = this.getSources(frameState.extent, viewState.resolution);
let ready = true;
for (let i = 0, ii = sources.length; i < ii; ++i) {
const source = sources[i];
const sourceState = source.getState();
if (sourceState == SourceState.LOADING) {
const onChange = () => {
if (source.getState() == SourceState.READY) {
source.removeEventListener('change', onChange);
this.changed();
}
};
source.addEventListener('change', onChange);
}
ready = ready && sourceState == SourceState.READY;
}
const canvas = this.renderSources(frameState, sources);
if (this.getRenderer().renderComplete && ready) {
// Fully rendered, done.
this.renderedResolution_ = viewState.resolution;
return canvas;
}
// Render sources from previously fully rendered frames
if (this.renderedResolution_ > 0.5 * viewState.resolution) {
const altSources = this.getSources(
frameState.extent,
this.renderedResolution_
).filter((source) => !sources.includes(source));
if (altSources.length > 0) {
return this.renderSources(frameState, altSources);
}
}
return canvas;
}
/** /**
* Update the layer style. The `updateStyleVariables` function is a more efficient * Update the layer style. The `updateStyleVariables` function is a more efficient
* way to update layer rendering. In cases where the whole style needs to be updated, * way to update layer rendering. In cases where the whole style needs to be updated,
+5 -3
View File
@@ -110,15 +110,17 @@ class CompositeMapRenderer extends MapRenderer {
for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) { for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) {
const layerState = layerStatesArray[i]; const layerState = layerStatesArray[i];
frameState.layerIndex = i; frameState.layerIndex = i;
const layer = layerState.layer;
const sourceState = layer.getSourceState();
if ( if (
!inView(layerState, viewState) || !inView(layerState, viewState) ||
(layerState.sourceState != SourceState.READY && (sourceState != SourceState.READY &&
layerState.sourceState != SourceState.UNDEFINED) sourceState != SourceState.UNDEFINED)
) { ) {
continue; continue;
} }
const layer = layerState.layer;
const element = layer.render(frameState, previousElement); const element = layer.render(frameState, previousElement);
if (!element) { if (!element) {
continue; continue;
+1 -1
View File
@@ -161,7 +161,7 @@ class WebGLLayerRenderer extends LayerRenderer {
* @return {boolean} Layer is ready to be rendered. * @return {boolean} Layer is ready to be rendered.
*/ */
prepareFrame(frameState) { prepareFrame(frameState) {
if (this.getLayer().getSource()) { if (this.getLayer().getRenderSource()) {
let incrementGroup = true; let incrementGroup = true;
let groupNumber = -1; let groupNumber = -1;
let className; let className;
+22 -10
View File
@@ -20,7 +20,6 @@ import {
} from '../../vec/mat4.js'; } from '../../vec/mat4.js';
import { import {
createOrUpdate as createTileCoord, createOrUpdate as createTileCoord,
getKeyZXY,
getKey as getTileCoordKey, getKey as getTileCoordKey,
} from '../../tilecoord.js'; } from '../../tilecoord.js';
import {fromUserExtent} from '../../proj.js'; import {fromUserExtent} from '../../proj.js';
@@ -98,7 +97,7 @@ function getRenderExtent(frameState, extent) {
); );
} }
const source = const source =
/** {import("../../source/Tile.js").default} */ layerState.layer.getSource(); /** {import("../../source/Tile.js").default} */ layerState.layer.getRenderSource();
if (!source.getWrapX()) { if (!source.getWrapX()) {
const gridExtent = source const gridExtent = source
.getTileGridForProjection(frameState.viewState.projection) .getTileGridForProjection(frameState.viewState.projection)
@@ -110,6 +109,10 @@ function getRenderExtent(frameState, extent) {
return extent; return extent;
} }
function getCacheKey(source, tileCoord) {
return `${source.getKey()},${getTileCoordKey(tileCoord)}`;
}
/** /**
* @typedef {Object} Options * @typedef {Object} Options
* @property {string} vertexShader Vertex shader source. * @property {string} vertexShader Vertex shader source.
@@ -140,6 +143,12 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
uniforms: options.uniforms, uniforms: options.uniforms,
}); });
/**
* The last call to `renderFrame` was completed with all tiles loaded
* @type {boolean}
*/
this.renderComplete = false;
/** /**
* This transform converts tile i, j coordinates to screen coordinates. * This transform converts tile i, j coordinates to screen coordinates.
* @type {import("../../transform.js").Transform} * @type {import("../../transform.js").Transform}
@@ -273,7 +282,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
*/ */
prepareFrameInternal(frameState) { prepareFrameInternal(frameState) {
const layer = this.getLayer(); const layer = this.getLayer();
const source = layer.getSource(); const source = layer.getRenderSource();
if (!source) { if (!source) {
return false; return false;
} }
@@ -293,7 +302,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
enqueueTiles(frameState, extent, z, tileTexturesByZ) { enqueueTiles(frameState, extent, z, tileTexturesByZ) {
const viewState = frameState.viewState; const viewState = frameState.viewState;
const tileLayer = this.getLayer(); const tileLayer = this.getLayer();
const tileSource = tileLayer.getSource(); const tileSource = tileLayer.getRenderSource();
const tileGrid = tileSource.getTileGridForProjection(viewState.projection); const tileGrid = tileSource.getTileGridForProjection(viewState.projection);
const tileTextureCache = this.tileTextureCache_; const tileTextureCache = this.tileTextureCache_;
const tileRange = tileGrid.getTileRangeForExtentAndZ( const tileRange = tileGrid.getTileRangeForExtentAndZ(
@@ -314,7 +323,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
for (let x = tileRange.minX; x <= tileRange.maxX; ++x) { for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (let y = tileRange.minY; y <= tileRange.maxY; ++y) { for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
const tileCoord = createTileCoord(z, x, y, this.tempTileCoord_); const tileCoord = createTileCoord(z, x, y, this.tempTileCoord_);
const tileCoordKey = getTileCoordKey(tileCoord); const cacheKey = getCacheKey(tileSource, tileCoord);
/** /**
* @type {TileTexture} * @type {TileTexture}
@@ -326,8 +335,8 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
*/ */
let tile; let tile;
if (tileTextureCache.containsKey(tileCoordKey)) { if (tileTextureCache.containsKey(cacheKey)) {
tileTexture = tileTextureCache.get(tileCoordKey); tileTexture = tileTextureCache.get(cacheKey);
tile = tileTexture.tile; tile = tileTexture.tile;
} }
if (!tileTexture || tileTexture.tile.key !== tileSource.getKey()) { if (!tileTexture || tileTexture.tile.key !== tileSource.getKey()) {
@@ -340,7 +349,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
); );
if (!tileTexture) { if (!tileTexture) {
tileTexture = new TileTexture(tile, tileGrid, this.helper); tileTexture = new TileTexture(tile, tileGrid, this.helper);
tileTextureCache.set(tileCoordKey, tileTexture); tileTextureCache.set(cacheKey, tileTexture);
} else { } else {
if (this.isDrawableTile_(tile)) { if (this.isDrawableTile_(tile)) {
tileTexture.setTile(tile); tileTexture.setTile(tile);
@@ -379,12 +388,13 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
* @return {HTMLElement} The rendered element. * @return {HTMLElement} The rendered element.
*/ */
renderFrame(frameState) { renderFrame(frameState) {
this.renderComplete = true;
const gl = this.helper.getGL(); const gl = this.helper.getGL();
this.preRender(gl, frameState); this.preRender(gl, frameState);
const viewState = frameState.viewState; const viewState = frameState.viewState;
const tileLayer = this.getLayer(); const tileLayer = this.getLayer();
const tileSource = tileLayer.getSource(); const tileSource = tileLayer.getRenderSource();
const tileGrid = tileSource.getTileGridForProjection(viewState.projection); const tileGrid = tileSource.getTileGridForProjection(viewState.projection);
const extent = getRenderExtent(frameState, frameState.extent); const extent = getRenderExtent(frameState, frameState.extent);
const z = tileGrid.getZForResolution( const z = tileGrid.getZForResolution(
@@ -438,6 +448,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
const tileCoordKey = getTileCoordKey(tileCoord); const tileCoordKey = getTileCoordKey(tileCoord);
alphaLookup[tileCoordKey] = alpha; alphaLookup[tileCoordKey] = alpha;
} }
this.renderComplete = false;
// first look for child tiles (at z + 1) // first look for child tiles (at z + 1)
const coveredByChildren = this.findAltTiles_( const coveredByChildren = this.findAltTiles_(
@@ -635,9 +646,10 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
let covered = true; let covered = true;
const tileTextureCache = this.tileTextureCache_; const tileTextureCache = this.tileTextureCache_;
const source = this.getLayer().getRenderSource();
for (let x = tileRange.minX; x <= tileRange.maxX; ++x) { for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (let y = tileRange.minY; y <= tileRange.maxY; ++y) { for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
const cacheKey = getKeyZXY(altZ, x, y); const cacheKey = getCacheKey(source, [altZ, x, y]);
let loaded = false; let loaded = false;
if (tileTextureCache.containsKey(cacheKey)) { if (tileTextureCache.containsKey(cacheKey)) {
const tileTexture = tileTextureCache.get(cacheKey); const tileTexture = tileTextureCache.get(cacheKey);
+37
View File
@@ -2,6 +2,9 @@
* @module ol/source * @module ol/source
*/ */
import LRUCache from './structs/LRUCache.js';
import {getIntersection} from './extent.js';
export {default as BingMaps} from './source/BingMaps.js'; export {default as BingMaps} from './source/BingMaps.js';
export {default as CartoDB} from './source/CartoDB.js'; export {default as CartoDB} from './source/CartoDB.js';
export {default as Cluster} from './source/Cluster.js'; export {default as Cluster} from './source/Cluster.js';
@@ -31,3 +34,37 @@ export {default as VectorTile} from './source/VectorTile.js';
export {default as WMTS} from './source/WMTS.js'; export {default as WMTS} from './source/WMTS.js';
export {default as XYZ} from './source/XYZ.js'; export {default as XYZ} from './source/XYZ.js';
export {default as Zoomify} from './source/Zoomify.js'; export {default as Zoomify} from './source/Zoomify.js';
/**
* Creates a sources function from a tile grid. This function can be used as value for the
* `sources` property of the {@link module:ol/layer/Layer~Layer} subclasses that support it.
* @param {import("./tilegrid/TileGrid.js").default} tileGrid Tile grid.
* @param {function(import("./tilecoord.js").TileCoord): import("./source/Source.js").default} factory Source factory.
* This function takes a {@link module:ol/tilecoord~TileCoord} as argument and is expected to return a
* {@link module:ol/source/Source~Source}. **Note**: The returned sources should have a tile grid with
* a limited set of resolutions, matching the resolution range of a single zoom level of the pyramid
* `tileGrid` that `createFromTileGrid` was called with.
* @return {function(import("./extent.js").Extent, number): Array<import("./source/Source.js").default>} Sources function.
* @api
*/
export function sourcesFromTileGrid(tileGrid, factory) {
const sourceCache = new LRUCache(32);
const tileGridExtent = tileGrid.getExtent();
return function (extent, resolution) {
sourceCache.expireCache();
if (tileGridExtent) {
extent = getIntersection(tileGridExtent, extent);
}
const z = tileGrid.getZForResolution(resolution);
const wantedSources = [];
tileGrid.forEachTileCoord(extent, z, (tileCoord) => {
const key = tileCoord.toString();
if (!sourceCache.containsKey(key)) {
const source = factory(tileCoord);
sourceCache.set(key, source);
}
wantedSources.push(sourceCache.get(key));
});
return wantedSources;
};
}
+10
View File
@@ -66,6 +66,16 @@ class LRUCache {
return this.highWaterMark > 0 && this.getCount() > this.highWaterMark; return this.highWaterMark > 0 && this.getCount() > this.highWaterMark;
} }
/**
* Expire the cache.
* @param {!Object<string, boolean>} [keep] Keys to keep. To be implemented by subclasses.
*/
expireCache(keep) {
while (this.canExpireCache()) {
this.pop();
}
}
/** /**
* FIXME empty description for jsdoc * FIXME empty description for jsdoc
*/ */
-7
View File
@@ -44,7 +44,6 @@ describe('ol/layer/Group', function () {
opacity: 1, opacity: 1,
visible: true, visible: true,
managed: true, managed: true,
sourceState: 'ready',
extent: undefined, extent: undefined,
zIndex: undefined, zIndex: undefined,
maxResolution: Infinity, maxResolution: Infinity,
@@ -161,7 +160,6 @@ describe('ol/layer/Group', function () {
opacity: 0.5, opacity: 0.5,
visible: false, visible: false,
managed: true, managed: true,
sourceState: 'ready',
extent: undefined, extent: undefined,
zIndex: 10, zIndex: 10,
maxResolution: 500, maxResolution: 500,
@@ -203,7 +201,6 @@ describe('ol/layer/Group', function () {
opacity: 0.5, opacity: 0.5,
visible: false, visible: false,
managed: true, managed: true,
sourceState: 'ready',
extent: groupExtent, extent: groupExtent,
zIndex: undefined, zIndex: undefined,
maxResolution: 500, maxResolution: 500,
@@ -399,7 +396,6 @@ describe('ol/layer/Group', function () {
opacity: 0.3, opacity: 0.3,
visible: false, visible: false,
managed: true, managed: true,
sourceState: 'ready',
extent: groupExtent, extent: groupExtent,
zIndex: 10, zIndex: 10,
maxResolution: 500, maxResolution: 500,
@@ -417,7 +413,6 @@ describe('ol/layer/Group', function () {
opacity: 0, opacity: 0,
visible: false, visible: false,
managed: true, managed: true,
sourceState: 'ready',
extent: undefined, extent: undefined,
zIndex: undefined, zIndex: undefined,
maxResolution: Infinity, maxResolution: Infinity,
@@ -433,7 +428,6 @@ describe('ol/layer/Group', function () {
opacity: 1, opacity: 1,
visible: true, visible: true,
managed: true, managed: true,
sourceState: 'ready',
extent: undefined, extent: undefined,
zIndex: undefined, zIndex: undefined,
maxResolution: Infinity, maxResolution: Infinity,
@@ -599,7 +593,6 @@ describe('ol/layer/Group', function () {
opacity: 0.25, opacity: 0.25,
visible: false, visible: false,
managed: true, managed: true,
sourceState: 'ready',
extent: undefined, extent: undefined,
zIndex: undefined, zIndex: undefined,
maxResolution: 150, maxResolution: 150,
-3
View File
@@ -56,7 +56,6 @@ describe('ol/layer/Layer', function () {
opacity: 1, opacity: 1,
visible: true, visible: true,
managed: true, managed: true,
sourceState: 'ready',
extent: undefined, extent: undefined,
zIndex: undefined, zIndex: undefined,
maxResolution: Infinity, maxResolution: Infinity,
@@ -95,7 +94,6 @@ describe('ol/layer/Layer', function () {
opacity: 0.5, opacity: 0.5,
visible: false, visible: false,
managed: true, managed: true,
sourceState: 'ready',
extent: undefined, extent: undefined,
zIndex: 10, zIndex: 10,
maxResolution: 500, maxResolution: 500,
@@ -430,7 +428,6 @@ describe('ol/layer/Layer', function () {
opacity: 0.33, opacity: 0.33,
visible: false, visible: false,
managed: true, managed: true,
sourceState: 'ready',
extent: undefined, extent: undefined,
zIndex: 10, zIndex: 10,
maxResolution: 500, maxResolution: 500,
@@ -373,4 +373,37 @@ describe('ol/layer/WebGLTile', function () {
done(); done();
}); });
}); });
it('handles multiple sources correctly', () => {
const source = layer.getSource();
expect(layer.getRenderSource()).to.be(source);
layer.sources_ = (extent, resolution) => {
return [
{
getState: () => 'ready',
extent,
resolution,
id: 'source1',
},
{
getState: () => 'ready',
extent,
resolution,
id: 'source2',
},
];
};
const sourceIds = [];
layer.getRenderer().prepareFrame = (frameState) => {
const renderedSource = layer.getRenderSource();
expect(renderedSource.extent).to.eql([0, 0, 100, 100]);
expect(renderedSource.resolution).to.be(1);
sourceIds.push(renderedSource.id);
};
layer.render({
extent: [0, 0, 100, 100],
viewState: {resolution: 1},
});
expect(sourceIds).to.eql(['source1', 'source2']);
});
}); });
+41
View File
@@ -0,0 +1,41 @@
import TileGrid from '../../../../src/ol/tilegrid/TileGrid.js';
import XYZ from '../../../../src/ol/source/XYZ.js';
import {createXYZ} from '../../../../src/ol/tilegrid.js';
import {get} from '../../../../src/ol/proj.js';
import {sourcesFromTileGrid} from '../../../../src/ol/source.js';
describe('ol/source', function () {
describe('sourcesFromTileGrid()', function () {
it('returns a function that returns the correct source', function () {
const resolutions = createXYZ({maxZoom: 1}).getResolutions();
const tileGrid = new TileGrid({
extent: get('EPSG:3857').getExtent(),
resolutions: [resolutions[1]],
tileSizes: [[256, 512]],
});
const factory = function (tileCoord) {
return new XYZ({
url: tileCoord.join('-') + '/{z}/{x}/{y}.png',
tileGrid: new TileGrid({
resolutions,
minZoom: tileCoord[0],
maxZoom: tileCoord[0] + 1,
extent: tileGrid.getTileCoordExtent(tileCoord),
origin: [-20037508.342789244, 20037508.342789244],
}),
});
};
const getSources = sourcesFromTileGrid(tileGrid, factory);
expect(getSources(tileGrid.getExtent(), resolutions[1]).length).to.be(2);
expect(
getSources(
[-10000, -10000, -5000, 10000],
resolutions[1]
)[0].getUrls()[0]
).to.be('0-0-0/{z}/{x}/{y}.png');
expect(
getSources([5000, -10000, 10000, 10000], resolutions[1])[0].getUrls()[0]
).to.be('0-1-0/{z}/{x}/{y}.png');
});
});
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

@@ -0,0 +1,58 @@
import Map from '../../../../src/ol/Map.js';
import TileGrid from '../../../../src/ol/tilegrid/TileGrid.js';
import TileLayer from '../../../../src/ol/layer/WebGLTile.js';
import View from '../../../../src/ol/View.js';
import XYZ from '../../../../src/ol/source/XYZ.js';
import {createXYZ} from '../../../../src/ol/tilegrid.js';
import {get} from '../../../../src/ol/proj.js';
import {sourcesFromTileGrid} from '../../../../src/ol/source.js';
const resolutions = createXYZ({maxZoom: 1}).getResolutions();
const tilePyramid = new TileGrid({
extent: get('EPSG:3857').getExtent(),
resolutions: [resolutions[1]],
tileSizes: [[256, 512]],
});
new Map({
target: 'map',
layers: [
new TileLayer({
sources: sourcesFromTileGrid(tilePyramid, (tileCoord) => {
let source;
switch (tileCoord.toString()) {
case '0,1,0':
source = new XYZ({
url: '/data/tiles/osm/{z}/{x}/{y}.png',
tileGrid: new TileGrid({
resolutions,
minZoom: tileCoord[0],
maxZoom: tileCoord[0] + 1,
extent: tilePyramid.getTileCoordExtent(tileCoord),
origin: [-20037508.342789244, 20037508.342789244],
}),
});
break;
default:
source = new XYZ({
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg',
tileGrid: new TileGrid({
resolutions,
minZoom: tileCoord[0],
maxZoom: tileCoord[0] + 1,
extent: tilePyramid.getTileCoordExtent(tileCoord),
origin: [-20037508.342789244, 20037508.342789244],
}),
});
}
return source;
}),
}),
],
view: new View({
center: [0, 0],
zoom: 1,
}),
});
render();
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB