Support multiple sources for layers
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
test/browser/spec/ol/source.test.js
Normal file
41
test/browser/spec/ol/source.test.js
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
BIN
test/rendering/cases/webgl-tile-multisource/expected.png
Normal file
BIN
test/rendering/cases/webgl-tile-multisource/expected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
58
test/rendering/cases/webgl-tile-multisource/main.js
Normal file
58
test/rendering/cases/webgl-tile-multisource/main.js
Normal 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();
|
||||||
BIN
test/rendering/data/tiles/osm/1/0/0.png
Normal file
BIN
test/rendering/data/tiles/osm/1/0/0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
BIN
test/rendering/data/tiles/osm/1/0/1.png
Normal file
BIN
test/rendering/data/tiles/osm/1/0/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
BIN
test/rendering/data/tiles/osm/1/1/0.png
Normal file
BIN
test/rendering/data/tiles/osm/1/1/0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
BIN
test/rendering/data/tiles/osm/1/1/1.png
Normal file
BIN
test/rendering/data/tiles/osm/1/1/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
Reference in New Issue
Block a user