Support multiple sources for layers

This commit is contained in:
Andreas Hocevar
2022-01-10 20:37:50 +01:00
parent 1a8df049e4
commit 0004b2594d
20 changed files with 348 additions and 67 deletions

View File

@@ -4,9 +4,8 @@ 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.
loading.
tags: "cog, tilepyramid, stac" tags: "cog, tilepyramid, stac"
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>

View File

@@ -1,16 +1,16 @@
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 {createXYZ} from '../src/ol/tilegrid.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 +21,25 @@ 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`; tileGrid: createXYZ({
if (!(url in layerForUrl)) { extent: tileGrid.getTileCoordExtent([z, x, y]),
pyramid.getLayers().push( minZoom: z,
new WebGLTileLayer({ maxZoom:
minZoom: z, z === tileGrid.getResolutions().length - 1 ? undefined : z + 1,
maxZoom: z === 0 || z === zs - 1 ? undefined : z + 1,
extent: tileGrid.getTileCoordExtent([z, x, y]),
source: new GeoTIFF({
sources: [
{
url: url,
},
],
}), }),
sources: [
{
url: `https://s2downloads.eox.at/demo/EOxCloudless/2019/rgb/${z}/${y}/${x}.tif`,
},
],
}) })
); ),
layerForUrl[url] = true; });
}
}
const map = new Map({ const map = new Map({
target: 'map', target: 'map',
@@ -56,16 +51,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)
);
});

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;

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.
*/ */

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,

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;

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;

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

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,35 @@ 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}.
* @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;
};
}

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
*/ */

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,

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,

View File

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

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

View File

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