Get pixel data
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
## Upgrade notes
|
## Upgrade notes
|
||||||
|
|
||||||
|
### Next
|
||||||
|
|
||||||
|
#### New `layer.getData()` method
|
||||||
|
|
||||||
|
Raster layers (static images, image tiles, data tiles) have a new `layer.getData(pixel)` method that returns the pixel data at the provided location. The return value depends on the underlying source data type. For example, a GeoTIFF may return a `Float32Array` with one value per band, while a PNG rendered from a tile layer will return a `Uint8ClampedArray` of RGBA values.
|
||||||
|
|
||||||
|
If you were previously using the `map.forEachLayerAtPixel()` method, you should use the new `layer.getData()` method instead. The old method returns composite pixel values from multiple layers and is limited to RGBA values. The new method doesn't suffer from these shortcomings and is more performant.
|
||||||
|
|
||||||
### v6.12.0
|
### v6.12.0
|
||||||
|
|
||||||
No special changes are required when upgrading to the 6.12.0 release.
|
No special changes are required when upgrading to the 6.12.0 release.
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ layout: example.html
|
|||||||
title: NDVI from a Sentinel 2 COG
|
title: NDVI from a Sentinel 2 COG
|
||||||
shortdesc: Calculating NDVI and applying a custom color map.
|
shortdesc: Calculating NDVI and applying a custom color map.
|
||||||
docs: >
|
docs: >
|
||||||
The GeoTIFF layer in this example draws from two Sentinel 2 sources: a red band and a near infrared band.
|
The GeoTIFF layer in this example draws from two Sentinel 2 sources: a red band and a near-infrared band.
|
||||||
The layer style includes a `color` expression that calculates the Normalized Difference Vegetation Index (NDVI)
|
The layer style includes a `color` expression that calculates the Normalized Difference Vegetation Index (NDVI)
|
||||||
from values in the two bands. The `interpolate` expression is used to map NDVI values to colors.
|
from values in the two bands. The `interpolate` expression is used to map NDVI values to colors.
|
||||||
|
The `layer.getData()` method can be used to retrieve pixel values from the GeoTIFF. Move your mouse
|
||||||
|
or tap on the map to see calculated NDVI values based on the red and near-infrared pixel values.
|
||||||
tags: "cog, ndvi"
|
tags: "cog, ndvi"
|
||||||
---
|
---
|
||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
|
<div>NDVI: <span id="output"></span></div>
|
||||||
|
|||||||
@@ -17,65 +17,74 @@ const source = new GeoTIFF({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const layer = new TileLayer({
|
||||||
|
style: {
|
||||||
|
color: [
|
||||||
|
'interpolate',
|
||||||
|
['linear'],
|
||||||
|
// calculate NDVI, bands come from the sources below
|
||||||
|
['/', ['-', ['band', 2], ['band', 1]], ['+', ['band', 2], ['band', 1]]],
|
||||||
|
// color ramp for NDVI values, ranging from -1 to 1
|
||||||
|
-0.2,
|
||||||
|
[191, 191, 191],
|
||||||
|
-0.1,
|
||||||
|
[219, 219, 219],
|
||||||
|
0,
|
||||||
|
[255, 255, 224],
|
||||||
|
0.025,
|
||||||
|
[255, 250, 204],
|
||||||
|
0.05,
|
||||||
|
[237, 232, 181],
|
||||||
|
0.075,
|
||||||
|
[222, 217, 156],
|
||||||
|
0.1,
|
||||||
|
[204, 199, 130],
|
||||||
|
0.125,
|
||||||
|
[189, 184, 107],
|
||||||
|
0.15,
|
||||||
|
[176, 194, 97],
|
||||||
|
0.175,
|
||||||
|
[163, 204, 89],
|
||||||
|
0.2,
|
||||||
|
[145, 191, 82],
|
||||||
|
0.25,
|
||||||
|
[128, 179, 71],
|
||||||
|
0.3,
|
||||||
|
[112, 163, 64],
|
||||||
|
0.35,
|
||||||
|
[97, 150, 54],
|
||||||
|
0.4,
|
||||||
|
[79, 138, 46],
|
||||||
|
0.45,
|
||||||
|
[64, 125, 36],
|
||||||
|
0.5,
|
||||||
|
[48, 110, 28],
|
||||||
|
0.55,
|
||||||
|
[33, 97, 18],
|
||||||
|
0.6,
|
||||||
|
[15, 84, 10],
|
||||||
|
0.65,
|
||||||
|
[0, 69, 0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
source: source,
|
||||||
|
});
|
||||||
|
|
||||||
const map = new Map({
|
const map = new Map({
|
||||||
target: 'map',
|
target: 'map',
|
||||||
layers: [
|
layers: [layer],
|
||||||
new TileLayer({
|
|
||||||
style: {
|
|
||||||
color: [
|
|
||||||
'interpolate',
|
|
||||||
['linear'],
|
|
||||||
// calculate NDVI, bands come from the sources below
|
|
||||||
[
|
|
||||||
'/',
|
|
||||||
['-', ['band', 2], ['band', 1]],
|
|
||||||
['+', ['band', 2], ['band', 1]],
|
|
||||||
],
|
|
||||||
// color ramp for NDVI values, ranging from -1 to 1
|
|
||||||
-0.2,
|
|
||||||
[191, 191, 191],
|
|
||||||
-0.1,
|
|
||||||
[219, 219, 219],
|
|
||||||
0,
|
|
||||||
[255, 255, 224],
|
|
||||||
0.025,
|
|
||||||
[255, 250, 204],
|
|
||||||
0.05,
|
|
||||||
[237, 232, 181],
|
|
||||||
0.075,
|
|
||||||
[222, 217, 156],
|
|
||||||
0.1,
|
|
||||||
[204, 199, 130],
|
|
||||||
0.125,
|
|
||||||
[189, 184, 107],
|
|
||||||
0.15,
|
|
||||||
[176, 194, 97],
|
|
||||||
0.175,
|
|
||||||
[163, 204, 89],
|
|
||||||
0.2,
|
|
||||||
[145, 191, 82],
|
|
||||||
0.25,
|
|
||||||
[128, 179, 71],
|
|
||||||
0.3,
|
|
||||||
[112, 163, 64],
|
|
||||||
0.35,
|
|
||||||
[97, 150, 54],
|
|
||||||
0.4,
|
|
||||||
[79, 138, 46],
|
|
||||||
0.45,
|
|
||||||
[64, 125, 36],
|
|
||||||
0.5,
|
|
||||||
[48, 110, 28],
|
|
||||||
0.55,
|
|
||||||
[33, 97, 18],
|
|
||||||
0.6,
|
|
||||||
[15, 84, 10],
|
|
||||||
0.65,
|
|
||||||
[0, 69, 0],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
source: source,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
view: source.getView(),
|
view: source.getView(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const output = document.getElementById('output');
|
||||||
|
function displayPixelValue(event) {
|
||||||
|
const data = layer.getData(event.pixel);
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const red = data[0];
|
||||||
|
const nir = data[1];
|
||||||
|
const ndvi = (nir - red) / (nir + red);
|
||||||
|
output.textContent = ndvi.toFixed(2);
|
||||||
|
}
|
||||||
|
map.on(['pointermove', 'click'], displayPixelValue);
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ layout: example.html
|
|||||||
title: WMS GetFeatureInfo (Image Layer)
|
title: WMS GetFeatureInfo (Image Layer)
|
||||||
shortdesc: Using an image WMS source with GetFeatureInfo requests
|
shortdesc: Using an image WMS source with GetFeatureInfo requests
|
||||||
docs: >
|
docs: >
|
||||||
This example shows how to trigger WMS GetFeatureInfo requests on click for a WMS image layer. Additionally <code>map.forEachLayerAtPixel</code> is used to change the mouse pointer when hovering a non-transparent pixel on the map.
|
This example shows how to trigger WMS GetFeatureInfo requests on click for a WMS image layer. Additionally `layer.getData(pixel)` is used to change the mouse pointer when hovering a non-transparent pixel on the map.
|
||||||
tags: "getfeatureinfo, forEachLayerAtPixel"
|
tags: "getfeatureinfo, getData"
|
||||||
---
|
---
|
||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
<div id="info"> </div>
|
<div id="info"> </div>
|
||||||
|
|||||||
@@ -47,9 +47,7 @@ map.on('pointermove', function (evt) {
|
|||||||
if (evt.dragging) {
|
if (evt.dragging) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pixel = map.getEventPixel(evt.originalEvent);
|
const data = wmsLayer.getData(evt.pixel);
|
||||||
const hit = map.forEachLayerAtPixel(pixel, function () {
|
const hit = data && data[3] > 0; // transparent pixels have zero for data[3]
|
||||||
return true;
|
|
||||||
});
|
|
||||||
map.getTargetElement().style.cursor = hit ? 'pointer' : '';
|
map.getTargetElement().style.cursor = hit ? 'pointer' : '';
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ layout: example.html
|
|||||||
title: WMS GetFeatureInfo (Tile Layer)
|
title: WMS GetFeatureInfo (Tile Layer)
|
||||||
shortdesc: Issuing GetFeatureInfo requests with a WMS tiled source
|
shortdesc: Issuing GetFeatureInfo requests with a WMS tiled source
|
||||||
docs: >
|
docs: >
|
||||||
This example shows how to trigger WMS GetFeatureInfo requests on click for a WMS tile layer. Additionally <code>map.forEachLayerAtPixel</code> is used to change the mouse pointer when hovering a non-transparent pixel on the map.
|
This example shows how to trigger WMS GetFeatureInfo requests on click for a WMS tile layer. Additionally `layer.getData(pixel)` is used to change the mouse pointer when hovering a non-transparent pixel on the map.
|
||||||
tags: "getfeatureinfo, forEachLayerAtPixel"
|
tags: "getfeatureinfo, getData"
|
||||||
---
|
---
|
||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
<div id="info"> </div>
|
<div id="info"> </div>
|
||||||
|
|||||||
@@ -47,9 +47,7 @@ map.on('pointermove', function (evt) {
|
|||||||
if (evt.dragging) {
|
if (evt.dragging) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pixel = map.getEventPixel(evt.originalEvent);
|
const data = wmsLayer.getData(evt.pixel);
|
||||||
const hit = map.forEachLayerAtPixel(pixel, function () {
|
const hit = data && data[3] > 0; // transparent pixels have zero for data[3]
|
||||||
return true;
|
|
||||||
});
|
|
||||||
map.getTargetElement().style.cursor = hit ? 'pointer' : '';
|
map.getTargetElement().style.cursor = hit ? 'pointer' : '';
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -685,6 +685,9 @@ class PluggableMap extends BaseObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Please the `layer.getData()` method for {@link module:ol/layer/Tile~TileLayer#getData tile layers} or
|
||||||
|
* {@link module:ol/layer/Image~ImageLayer#getData image layers} instead of using this method.
|
||||||
|
*
|
||||||
* Detect layers that have a color value at a pixel on the viewport, and
|
* Detect layers that have a color value at a pixel on the viewport, and
|
||||||
* execute a callback with each matching layer. Layers included in the
|
* execute a callback with each matching layer. Layers included in the
|
||||||
* detection can be configured through `opt_layerFilter`.
|
* detection can be configured through `opt_layerFilter`.
|
||||||
|
|||||||
@@ -136,6 +136,26 @@ class BaseTileLayer extends Layer {
|
|||||||
setUseInterimTilesOnError(useInterimTilesOnError) {
|
setUseInterimTilesOnError(useInterimTilesOnError) {
|
||||||
this.set(TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
|
this.set(TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data for a pixel location. The return type depends on the source data. For image tiles,
|
||||||
|
* a four element RGBA array will be returned. For data tiles, the array length will match the
|
||||||
|
* number of bands in the dataset. For requests outside the layer extent, `null` will be returned.
|
||||||
|
* Data for a image tiles can only be retrieved if the source's `crossOrigin` property is set.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* // display layer data on every pointer move
|
||||||
|
* map.on('pointermove', (event) => {
|
||||||
|
* console.log(layer.getData(event.pixel));
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @param {import("../pixel").Pixel} pixel Pixel.
|
||||||
|
* @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView|null} Pixel data.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
getData(pixel) {
|
||||||
|
return super.getData(pixel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BaseTileLayer;
|
export default BaseTileLayer;
|
||||||
|
|||||||
@@ -27,6 +27,25 @@ class ImageLayer extends BaseImageLayer {
|
|||||||
createRenderer() {
|
createRenderer() {
|
||||||
return new CanvasImageLayerRenderer(this);
|
return new CanvasImageLayerRenderer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data for a pixel location. A four element RGBA array will be returned. For requests outside the
|
||||||
|
* layer extent, `null` will be returned. Data for an image can only be retrieved if the
|
||||||
|
* source's `crossOrigin` property is set.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* // display layer data on every pointer move
|
||||||
|
* map.on('pointermove', (event) => {
|
||||||
|
* console.log(layer.getData(event.pixel));
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @param {import("../pixel").Pixel} pixel Pixel.
|
||||||
|
* @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView|null} Pixel data.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
getData(pixel) {
|
||||||
|
return super.getData(pixel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ImageLayer;
|
export default ImageLayer;
|
||||||
|
|||||||
@@ -250,6 +250,17 @@ class Layer extends BaseLayer {
|
|||||||
return this.renderer_.getFeatures(pixel);
|
return this.renderer_.getFeatures(pixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../pixel").Pixel} pixel Pixel.
|
||||||
|
* @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView|null} Pixel data.
|
||||||
|
*/
|
||||||
|
getData(pixel) {
|
||||||
|
if (!this.renderer_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.renderer_.getData(pixel);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In charge to manage the rendering of the layer. One layer type is
|
* In charge to manage the rendering of the layer. One layer type is
|
||||||
* bounded with one layer renderer.
|
* bounded with one layer renderer.
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ class LayerRenderer extends Observable {
|
|||||||
return abstract();
|
return abstract();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../pixel.js").Pixel} pixel Pixel.
|
||||||
|
* @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView|null} Pixel data.
|
||||||
|
*/
|
||||||
|
getData(pixel) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether render should be called.
|
* Determine whether render should be called.
|
||||||
* @abstract
|
* @abstract
|
||||||
@@ -191,6 +199,14 @@ class LayerRenderer extends Observable {
|
|||||||
layer.changed();
|
layer.changed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up.
|
||||||
|
*/
|
||||||
|
disposeInternal() {
|
||||||
|
delete this.layer_;
|
||||||
|
super.disposeInternal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayerRenderer;
|
export default LayerRenderer;
|
||||||
|
|||||||
@@ -5,15 +5,19 @@ import CanvasLayerRenderer from './Layer.js';
|
|||||||
import ViewHint from '../../ViewHint.js';
|
import ViewHint from '../../ViewHint.js';
|
||||||
import {ENABLE_RASTER_REPROJECTION} from '../../reproj/common.js';
|
import {ENABLE_RASTER_REPROJECTION} from '../../reproj/common.js';
|
||||||
import {IMAGE_SMOOTHING_DISABLED, IMAGE_SMOOTHING_ENABLED} from './common.js';
|
import {IMAGE_SMOOTHING_DISABLED, IMAGE_SMOOTHING_ENABLED} from './common.js';
|
||||||
import {assign} from '../../obj.js';
|
|
||||||
import {
|
import {
|
||||||
|
apply as applyTransform,
|
||||||
compose as composeTransform,
|
compose as composeTransform,
|
||||||
makeInverse,
|
makeInverse,
|
||||||
toString as toTransformString,
|
toString as toTransformString,
|
||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
|
import {assign} from '../../obj.js';
|
||||||
import {
|
import {
|
||||||
|
containsCoordinate,
|
||||||
containsExtent,
|
containsExtent,
|
||||||
|
getHeight,
|
||||||
getIntersection,
|
getIntersection,
|
||||||
|
getWidth,
|
||||||
intersects as intersectsExtent,
|
intersects as intersectsExtent,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
} from '../../extent.js';
|
} from '../../extent.js';
|
||||||
@@ -98,6 +102,51 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
|||||||
return !!this.image_;
|
return !!this.image_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../pixel.js").Pixel} pixel Pixel.
|
||||||
|
* @return {Uint8ClampedArray} Data at the pixel location.
|
||||||
|
*/
|
||||||
|
getData(pixel) {
|
||||||
|
const frameState = this.frameState;
|
||||||
|
if (!frameState) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layer = this.getLayer();
|
||||||
|
const coordinate = applyTransform(
|
||||||
|
frameState.pixelToCoordinateTransform,
|
||||||
|
pixel.slice()
|
||||||
|
);
|
||||||
|
|
||||||
|
const layerExtent = layer.getExtent();
|
||||||
|
if (layerExtent) {
|
||||||
|
if (!containsCoordinate(layerExtent, coordinate)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageExtent = this.image_.getExtent();
|
||||||
|
const img = this.image_.getImage();
|
||||||
|
|
||||||
|
const imageMapWidth = getWidth(imageExtent);
|
||||||
|
const col = Math.floor(
|
||||||
|
img.width * ((coordinate[0] - imageExtent[0]) / imageMapWidth)
|
||||||
|
);
|
||||||
|
if (col < 0 || col >= img.width) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageMapHeight = getHeight(imageExtent);
|
||||||
|
const row = Math.floor(
|
||||||
|
img.height * ((imageExtent[3] - coordinate[1]) / imageMapHeight)
|
||||||
|
);
|
||||||
|
if (row < 0 || row >= img.height) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getImageData(img, col, row);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the layer.
|
* Render the layer.
|
||||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
|
|||||||
@@ -20,6 +20,18 @@ import {
|
|||||||
import {createCanvasContext2D} from '../../dom.js';
|
import {createCanvasContext2D} from '../../dom.js';
|
||||||
import {equals} from '../../array.js';
|
import {equals} from '../../array.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {CanvasRenderingContext2D}
|
||||||
|
*/
|
||||||
|
let pixelContext = null;
|
||||||
|
|
||||||
|
function createPixelContext() {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 1;
|
||||||
|
canvas.height = 1;
|
||||||
|
pixelContext = canvas.getContext('2d');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
* @template {import("../../layer/Layer.js").default} LayerType
|
* @template {import("../../layer/Layer.js").default} LayerType
|
||||||
@@ -83,6 +95,34 @@ class CanvasLayerRenderer extends LayerRenderer {
|
|||||||
* @type {CanvasRenderingContext2D}
|
* @type {CanvasRenderingContext2D}
|
||||||
*/
|
*/
|
||||||
this.pixelContext_ = null;
|
this.pixelContext_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {import("../../PluggableMap.js").FrameState|null}
|
||||||
|
*/
|
||||||
|
this.frameState = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image.
|
||||||
|
* @param {number} col The column index.
|
||||||
|
* @param {number} row The row index.
|
||||||
|
* @return {Uint8ClampedArray|null} The image data.
|
||||||
|
*/
|
||||||
|
getImageData(image, col, row) {
|
||||||
|
if (!pixelContext) {
|
||||||
|
createPixelContext();
|
||||||
|
}
|
||||||
|
pixelContext.clearRect(0, 0, 1, 1);
|
||||||
|
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
pixelContext.drawImage(image, col, row, 1, 1, 0, 0, 1, 1);
|
||||||
|
data = pixelContext.getImageData(0, 0, 1, 1).data;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -215,6 +255,7 @@ class CanvasLayerRenderer extends LayerRenderer {
|
|||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
preRender(context, frameState) {
|
preRender(context, frameState) {
|
||||||
|
this.frameState = frameState;
|
||||||
this.dispatchRenderEvent_(RenderEventType.PRERENDER, context, frameState);
|
this.dispatchRenderEvent_(RenderEventType.PRERENDER, context, frameState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,6 +365,14 @@ class CanvasLayerRenderer extends LayerRenderer {
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up.
|
||||||
|
*/
|
||||||
|
disposeInternal() {
|
||||||
|
delete this.frameState;
|
||||||
|
super.disposeInternal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CanvasLayerRenderer;
|
export default CanvasLayerRenderer;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* @module ol/renderer/canvas/TileLayer
|
* @module ol/renderer/canvas/TileLayer
|
||||||
*/
|
*/
|
||||||
import CanvasLayerRenderer from './Layer.js';
|
import CanvasLayerRenderer from './Layer.js';
|
||||||
|
import ImageTile from '../../ImageTile.js';
|
||||||
|
import ReprojTile from '../../reproj/Tile.js';
|
||||||
import TileRange from '../../TileRange.js';
|
import TileRange from '../../TileRange.js';
|
||||||
import TileState from '../../TileState.js';
|
import TileState from '../../TileState.js';
|
||||||
import {IMAGE_SMOOTHING_DISABLED, IMAGE_SMOOTHING_ENABLED} from './common.js';
|
import {IMAGE_SMOOTHING_DISABLED, IMAGE_SMOOTHING_ENABLED} from './common.js';
|
||||||
@@ -13,6 +15,7 @@ import {
|
|||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
import {assign} from '../../obj.js';
|
import {assign} from '../../obj.js';
|
||||||
import {
|
import {
|
||||||
|
containsCoordinate,
|
||||||
createEmpty,
|
createEmpty,
|
||||||
equals,
|
equals,
|
||||||
getIntersection,
|
getIntersection,
|
||||||
@@ -22,6 +25,7 @@ import {cssOpacity} from '../../css.js';
|
|||||||
import {fromUserExtent} from '../../proj.js';
|
import {fromUserExtent} from '../../proj.js';
|
||||||
import {getUid} from '../../util.js';
|
import {getUid} from '../../util.js';
|
||||||
import {numberSafeCompareFunction} from '../../array.js';
|
import {numberSafeCompareFunction} from '../../array.js';
|
||||||
|
import {toSize} from '../../size.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @classdesc
|
* @classdesc
|
||||||
@@ -136,6 +140,79 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
return tile;
|
return tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../pixel.js").Pixel} pixel Pixel.
|
||||||
|
* @return {Uint8ClampedArray} Data at the pixel location.
|
||||||
|
*/
|
||||||
|
getData(pixel) {
|
||||||
|
const frameState = this.frameState;
|
||||||
|
if (!frameState) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layer = this.getLayer();
|
||||||
|
const coordinate = applyTransform(
|
||||||
|
frameState.pixelToCoordinateTransform,
|
||||||
|
pixel.slice()
|
||||||
|
);
|
||||||
|
|
||||||
|
const layerExtent = layer.getExtent();
|
||||||
|
if (layerExtent) {
|
||||||
|
if (!containsCoordinate(layerExtent, coordinate)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pixelRatio = frameState.pixelRatio;
|
||||||
|
const projection = frameState.viewState.projection;
|
||||||
|
const viewState = frameState.viewState;
|
||||||
|
const source = layer.getRenderSource();
|
||||||
|
const tileGrid = source.getTileGridForProjection(viewState.projection);
|
||||||
|
const tilePixelRatio = source.getTilePixelRatio(frameState.pixelRatio);
|
||||||
|
|
||||||
|
for (
|
||||||
|
let z = tileGrid.getZForResolution(viewState.resolution);
|
||||||
|
z >= tileGrid.getMinZoom();
|
||||||
|
--z
|
||||||
|
) {
|
||||||
|
const tileCoord = tileGrid.getTileCoordForCoordAndZ(coordinate, z);
|
||||||
|
const tile = source.getTile(
|
||||||
|
z,
|
||||||
|
tileCoord[1],
|
||||||
|
tileCoord[2],
|
||||||
|
pixelRatio,
|
||||||
|
projection
|
||||||
|
);
|
||||||
|
if (!(tile instanceof ImageTile || tile instanceof ReprojTile)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tile.getState() !== TileState.LOADED) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tileOrigin = tileGrid.getOrigin(z);
|
||||||
|
const tileSize = toSize(tileGrid.getTileSize(z));
|
||||||
|
const tileResolution = tileGrid.getResolution(z);
|
||||||
|
|
||||||
|
const col = Math.floor(
|
||||||
|
tilePixelRatio *
|
||||||
|
((coordinate[0] - tileOrigin[0]) / tileResolution -
|
||||||
|
tileCoord[1] * tileSize[0])
|
||||||
|
);
|
||||||
|
|
||||||
|
const row = Math.floor(
|
||||||
|
tilePixelRatio *
|
||||||
|
((tileOrigin[1] - coordinate[1]) / tileResolution -
|
||||||
|
tileCoord[2] * tileSize[1])
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.getImageData(tile.getImage(), col, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object<number, Object<string, import("../../Tile.js").default>>} tiles Lookup of loaded tiles by zoom level.
|
* @param {Object<number, Object<string, import("../../Tile.js").default>>} tiles Lookup of loaded tiles by zoom level.
|
||||||
* @param {number} zoom Zoom level.
|
* @param {number} zoom Zoom level.
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import WebGLLayerRenderer from './Layer.js';
|
|||||||
import {AttributeType} from '../../webgl/Helper.js';
|
import {AttributeType} from '../../webgl/Helper.js';
|
||||||
import {ELEMENT_ARRAY_BUFFER, STATIC_DRAW} from '../../webgl.js';
|
import {ELEMENT_ARRAY_BUFFER, STATIC_DRAW} from '../../webgl.js';
|
||||||
import {
|
import {
|
||||||
|
apply as applyTransform,
|
||||||
compose as composeTransform,
|
compose as composeTransform,
|
||||||
create as createTransform,
|
create as createTransform,
|
||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
|
import {containsCoordinate, getIntersection, isEmpty} from '../../extent.js';
|
||||||
import {
|
import {
|
||||||
create as createMat4,
|
create as createMat4,
|
||||||
fromTransform as mat4FromTransform,
|
fromTransform as mat4FromTransform,
|
||||||
@@ -23,7 +25,6 @@ import {
|
|||||||
getKey as getTileCoordKey,
|
getKey as getTileCoordKey,
|
||||||
} from '../../tilecoord.js';
|
} from '../../tilecoord.js';
|
||||||
import {fromUserExtent} from '../../proj.js';
|
import {fromUserExtent} from '../../proj.js';
|
||||||
import {getIntersection, isEmpty} from '../../extent.js';
|
|
||||||
import {getUid} from '../../util.js';
|
import {getUid} from '../../util.js';
|
||||||
import {numberSafeCompareFunction} from '../../array.js';
|
import {numberSafeCompareFunction} from '../../array.js';
|
||||||
import {toSize} from '../../size.js';
|
import {toSize} from '../../size.js';
|
||||||
@@ -233,6 +234,12 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.paletteTextures_ = options.paletteTextures || [];
|
this.paletteTextures_ = options.paletteTextures || [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {import("../../PluggableMap.js").FrameState|null}
|
||||||
|
*/
|
||||||
|
this.frameState_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -355,13 +362,13 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
viewState.projection
|
viewState.projection
|
||||||
);
|
);
|
||||||
if (!tileTexture) {
|
if (!tileTexture) {
|
||||||
tileTexture = new TileTexture(
|
tileTexture = new TileTexture({
|
||||||
tile,
|
tile: tile,
|
||||||
tileGrid,
|
grid: tileGrid,
|
||||||
this.helper,
|
helper: this.helper,
|
||||||
tilePixelRatio,
|
tilePixelRatio: tilePixelRatio,
|
||||||
gutter
|
gutter: gutter,
|
||||||
);
|
});
|
||||||
tileTextureCache.set(cacheKey, tileTexture);
|
tileTextureCache.set(cacheKey, tileTexture);
|
||||||
} else {
|
} else {
|
||||||
if (this.isDrawableTile_(tile)) {
|
if (this.isDrawableTile_(tile)) {
|
||||||
@@ -401,6 +408,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
* @return {HTMLElement} The rendered element.
|
* @return {HTMLElement} The rendered element.
|
||||||
*/
|
*/
|
||||||
renderFrame(frameState) {
|
renderFrame(frameState) {
|
||||||
|
this.frameState_ = frameState;
|
||||||
this.renderComplete = true;
|
this.renderComplete = true;
|
||||||
const gl = this.helper.getGL();
|
const gl = this.helper.getGL();
|
||||||
this.preRender(gl, frameState);
|
this.preRender(gl, frameState);
|
||||||
@@ -648,6 +656,83 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../pixel.js").Pixel} pixel Pixel.
|
||||||
|
* @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView} Data at the pixel location.
|
||||||
|
*/
|
||||||
|
getData(pixel) {
|
||||||
|
const gl = this.helper.getGL();
|
||||||
|
if (!gl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const frameState = this.frameState_;
|
||||||
|
if (!frameState) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layer = this.getLayer();
|
||||||
|
const coordinate = applyTransform(
|
||||||
|
frameState.pixelToCoordinateTransform,
|
||||||
|
pixel.slice()
|
||||||
|
);
|
||||||
|
|
||||||
|
const viewState = frameState.viewState;
|
||||||
|
const layerExtent = layer.getExtent();
|
||||||
|
if (layerExtent) {
|
||||||
|
if (
|
||||||
|
!containsCoordinate(
|
||||||
|
fromUserExtent(layerExtent, viewState.projection),
|
||||||
|
coordinate
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = layer.getRenderSource();
|
||||||
|
const tileGrid = source.getTileGridForProjection(viewState.projection);
|
||||||
|
if (!source.getWrapX()) {
|
||||||
|
const gridExtent = tileGrid.getExtent();
|
||||||
|
if (gridExtent) {
|
||||||
|
if (!containsCoordinate(gridExtent, coordinate)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tileTextureCache = this.tileTextureCache_;
|
||||||
|
for (
|
||||||
|
let z = tileGrid.getZForResolution(viewState.resolution);
|
||||||
|
z >= tileGrid.getMinZoom();
|
||||||
|
--z
|
||||||
|
) {
|
||||||
|
const tileCoord = tileGrid.getTileCoordForCoordAndZ(coordinate, z);
|
||||||
|
const cacheKey = getCacheKey(source, tileCoord);
|
||||||
|
if (!tileTextureCache.containsKey(cacheKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const tileTexture = tileTextureCache.get(cacheKey);
|
||||||
|
if (!tileTexture.loaded) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const tileOrigin = tileGrid.getOrigin(z);
|
||||||
|
const tileSize = toSize(tileGrid.getTileSize(z));
|
||||||
|
const tileResolution = tileGrid.getResolution(z);
|
||||||
|
|
||||||
|
const col =
|
||||||
|
(coordinate[0] - tileOrigin[0]) / tileResolution -
|
||||||
|
tileCoord[1] * tileSize[0];
|
||||||
|
|
||||||
|
const row =
|
||||||
|
(tileOrigin[1] - coordinate[1]) / tileResolution -
|
||||||
|
tileCoord[2] * tileSize[1];
|
||||||
|
|
||||||
|
return tileTexture.getPixelData(col, row);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look for tiles covering the provided tile coordinate at an alternate
|
* Look for tiles covering the provided tile coordinate at an alternate
|
||||||
* zoom level. Loaded tiles will be added to the provided tile texture lookup.
|
* zoom level. Loaded tiles will be added to the provided tile texture lookup.
|
||||||
@@ -719,6 +804,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
|
|
||||||
delete this.indices_;
|
delete this.indices_;
|
||||||
delete this.tileTextureCache_;
|
delete this.tileTextureCache_;
|
||||||
|
delete this.frameState_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ class DataTileSource extends TileSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
|
||||||
* @param {number} z Tile coordinate z.
|
* @param {number} z Tile coordinate z.
|
||||||
* @param {number} x Tile coordinate x.
|
* @param {number} x Tile coordinate x.
|
||||||
* @param {number} y Tile coordinate y.
|
* @param {number} y Tile coordinate y.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* @module ol/webgl/TileTexture
|
* @module ol/webgl/TileTexture
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import DataTile from '../DataTile.js';
|
||||||
import EventTarget from '../events/Target.js';
|
import EventTarget from '../events/Target.js';
|
||||||
import EventType from '../events/EventType.js';
|
import EventType from '../events/EventType.js';
|
||||||
import ImageTile from '../ImageTile.js';
|
import ImageTile from '../ImageTile.js';
|
||||||
@@ -117,19 +118,36 @@ function uploadDataTexture(
|
|||||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, oldUnpackAlignment);
|
gl.pixelStorei(gl.UNPACK_ALIGNMENT, oldUnpackAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {CanvasRenderingContext2D}
|
||||||
|
*/
|
||||||
|
let pixelContext = null;
|
||||||
|
|
||||||
|
function createPixelContext() {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 1;
|
||||||
|
canvas.height = 1;
|
||||||
|
pixelContext = canvas.getContext('2d');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import("../DataTile.js").default|ImageTile|ReprojTile} TileType
|
* @typedef {import("../DataTile.js").default|ImageTile|ReprojTile} TileType
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Options
|
||||||
|
* @property {TileType} tile The tile.
|
||||||
|
* @property {import("../tilegrid/TileGrid.js").default} grid Tile grid.
|
||||||
|
* @property {import("../webgl/Helper.js").default} helper WebGL helper.
|
||||||
|
* @property {number} [tilePixelRatio=1] Tile pixel ratio.
|
||||||
|
* @property {number} [gutter=0] The size in pixels of the gutter around image tiles to ignore.
|
||||||
|
*/
|
||||||
|
|
||||||
class TileTexture extends EventTarget {
|
class TileTexture extends EventTarget {
|
||||||
/**
|
/**
|
||||||
* @param {TileType} tile The tile.
|
* @param {Options} options The tile texture options.
|
||||||
* @param {import("../tilegrid/TileGrid.js").default} grid Tile grid.
|
|
||||||
* @param {import("../webgl/Helper.js").default} helper WebGL helper.
|
|
||||||
* @param {number} [opt_tilePixelRatio=1] Tile pixel ratio.
|
|
||||||
* @param {number} [opt_gutter=0] The size in pixels of the gutter around image tiles to ignore.
|
|
||||||
*/
|
*/
|
||||||
constructor(tile, grid, helper, opt_tilePixelRatio, opt_gutter) {
|
constructor(options) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,16 +161,33 @@ class TileTexture extends EventTarget {
|
|||||||
this.textures = [];
|
this.textures = [];
|
||||||
this.handleTileChange_ = this.handleTileChange_.bind(this);
|
this.handleTileChange_ = this.handleTileChange_.bind(this);
|
||||||
|
|
||||||
this.size = toSize(grid.getTileSize(tile.tileCoord[0]));
|
/**
|
||||||
|
* @type {import("../size.js").Size}
|
||||||
|
*/
|
||||||
|
this.size = toSize(options.grid.getTileSize(options.tile.tileCoord[0]));
|
||||||
|
|
||||||
this.tilePixelRatio_ =
|
/**
|
||||||
opt_tilePixelRatio !== undefined ? opt_tilePixelRatio : 1;
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.tilePixelRatio_ = options.tilePixelRatio || 1;
|
||||||
|
|
||||||
this.gutter_ = opt_gutter !== undefined ? opt_gutter : 0;
|
/**
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.gutter_ = options.gutter || 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
this.bandCount = NaN;
|
this.bandCount = NaN;
|
||||||
|
|
||||||
this.helper_ = helper;
|
/**
|
||||||
|
* @type {import("../webgl/Helper.js").default}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.helper_ = options.helper;
|
||||||
|
|
||||||
const coords = new WebGLArrayBuffer(ARRAY_BUFFER, STATIC_DRAW);
|
const coords = new WebGLArrayBuffer(ARRAY_BUFFER, STATIC_DRAW);
|
||||||
coords.fromArray([
|
coords.fromArray([
|
||||||
@@ -165,10 +200,14 @@ class TileTexture extends EventTarget {
|
|||||||
0, // P3
|
0, // P3
|
||||||
0,
|
0,
|
||||||
]);
|
]);
|
||||||
helper.flushBufferData(coords);
|
this.helper_.flushBufferData(coords);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {WebGLArrayBuffer}
|
||||||
|
*/
|
||||||
this.coords = coords;
|
this.coords = coords;
|
||||||
this.setTile(tile);
|
|
||||||
|
this.setTile(options.tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -320,6 +359,50 @@ class TileTexture extends EventTarget {
|
|||||||
}
|
}
|
||||||
this.tile.removeEventListener(EventType.CHANGE, this.handleTileChange_);
|
this.tile.removeEventListener(EventType.CHANGE, this.handleTileChange_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data for a pixel. If the tile is not loaded, null is returned.
|
||||||
|
* @param {number} col The column index.
|
||||||
|
* @param {number} row The row index.
|
||||||
|
* @return {import("../DataTile.js").Data|null} The data.
|
||||||
|
*/
|
||||||
|
getPixelData(col, row) {
|
||||||
|
if (!this.loaded) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
col = Math.floor(this.tilePixelRatio_ * col);
|
||||||
|
row = Math.floor(this.tilePixelRatio_ * row);
|
||||||
|
|
||||||
|
if (this.tile instanceof DataTile) {
|
||||||
|
const data = this.tile.getData();
|
||||||
|
const pixelsPerRow = Math.floor(this.tilePixelRatio_ * this.size[0]);
|
||||||
|
if (data instanceof DataView) {
|
||||||
|
const bytesPerPixel = data.byteLength / (this.size[0] * this.size[1]);
|
||||||
|
const offset = row * pixelsPerRow * bytesPerPixel + col * bytesPerPixel;
|
||||||
|
const buffer = data.buffer.slice(offset, offset + bytesPerPixel);
|
||||||
|
return new DataView(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = row * pixelsPerRow * this.bandCount + col * this.bandCount;
|
||||||
|
return data.slice(offset, offset + this.bandCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pixelContext) {
|
||||||
|
createPixelContext();
|
||||||
|
}
|
||||||
|
pixelContext.clearRect(0, 0, 1, 1);
|
||||||
|
|
||||||
|
let data;
|
||||||
|
const image = this.tile.getImage();
|
||||||
|
try {
|
||||||
|
pixelContext.drawImage(image, col, row, 1, 1, 0, 0, 1, 1);
|
||||||
|
data = pixelContext.getImageData(0, 0, 1, 1).data;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TileTexture;
|
export default TileTexture;
|
||||||
|
|||||||
75
test/browser/spec/ol/layer/Image.test.js
Normal file
75
test/browser/spec/ol/layer/Image.test.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import ImageLayer from '../../../../../src/ol/layer/Image.js';
|
||||||
|
import Map from '../../../../../src/ol/Map.js';
|
||||||
|
import Static from '../../../../../src/ol/source/ImageStatic.js';
|
||||||
|
import View from '../../../../../src/ol/View.js';
|
||||||
|
import {Projection} from '../../../../../src/ol/proj.js';
|
||||||
|
|
||||||
|
describe('ol/layer/Image', () => {
|
||||||
|
describe('getData()', () => {
|
||||||
|
let map, target, layer;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
const projection = new Projection({
|
||||||
|
code: 'custom-image',
|
||||||
|
units: 'pixels',
|
||||||
|
extent: [0, 0, 200, 200],
|
||||||
|
});
|
||||||
|
|
||||||
|
target = document.createElement('div');
|
||||||
|
target.style.width = '100px';
|
||||||
|
target.style.height = '100px';
|
||||||
|
document.body.appendChild(target);
|
||||||
|
|
||||||
|
const imageExtent = [0, 0, 20, 20];
|
||||||
|
const source = new Static({
|
||||||
|
url: 'spec/ol/data/dot.png',
|
||||||
|
projection: projection,
|
||||||
|
imageExtent: imageExtent,
|
||||||
|
});
|
||||||
|
|
||||||
|
layer = new ImageLayer({
|
||||||
|
source: source,
|
||||||
|
extent: imageExtent,
|
||||||
|
});
|
||||||
|
|
||||||
|
map = new Map({
|
||||||
|
pixelRatio: 1,
|
||||||
|
target: target,
|
||||||
|
layers: [layer],
|
||||||
|
view: new View({
|
||||||
|
projection: projection,
|
||||||
|
center: [10, 10],
|
||||||
|
zoom: 1,
|
||||||
|
maxZoom: 8,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
map.once('rendercomplete', () => {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
map.setTarget(null);
|
||||||
|
document.body.removeChild(target);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not detect pixels outside of the layer extent', () => {
|
||||||
|
map.renderSync();
|
||||||
|
const pixel = [10, 10];
|
||||||
|
const data = layer.getData(pixel);
|
||||||
|
expect(data).to.be(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect pixels in the layer extent', () => {
|
||||||
|
map.renderSync();
|
||||||
|
const pixel = [50, 50];
|
||||||
|
const data = layer.getData(pixel);
|
||||||
|
expect(data).to.be.a(Uint8ClampedArray);
|
||||||
|
expect(data.length).to.be(4);
|
||||||
|
expect(data[0]).to.be(255);
|
||||||
|
expect(data[1]).to.be(255);
|
||||||
|
expect(data[2]).to.be(255);
|
||||||
|
expect(data[3]).to.be(255);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@ import TileLayer from '../../../../../src/ol/layer/Tile.js';
|
|||||||
import {Map, View} from '../../../../../src/ol/index.js';
|
import {Map, View} from '../../../../../src/ol/index.js';
|
||||||
import {OSM, XYZ} from '../../../../../src/ol/source.js';
|
import {OSM, XYZ} from '../../../../../src/ol/source.js';
|
||||||
|
|
||||||
describe('ol.layer.Tile', function () {
|
describe('ol/layer/Tile', function () {
|
||||||
describe('constructor (defaults)', function () {
|
describe('constructor (defaults)', function () {
|
||||||
let layer;
|
let layer;
|
||||||
|
|
||||||
@@ -29,6 +29,48 @@ describe('ol.layer.Tile', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getData()', () => {
|
||||||
|
let map, target, layer;
|
||||||
|
beforeEach((done) => {
|
||||||
|
target = document.createElement('div');
|
||||||
|
target.style.width = '100px';
|
||||||
|
target.style.height = '100px';
|
||||||
|
document.body.appendChild(target);
|
||||||
|
|
||||||
|
layer = new TileLayer({
|
||||||
|
source: new XYZ({
|
||||||
|
url: 'spec/ol/data/osm-0-0-0.png',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
map = new Map({
|
||||||
|
target: target,
|
||||||
|
layers: [layer],
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
map.once('rendercomplete', () => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
map.setTarget(null);
|
||||||
|
document.body.removeChild(target);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets pixel data', () => {
|
||||||
|
const data = layer.getData([50, 50]);
|
||||||
|
expect(data).to.be.a(Uint8ClampedArray);
|
||||||
|
expect(data.length).to.be(4);
|
||||||
|
expect(data[0]).to.be(181);
|
||||||
|
expect(data[1]).to.be(208);
|
||||||
|
expect(data[2]).to.be(208);
|
||||||
|
expect(data[3]).to.be(255);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('frameState.animate after tile transition with layer opacity', function () {
|
describe('frameState.animate after tile transition with layer opacity', function () {
|
||||||
let target, map;
|
let target, map;
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import DataTileSource from '../../../../../src/ol/source/DataTile.js';
|
import DataTileSource from '../../../../../src/ol/source/DataTile.js';
|
||||||
|
import GeoTIFF from '../../../../../src/ol/source/GeoTIFF.js';
|
||||||
import Map from '../../../../../src/ol/Map.js';
|
import Map from '../../../../../src/ol/Map.js';
|
||||||
import View from '../../../../../src/ol/View.js';
|
import View from '../../../../../src/ol/View.js';
|
||||||
import WebGLHelper from '../../../../../src/ol/webgl/Helper.js';
|
import WebGLHelper from '../../../../../src/ol/webgl/Helper.js';
|
||||||
@@ -53,6 +54,80 @@ describe('ol/layer/WebGLTile', function () {
|
|||||||
map.getLayers().forEach((layer) => layer.dispose());
|
map.getLayers().forEach((layer) => layer.dispose());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getData()', () => {
|
||||||
|
/** @type {Map} */
|
||||||
|
let map;
|
||||||
|
let target;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
target = document.createElement('div');
|
||||||
|
target.style.width = '100px';
|
||||||
|
target.style.height = '100px';
|
||||||
|
document.body.appendChild(target);
|
||||||
|
map = new Map({
|
||||||
|
target: target,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
map.setTarget(null);
|
||||||
|
document.body.removeChild(target);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retrieves pixel data', (done) => {
|
||||||
|
const source = new GeoTIFF({
|
||||||
|
sources: [{url: 'spec/ol/source/images/0-0-0.tif'}],
|
||||||
|
});
|
||||||
|
|
||||||
|
const layer = new WebGLTileLayer({source: source});
|
||||||
|
|
||||||
|
map.addLayer(layer);
|
||||||
|
map.setView(source.getView());
|
||||||
|
|
||||||
|
map.once('rendercomplete', () => {
|
||||||
|
const data = layer.getData([50, 25]);
|
||||||
|
expect(data).to.be.a(Uint8Array);
|
||||||
|
expect(data.length).to.be(4);
|
||||||
|
expect(data[0]).to.be(255);
|
||||||
|
expect(data[1]).to.be(189);
|
||||||
|
expect(data[2]).to.be(103);
|
||||||
|
expect(data[3]).to.be(255);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves the original data type', (done) => {
|
||||||
|
const layer = new WebGLTileLayer({
|
||||||
|
source: new DataTileSource({
|
||||||
|
tilePixelRatio: 1 / 256,
|
||||||
|
loader(z, x, y) {
|
||||||
|
return new Float32Array([1.11, 2.22, 3.33, 4.44, 5.55]);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addLayer(layer);
|
||||||
|
map.setView(
|
||||||
|
new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 0,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
map.once('rendercomplete', () => {
|
||||||
|
const data = layer.getData([50, 25]);
|
||||||
|
expect(data).to.be.a(Float32Array);
|
||||||
|
expect(data.length).to.be(5);
|
||||||
|
expect(data[0]).to.roughlyEqual(1.11, 1e-5);
|
||||||
|
expect(data[1]).to.roughlyEqual(2.22, 1e-5);
|
||||||
|
expect(data[2]).to.roughlyEqual(3.33, 1e-5);
|
||||||
|
expect(data[3]).to.roughlyEqual(4.44, 1e-5);
|
||||||
|
expect(data[4]).to.roughlyEqual(5.55, 1e-5);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('dispose()', () => {
|
describe('dispose()', () => {
|
||||||
it('calls dispose on the renderer', () => {
|
it('calls dispose on the renderer', () => {
|
||||||
const renderer = layer.getRenderer();
|
const renderer = layer.getRenderer();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import VectorSource from '../../../../../../src/ol/source/Vector.js';
|
|||||||
import View from '../../../../../../src/ol/View.js';
|
import View from '../../../../../../src/ol/View.js';
|
||||||
import {get as getProj} from '../../../../../../src/ol/proj.js';
|
import {get as getProj} from '../../../../../../src/ol/proj.js';
|
||||||
|
|
||||||
describe('ol.renderer.canvas.ImageLayer', function () {
|
describe('ol/renderer/canvas/ImageLayer', function () {
|
||||||
describe('#forEachLayerAtCoordinate', function () {
|
describe('#forEachLayerAtCoordinate', function () {
|
||||||
let map, target, source;
|
let map, target, source;
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
@@ -42,11 +42,11 @@ describe('ol/webgl/TileTexture', function () {
|
|||||||
mapId: 'map-1',
|
mapId: 'map-1',
|
||||||
});
|
});
|
||||||
|
|
||||||
tileTexture = new TileTexture(
|
tileTexture = new TileTexture({
|
||||||
layer.getSource().getTile(3, 2, 1),
|
tile: layer.getSource().getTile(3, 2, 1),
|
||||||
layer.getSource().getTileGrid(),
|
grid: layer.getSource().getTileGrid(),
|
||||||
renderer.helper
|
helper: renderer.helper,
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user