Skip to content

Commit

Permalink
Merge pull request openlayers#13155 from tschaub/data-tile-interpolation
Browse files Browse the repository at this point in the history
Allow interpolation to be configured for data tile sources
  • Loading branch information
tschaub authored Dec 28, 2021
2 parents bec4b30 + e2883fb commit f0d488c
Show file tree
Hide file tree
Showing 59 changed files with 683 additions and 197 deletions.
24 changes: 24 additions & 0 deletions changelog/upgrade-notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
## Upgrade notes

### Next

#### New `interpolate` option for sources

Sources now have an `interpolate` option. This option controls whether data from the source is interpolated when resampling.

For `ol/source/DataTile` sources, the default is `interpolate: false`. This means that when a data tile source is used with a WebGL tile layer renderer, your style expression will have access to pixel values in the data tiles without interpolation. If this option is set to true, linear interpolation will be used when over- or under-sampling the data.

#### Deprecation of the `imageSmoothing` option for sources

The `imageSmoothing` option for sources has been deprecated and will be removed in the next major release. Use the `interpolate` option instead.

```js
// if you were using `imageSmoothing`
const before = new TileSource({
imageSmoothing: false
});

// use the `interpolate` option instead
const after = new TileSource({
interpolate: false
});
```

### v6.9.0

There should be nothing special required when upgrading from v6.8 to v6.9.
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,39 +1,32 @@
---
layout: example.html
title: Disable Image Smoothing
shortdesc: Example of disabling image smoothing
title: Interpolation
shortdesc: Example of data interpolation
docs: >
Example of disabling image smoothing when using raster DEM (digital elevation model) data.
The <code>imageSmoothing: false</code> setting is used to disable canvas image smoothing during
Example of data resampling when using raster DEM (digital elevation model) data.
The <code>interpolate: false</code> setting is used to disable interpolation of data values during
reprojection and rendering. Elevation data is
calculated from the pixel value returned by <b>forEachLayerAtPixel</b>. For comparison a second map
with smoothing enabled returns inaccuate elevations which are very noticeable close to 3107 meters
with interpolation enabled returns inaccuate elevations which are very noticeable close to 3107 meters
due to how elevation is calculated from the pixel value.
tags: "disable image smoothing, xyz, maptiler, reprojection"
tags: "disable image interpolation, xyz, maptiler, reprojection"
cloak:
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
value: Get your own API key at https://www.maptiler.com/cloud/
---
<div class="wrapper">
<div class="half">
<h4>Smoothing Disabled</h4>
<h4>Not Interpolated</h4>
<div id="map1" class="map"></div>
<div>
<label>
Elevation
<span id="info1">0.0</span> meters
</label>
</div>
<div>
<label>
Imagery opacity
<input id="opacity" type="range" min="0" max="100" value="80" />
<span id="output"></span> %
</label>
</div>
</div>
<div class="half">
<h4>Uncorrected Comparison</h4>
<h4>Interpolated</h4>
<div id="map2" class="map"></div>
<div>
<label>
Expand Down
48 changes: 7 additions & 41 deletions examples/disable-image-smoothing.js → examples/interpolation.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const attributions =
'<a href="https://www.maptiler.com/copyright/" target="_blank">&copy; MapTiler</a> ' +
'<a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>';

const disabledLayer = new TileLayer({
const notInterpolated = new TileLayer({
// specify className so forEachLayerAtPixel can distinguish layers
className: 'ol-layer-dem',
source: new XYZ({
Expand All @@ -18,21 +18,11 @@ const disabledLayer = new TileLayer({
tileSize: 512,
maxZoom: 12,
crossOrigin: '',
imageSmoothing: false,
interpolate: false,
}),
});

const imagery = new TileLayer({
className: 'ol-layer-imagery',
source: new XYZ({
attributions: attributions,
url: 'https://api.maptiler.com/tiles/satellite/{z}/{x}/{y}.jpg?key=' + key,
maxZoom: 20,
crossOrigin: '',
}),
});

const enabledLayer = new TileLayer({
const interpolated = new TileLayer({
source: new XYZ({
attributions: attributions,
url:
Expand All @@ -43,30 +33,6 @@ const enabledLayer = new TileLayer({
}),
});

imagery.on('prerender', function (evt) {
// use opaque background to conceal DEM while fully opaque imagery renders
if (imagery.getOpacity() === 1) {
evt.context.fillStyle = 'white';
evt.context.fillRect(
0,
0,
evt.context.canvas.width,
evt.context.canvas.height
);
}
});

const control = document.getElementById('opacity');
const output = document.getElementById('output');
const listener = function () {
output.innerText = control.value;
imagery.setOpacity(control.value / 100);
};
control.addEventListener('input', listener);
control.addEventListener('change', listener);
output.innerText = control.value;
imagery.setOpacity(control.value / 100);

const view = new View({
center: [6.893, 45.8295],
zoom: 16,
Expand All @@ -75,13 +41,13 @@ const view = new View({

const map1 = new Map({
target: 'map1',
layers: [disabledLayer, imagery],
layers: [notInterpolated],
view: view,
});

const map2 = new Map({
target: 'map2',
layers: [enabledLayer],
layers: [interpolated],
view: view,
});

Expand All @@ -101,7 +67,7 @@ const showElevations = function (evt) {
},
{
layerFilter: function (layer) {
return layer === disabledLayer;
return layer === notInterpolated;
},
}
);
Expand All @@ -114,7 +80,7 @@ const showElevations = function (evt) {
},
{
layerFilter: function (layer) {
return layer === enabledLayer;
return layer === interpolated;
},
}
);
Expand Down
4 changes: 2 additions & 2 deletions examples/reprojection-image.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
---
<div id="map" class="map"></div>
<div>
<input type="checkbox" id="imageSmoothing" checked />
<label for="imageSmoothing">Image smoothing</label>
<input type="checkbox" id="interpolate" checked />
<label for="interpolate">Interpolate</label>
</div>
6 changes: 3 additions & 3 deletions examples/reprojection-image.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const map = new Map({
}),
});

const imageSmoothing = document.getElementById('imageSmoothing');
const interpolate = document.getElementById('interpolate');

function setSource() {
const source = new Static({
Expand All @@ -44,10 +44,10 @@ function setSource() {
crossOrigin: '',
projection: 'EPSG:27700',
imageExtent: imageExtent,
imageSmoothing: imageSmoothing.checked,
interpolate: interpolate.checked,
});
imageLayer.setSource(source);
}
setSource();

imageSmoothing.addEventListener('change', setSource);
interpolate.addEventListener('change', setSource);
23 changes: 21 additions & 2 deletions src/ol/DataTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import TileState from './TileState.js';
/**
* @typedef {Object} Options
* @property {import("./tilecoord.js").TileCoord} tileCoord Tile coordinate.
* @property {function() : Promise<Data>} loader Data loader.
* @property {function(): Promise<Data>} loader Data loader.
* @property {number} [transition=250] A duration for tile opacity
* transitions in milliseconds. A duration of 0 disables the opacity transition.
* @property {boolean} [interpolate=false] Use interpolated values when resampling. By default,
* the nearest neighbor is used when resampling.
* @api
*/

Expand All @@ -26,10 +28,27 @@ class DataTile extends Tile {
constructor(options) {
const state = TileState.IDLE;

super(options.tileCoord, state, {transition: options.transition});
super(options.tileCoord, state, {
transition: options.transition,
interpolate: options.interpolate,
});

/**
* @type {function(): Promise<Data>}
* @private
*/
this.loader_ = options.loader;

/**
* @type {Data}
* @private
*/
this.data_ = null;

/**
* @type {Error}
* @private
*/
this.error_ = null;
}

Expand Down
7 changes: 7 additions & 0 deletions src/ol/Tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import {easeIn} from './easing.js';
* @typedef {Object} Options
* @property {number} [transition=250] A duration for tile opacity
* transitions in milliseconds. A duration of 0 disables the opacity transition.
* @property {boolean} [interpolate=false] Use interpolated values when resampling. By default,
* the nearest neighbor is used when resampling.
* @api
*/

Expand Down Expand Up @@ -123,6 +125,11 @@ class Tile extends EventTarget {
* @type {Object<string, number>}
*/
this.transitionStarts_ = {};

/**
* @type {boolean}
*/
this.interpolate = !!options.interpolate;
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/ol/renderer/canvas/ImageLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import CanvasLayerRenderer from './Layer.js';
import ViewHint from '../../ViewHint.js';
import {ENABLE_RASTER_REPROJECTION} from '../../reproj/common.js';
import {IMAGE_SMOOTHING_DISABLED} from './common.js';
import {assign} from '../../obj.js';
import {compose as composeTransform, makeInverse} from '../../transform.js';
import {containsExtent, intersects as intersectsExtent} from '../../extent.js';
Expand Down Expand Up @@ -179,7 +180,10 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
const dw = img.width * transform[0];
const dh = img.height * transform[3];

assign(context, this.getLayer().getSource().getContextOptions());
if (!this.getLayer().getSource().getInterpolate()) {
assign(context, IMAGE_SMOOTHING_DISABLED);
}

this.preRender(context, frameState);
if (render && dw >= 0.5 && dh >= 0.5) {
const dx = transform[4];
Expand Down
6 changes: 5 additions & 1 deletion src/ol/renderer/canvas/TileLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import CanvasLayerRenderer from './Layer.js';
import TileRange from '../../TileRange.js';
import TileState from '../../TileState.js';
import {IMAGE_SMOOTHING_DISABLED} from './common.js';
import {
apply as applyTransform,
compose as composeTransform,
Expand Down Expand Up @@ -317,7 +318,10 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
this.clipUnrotated(context, frameState, layerExtent);
}

assign(context, tileSource.getContextOptions());
if (!tileSource.getInterpolate()) {
assign(context, IMAGE_SMOOTHING_DISABLED);
}

this.preRender(context, frameState);

this.renderedTiles.length = 0;
Expand Down
12 changes: 12 additions & 0 deletions src/ol/renderer/canvas/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @module ol/renderer/canvas/common
*/

/**
* Context options to disable image smoothing.
* @type {Object}
*/
export const IMAGE_SMOOTHING_DISABLED = {
imageSmoothingEnabled: false,
msImageSmoothingEnabled: false,
};
21 changes: 12 additions & 9 deletions src/ol/reproj.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @module ol/reproj
*/
import {IMAGE_SMOOTHING_DISABLED} from './source/common.js';
import {IMAGE_SMOOTHING_DISABLED} from './renderer/canvas/common.js';
import {assign} from './obj.js';
import {
containsCoordinate,
Expand Down Expand Up @@ -198,7 +198,7 @@ export function calculateSourceExtentResolution(
* @param {Array<ImageExtent>} sources Array of sources.
* @param {number} gutter Gutter of the sources.
* @param {boolean} [opt_renderEdges] Render reprojection edges.
* @param {object} [opt_contextOptions] Properties to set on the canvas context.
* @param {object} [opt_interpolate] Use linear interpolation when resampling.
* @return {HTMLCanvasElement} Canvas with reprojected data.
*/
export function render(
Expand All @@ -213,13 +213,16 @@ export function render(
sources,
gutter,
opt_renderEdges,
opt_contextOptions
opt_interpolate
) {
const context = createCanvasContext2D(
Math.round(pixelRatio * width),
Math.round(pixelRatio * height)
);
assign(context, opt_contextOptions);

if (!opt_interpolate) {
assign(context, IMAGE_SMOOTHING_DISABLED);
}

if (sources.length === 0) {
return context.canvas;
Expand All @@ -244,7 +247,10 @@ export function render(
Math.round((pixelRatio * canvasWidthInUnits) / sourceResolution),
Math.round((pixelRatio * canvasHeightInUnits) / sourceResolution)
);
assign(stitchContext, opt_contextOptions);

if (!opt_interpolate) {
assign(stitchContext, IMAGE_SMOOTHING_DISABLED);
}

const stitchScale = pixelRatio / sourceResolution;

Expand Down Expand Up @@ -341,10 +347,7 @@ export function render(
context.save();
context.beginPath();

if (
isBrokenDiagonalRendering() ||
opt_contextOptions === IMAGE_SMOOTHING_DISABLED
) {
if (isBrokenDiagonalRendering() || !opt_interpolate) {
// Make sure that all lines are horizontal or vertical
context.moveTo(u1, v1);
// This is the diagonal line. Do it in 4 steps
Expand Down
Loading

0 comments on commit f0d488c

Please sign in to comment.