diff --git a/examples/cog-math.html b/examples/cog-math.html
new file mode 100644
index 0000000000..318f626e11
--- /dev/null
+++ b/examples/cog-math.html
@@ -0,0 +1,11 @@
+---
+layout: example.html
+title: NDVI from a Sentinel 2 COG
+shortdesc: Calculating NDVI and applying a custom color map.
+docs: >
+ 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)
+ from values in the two bands. The `interpolate` expression is used to map NDVI values to colors.
+tags: "cog, ndvi"
+---
+
diff --git a/examples/cog-math.js b/examples/cog-math.js
new file mode 100644
index 0000000000..452f84fccd
--- /dev/null
+++ b/examples/cog-math.js
@@ -0,0 +1,105 @@
+import GeoTIFF from '../src/ol/source/GeoTIFF.js';
+import Map from '../src/ol/Map.js';
+import Projection from '../src/ol/proj/Projection.js';
+import TileLayer from '../src/ol/layer/WebGLTile.js';
+import View from '../src/ol/View.js';
+import proj4 from 'proj4';
+import {getCenter} from '../src/ol/extent.js';
+import {register} from '../src/ol/proj/proj4.js';
+
+proj4.defs('EPSG:32636', '+proj=utm +zone=36 +datum=WGS84 +units=m +no_defs');
+register(proj4);
+
+const projection = new Projection({
+ code: 'EPSG:32636',
+ extent: [166021.44, 0.0, 534994.66, 9329005.18],
+});
+
+// metadata from https://s3.us-west-2.amazonaws.com/sentinel-cogs/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/S2A_36QWD_20200701_0_L2A.json
+const sourceExtent = [499980, 1790220, 609780, 1900020];
+
+const map = new Map({
+ target: 'map',
+ layers: [
+ 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: new GeoTIFF({
+ sources: [
+ {
+ // visible red, band 1 in the style expression above
+ url:
+ 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/B04.tif',
+ nodata: 0,
+ max: 10000,
+ },
+ {
+ // near infrared, band 2 in the style expression above
+ url:
+ 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/B08.tif',
+ nodata: 0,
+ max: 10000,
+ },
+ ],
+ }),
+ extent: sourceExtent,
+ }),
+ ],
+ view: new View({
+ center: getCenter(sourceExtent),
+ extent: sourceExtent,
+ zoom: 9,
+ projection: projection,
+ }),
+});
diff --git a/examples/cog-overviews.html b/examples/cog-overviews.html
new file mode 100644
index 0000000000..757d03225d
--- /dev/null
+++ b/examples/cog-overviews.html
@@ -0,0 +1,12 @@
+---
+layout: example.html
+title: GeoTIFF with Overviews
+shortdesc: Rendering a GeoTIFF with external overviews as a layer.
+docs: >
+ In some cases, a GeoTIFF may have external overviews. This example uses the
+ `overviews` property to provide URLs for the external overviews. The example
+ composes a false color composite using shortwave infrared (B6), near infrared (B5),
+ and visible green (B3) bands from a Landsat 8 image.
+tags: "cog"
+---
+
diff --git a/examples/cog-overviews.js b/examples/cog-overviews.js
new file mode 100644
index 0000000000..d0e46697dc
--- /dev/null
+++ b/examples/cog-overviews.js
@@ -0,0 +1,68 @@
+import GeoTIFF from '../src/ol/source/GeoTIFF.js';
+import Map from '../src/ol/Map.js';
+import Projection from '../src/ol/proj/Projection.js';
+import TileLayer from '../src/ol/layer/WebGLTile.js';
+import View from '../src/ol/View.js';
+import proj4 from 'proj4';
+import {getCenter} from '../src/ol/extent.js';
+import {register} from '../src/ol/proj/proj4.js';
+
+proj4.defs('EPSG:32645', '+proj=utm +zone=45 +datum=WGS84 +units=m +no_defs');
+register(proj4);
+
+const projection = new Projection({
+ code: 'EPSG:32645',
+ extent: [166021.44, 0.0, 534994.66, 9329005.18],
+});
+
+const sourceExtent = [382200, 2279370, 610530, 2512500];
+
+const base =
+ 'https://landsat-pds.s3.amazonaws.com/c1/L8/139/045/LC08_L1TP_139045_20170304_20170316_01_T1/LC08_L1TP_139045_20170304_20170316_01_T1';
+
+// scale values in this range to 0 - 1
+const min = 10000;
+const max = 15000;
+
+const map = new Map({
+ target: 'map',
+ layers: [
+ new TileLayer({
+ extent: sourceExtent,
+ style: {
+ saturation: -0.3,
+ },
+ source: new GeoTIFF({
+ sources: [
+ {
+ url: `${base}_B6.TIF`,
+ overviews: [`${base}_B6.TIF.ovr`],
+ min: min,
+ max: max,
+ nodata: 0,
+ },
+ {
+ url: `${base}_B5.TIF`,
+ overviews: [`${base}_B5.TIF.ovr`],
+ min: min,
+ max: max,
+ nodata: 0,
+ },
+ {
+ url: `${base}_B3.TIF`,
+ overviews: [`${base}_B3.TIF.ovr`],
+ min: min,
+ max: max,
+ nodata: 0,
+ },
+ ],
+ }),
+ }),
+ ],
+ view: new View({
+ center: getCenter(sourceExtent),
+ extent: sourceExtent,
+ zoom: 8,
+ projection: projection,
+ }),
+});
diff --git a/examples/cog.html b/examples/cog.html
new file mode 100644
index 0000000000..2328e224ec
--- /dev/null
+++ b/examples/cog.html
@@ -0,0 +1,11 @@
+---
+layout: example.html
+title: Cloud Optimized GeoTIFF (COG)
+shortdesc: Rendering a COG as a tiled layer.
+docs: >
+ Tiled data from a Cloud Optimized GeoTIFF (COG) can be rendered as a layer. In this
+ example, a single 3-band GeoTIFF is used to render RGB data. The `nodata` property is
+ used to avoid rendering pixels where all three bands are 0.
+tags: "cog"
+---
+
diff --git a/examples/cog.js b/examples/cog.js
new file mode 100644
index 0000000000..411214f6a7
--- /dev/null
+++ b/examples/cog.js
@@ -0,0 +1,43 @@
+import GeoTIFF from '../src/ol/source/GeoTIFF.js';
+import Map from '../src/ol/Map.js';
+import Projection from '../src/ol/proj/Projection.js';
+import TileLayer from '../src/ol/layer/WebGLTile.js';
+import View from '../src/ol/View.js';
+import proj4 from 'proj4';
+import {getCenter} from '../src/ol/extent.js';
+import {register} from '../src/ol/proj/proj4.js';
+
+proj4.defs('EPSG:32636', '+proj=utm +zone=36 +datum=WGS84 +units=m +no_defs');
+register(proj4);
+
+const projection = new Projection({
+ code: 'EPSG:32636',
+ extent: [166021.44, 0.0, 534994.66, 9329005.18],
+});
+
+// metadata from https://s3.us-west-2.amazonaws.com/sentinel-cogs/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/S2A_36QWD_20200701_0_L2A.json
+const sourceExtent = [499980, 1790220, 609780, 1900020];
+
+const map = new Map({
+ target: 'map',
+ layers: [
+ new TileLayer({
+ source: new GeoTIFF({
+ sources: [
+ {
+ url:
+ 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/2020/S2A_36QWD_20200701_0_L2A/TCI.tif',
+ nodata: 0,
+ },
+ ],
+ }),
+ extent: sourceExtent,
+ }),
+ ],
+ view: new View({
+ center: getCenter(sourceExtent),
+ extent: sourceExtent,
+ zoom: 9,
+ projection: projection,
+ }),
+});
diff --git a/examples/data-tiles.html b/examples/data-tiles.html
new file mode 100644
index 0000000000..99ed41bebc
--- /dev/null
+++ b/examples/data-tiles.html
@@ -0,0 +1,9 @@
+---
+layout: example.html
+title: Data Tiles
+shortdesc: Generating tile data from scratch.
+docs: >
+ This example generates RGBA tile data from scratch.
+tags: "data tiles"
+---
+
diff --git a/examples/data-tiles.js b/examples/data-tiles.js
new file mode 100644
index 0000000000..aa34aa406c
--- /dev/null
+++ b/examples/data-tiles.js
@@ -0,0 +1,43 @@
+import DataTile from '../src/ol/source/DataTile.js';
+import Map from '../src/ol/Map.js';
+import TileLayer from '../src/ol/layer/WebGLTile.js';
+import View from '../src/ol/View.js';
+
+const size = 256;
+
+const canvas = document.createElement('canvas');
+canvas.width = size;
+canvas.height = size;
+
+const context = canvas.getContext('2d');
+context.strokeStyle = 'white';
+context.textAlign = 'center';
+context.font = '24px sans-serif';
+const lineHeight = 30;
+
+const map = new Map({
+ target: 'map',
+ layers: [
+ new TileLayer({
+ source: new DataTile({
+ loader: function (z, x, y) {
+ const half = size / 2;
+ context.clearRect(0, 0, size, size);
+ context.fillStyle = 'rgba(100, 100, 100, 0.5)';
+ context.fillRect(0, 0, size, size);
+ context.fillStyle = 'black';
+ context.fillText(`z: ${z}`, half, half - lineHeight);
+ context.fillText(`x: ${x}`, half, half);
+ context.fillText(`y: ${y}`, half, half + lineHeight);
+ context.strokeRect(0, 0, size, size);
+ const data = context.getImageData(0, 0, size, size).data;
+ return Promise.resolve(data);
+ },
+ }),
+ }),
+ ],
+ view: new View({
+ center: [0, 0],
+ zoom: 0,
+ }),
+});
diff --git a/examples/webgl-sea-level.css b/examples/webgl-sea-level.css
new file mode 100644
index 0000000000..56b832a021
--- /dev/null
+++ b/examples/webgl-sea-level.css
@@ -0,0 +1,13 @@
+#level {
+ display: inline-block;
+ width: 150px;
+ vertical-align: text-bottom;
+}
+
+a.location {
+ cursor: pointer;
+}
+
+#map {
+ background: #8bd4ff;
+}
diff --git a/examples/webgl-sea-level.html b/examples/webgl-sea-level.html
new file mode 100644
index 0000000000..71dcc94131
--- /dev/null
+++ b/examples/webgl-sea-level.html
@@ -0,0 +1,37 @@
+---
+layout: example.html
+title: Sea Level (with WebGL)
+shortdesc: Render sea level at different elevations
+docs: >
+
+ The style property of a WebGL tile layer accepts a color expression that
+ can be used to modify pixel values before rendering. Here, RGB tiles representing elevation
+ data are loaded and rendered so that values at or below sea level are blue, and values
+ above sea level are transparent. The color expression operates on normalized pixel
+ values ranging from 0 to 1. The band operator is used to select normalized values
+ from a single band.
+
+ After converting the normalized RGB values to elevation, the interpolate expression
+ is used to pick colors to apply at a given elevation. Instead of using constant
+ numeric values as the stops in the colors array, the var operator allows you to
+ use a value that can be modified by your application. When the user drags the
+ sea level slider, the layer.updateStyleVariables() function is called to update
+ the level style variable with the value from the slider.
+
+tags: "webgl, math, flood"
+cloak:
+ - key: get_your_own_D6rA4zTHduk6KOKTXzGB
+ value: Get your own API key at https://www.maptiler.com/cloud/
+---
+
+
+ Sea level
+
+ + m
+
+
+Go to
+San Francisco ,
+New York ,
+Mumbai , or
+Shanghai
diff --git a/examples/webgl-sea-level.js b/examples/webgl-sea-level.js
new file mode 100644
index 0000000000..7490d17b5f
--- /dev/null
+++ b/examples/webgl-sea-level.js
@@ -0,0 +1,90 @@
+import Map from '../src/ol/Map.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 {fromLonLat} from '../src/ol/proj.js';
+
+const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
+const attributions =
+ '© MapTiler ' +
+ '© OpenStreetMap contributors ';
+
+const elevation = new TileLayer({
+ opacity: 0.6,
+ source: new XYZ({
+ url:
+ 'https://api.maptiler.com/tiles/terrain-rgb/{z}/{x}/{y}.png?key=' + key,
+ maxZoom: 10,
+ tileSize: 512,
+ crossOrigin: 'anonymous',
+ }),
+ style: {
+ variables: {
+ level: 0,
+ },
+ color: [
+ 'interpolate',
+ ['linear'],
+ // band math operates on normalized values from 0-1
+ // so we scale by 255 to align with the elevation formula
+ // from https://cloud.maptiler.com/tiles/terrain-rgb/
+ [
+ '+',
+ -10000,
+ [
+ '*',
+ 0.1 * 255,
+ [
+ '+',
+ ['*', 256 * 256, ['band', 1]],
+ ['+', ['*', 256, ['band', 2]], ['band', 3]],
+ ],
+ ],
+ ],
+ // use the `level` style variable as a stop in the color ramp
+ ['var', 'level'],
+ [139, 212, 255, 1],
+ ['+', 0.01, ['var', 'level']],
+ [139, 212, 255, 0],
+ ],
+ },
+});
+
+const map = new Map({
+ target: 'map',
+ layers: [
+ new TileLayer({
+ source: new XYZ({
+ url: 'https://api.maptiler.com/maps/streets/{z}/{x}/{y}.png?key=' + key,
+ attributions: attributions,
+ crossOrigin: 'anonymous',
+ tileSize: 512,
+ }),
+ }),
+ elevation,
+ ],
+ view: new View({
+ center: fromLonLat([-122.3267, 37.8377]),
+ zoom: 11,
+ }),
+});
+
+const control = document.getElementById('level');
+const output = document.getElementById('output');
+control.addEventListener('input', function () {
+ output.innerText = control.value;
+ elevation.updateStyleVariables({level: parseFloat(control.value)});
+});
+output.innerText = control.value;
+
+const locations = document.getElementsByClassName('location');
+for (let i = 0, ii = locations.length; i < ii; ++i) {
+ locations[i].addEventListener('click', relocate);
+}
+
+function relocate(event) {
+ const data = event.target.dataset;
+ const view = map.getView();
+ view.setCenter(fromLonLat(data.center.split(',').map(Number)));
+ view.setZoom(Number(data.zoom));
+}
diff --git a/examples/webgl-tile-style.css b/examples/webgl-tile-style.css
new file mode 100644
index 0000000000..a24af258c9
--- /dev/null
+++ b/examples/webgl-tile-style.css
@@ -0,0 +1,4 @@
+#controls {
+ display: flex;
+ justify-content: space-around;
+}
diff --git a/examples/webgl-tile-style.html b/examples/webgl-tile-style.html
new file mode 100644
index 0000000000..e94e9b17d7
--- /dev/null
+++ b/examples/webgl-tile-style.html
@@ -0,0 +1,31 @@
+---
+layout: example.html
+title: WebGL Tile Layer Styles
+shortdesc: Styling raster tiles with WebGL.
+docs: >
+ The `style` property of a WebGL tile layer can be used to adjust properties like
+ `exposure`, `contrast`, and `saturation`. Typically those values would be set to
+ numeric constants to apply a filter to imagery. In this example, the style properties
+ are set to variables that can be updated based on application state. Adjusting the
+ sliders results in a call to `layer.updateStyleVariables()` to use new values for the
+ associated style properties.
+tags: "webgl, style"
+cloak:
+ - key: get_your_own_D6rA4zTHduk6KOKTXzGB
+ value: Get your own API key at https://www.maptiler.com/cloud/
+---
+
+
+
+
+ exposure
+
+
+
+ contrast
+
+
+
+ saturation
+
+
\ No newline at end of file
diff --git a/examples/webgl-tile-style.js b/examples/webgl-tile-style.js
new file mode 100644
index 0000000000..fce4addba1
--- /dev/null
+++ b/examples/webgl-tile-style.js
@@ -0,0 +1,55 @@
+import Map from '../src/ol/Map.js';
+import TileLayer from '../src/ol/layer/WebGLTile.js';
+import View from '../src/ol/View.js';
+import XYZ from '../src/ol/source/XYZ.js';
+
+const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
+const attributions =
+ '© MapTiler ' +
+ '© OpenStreetMap contributors ';
+
+const variables = {
+ exposure: 0,
+ contrast: 0,
+ saturation: 0,
+};
+
+const layer = new TileLayer({
+ style: {
+ exposure: ['var', 'exposure'],
+ contrast: ['var', 'contrast'],
+ saturation: ['var', 'saturation'],
+ variables: variables,
+ },
+ source: new XYZ({
+ crossOrigin: 'anonymous', // TODO: determine if we can avoid this
+ attributions: attributions,
+ url: 'https://api.maptiler.com/tiles/satellite/{z}/{x}/{y}.jpg?key=' + key,
+ maxZoom: 20,
+ }),
+});
+
+const map = new Map({
+ target: 'map',
+ layers: [layer],
+ view: new View({
+ center: [0, 0],
+ zoom: 0,
+ }),
+});
+
+for (const name in variables) {
+ const element = document.getElementById(name);
+ const value = variables[name];
+ element.value = value.toString();
+ document.getElementById(`${name}-value`).innerText = `(${value})`;
+
+ element.addEventListener('input', function (event) {
+ const value = parseFloat(event.target.value);
+ document.getElementById(`${name}-value`).innerText = `(${value})`;
+
+ const updates = {};
+ updates[name] = value;
+ layer.updateStyleVariables(updates);
+ });
+}
diff --git a/examples/webgl-tiles.html b/examples/webgl-tiles.html
new file mode 100644
index 0000000000..31b40ffcf0
--- /dev/null
+++ b/examples/webgl-tiles.html
@@ -0,0 +1,9 @@
+---
+layout: example.html
+title: WebGL Tiles
+shortdesc: Rendering raster data with WebGL.
+docs: >
+ This example uses WebGL to raster tiles on a map.
+tags: "webgl, osm"
+---
+
diff --git a/examples/webgl-tiles.js b/examples/webgl-tiles.js
new file mode 100644
index 0000000000..4353f3043c
--- /dev/null
+++ b/examples/webgl-tiles.js
@@ -0,0 +1,17 @@
+import Map from '../src/ol/Map.js';
+import OSM from '../src/ol/source/OSM.js';
+import TileLayer from '../src/ol/layer/WebGLTile.js';
+import View from '../src/ol/View.js';
+
+const map = new Map({
+ target: 'map',
+ layers: [
+ new TileLayer({
+ source: new OSM(),
+ }),
+ ],
+ view: new View({
+ center: [0, 0],
+ zoom: 0,
+ }),
+});
diff --git a/examples/webpack/config.mjs b/examples/webpack/config.mjs
index 4f02305c56..81445dc823 100644
--- a/examples/webpack/config.mjs
+++ b/examples/webpack/config.mjs
@@ -101,6 +101,8 @@ export default {
resolve: {
fallback: {
fs: false,
+ http: false,
+ https: false,
},
alias: {
// allow imports from 'ol/module' instead of specifiying the source path
diff --git a/package-lock.json b/package-lock.json
index d16cab342f..3a67195d2f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "6.6.2-dev",
"license": "BSD-2-Clause",
"dependencies": {
+ "geotiff": "^1.0.0-beta.16",
"ol-mapbox-style": "^6.4.1",
"pbf": "3.2.1",
"rbush": "^3.0.1"
@@ -1837,6 +1838,15 @@
"eslint": ">=5.1.0"
}
},
+ "node_modules/@petamoriken/float16": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-1.1.1.tgz",
+ "integrity": "sha512-0r8nE5Q60tj3FbWWYLjAdGnWZgP7CMWXNaI5UsNzypRyxLDb/uvOl5SDw8GcPNu6pSTOt+KSI+0oL6fhSpNOFQ==",
+ "dependencies": {
+ "lodash": ">=4.17.5 <5.0.0",
+ "lodash-es": ">=4.17.5 <5.0.0"
+ }
+ },
"node_modules/@rollup/plugin-babel": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz",
@@ -2968,7 +2978,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
"engines": {
"node": ">=6"
}
@@ -3336,6 +3345,11 @@
"node": ">= 0.6"
}
},
+ "node_modules/content-type-parser": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.2.tgz",
+ "integrity": "sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ=="
+ },
"node_modules/convert-source-map": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
@@ -3576,7 +3590,6 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
- "dev": true,
"dependencies": {
"ms": "2.1.2"
},
@@ -4659,6 +4672,27 @@
"node": ">=8"
}
},
+ "node_modules/eslint/node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/esm": {
+ "version": "3.2.25",
+ "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
+ "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/espree": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz",
@@ -5330,6 +5364,24 @@
"node": ">=6.9.0"
}
},
+ "node_modules/geotiff": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/geotiff/-/geotiff-1.0.4.tgz",
+ "integrity": "sha512-JmtpvVHlxvyrWgT6Uf0sy7flmXhjWtG0cqVv+G9fMcupV4DAPdTv7tkhsoMnn9RpIIwolveB/VnyII8cRMOD7A==",
+ "dependencies": {
+ "@petamoriken/float16": "^1.0.7",
+ "content-type-parser": "^1.0.2",
+ "lru-cache": "^6.0.0",
+ "pako": "^1.0.11",
+ "parse-headers": "^2.0.2",
+ "threads": "^1.3.1",
+ "txml": "^3.1.2"
+ },
+ "engines": {
+ "browsers": "defaults",
+ "node": ">=10.19"
+ }
+ },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -5924,8 +5976,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/internal-ip": {
"version": "6.2.0",
@@ -6190,6 +6241,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-observable": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-2.1.0.tgz",
+ "integrity": "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-path-cwd": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
@@ -7123,8 +7185,12 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
@@ -7292,7 +7358,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -7699,8 +7764,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/multicast-dns": {
"version": "6.2.3",
@@ -7947,6 +8011,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/observable-fns": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/observable-fns/-/observable-fns-0.5.1.tgz",
+ "integrity": "sha512-wf7g4Jpo1Wt2KIqZKLGeiuLOEMqpaOZ5gJn7DmSdqXgTdxRwSdBhWegQQpPteQ2gZvzCKqNNpwb853wcpA0j7A=="
+ },
"node_modules/obuf": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
@@ -8172,6 +8241,11 @@
"node": ">=6"
}
},
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -8184,6 +8258,11 @@
"node": ">=6"
}
},
+ "node_modules/parse-headers": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz",
+ "integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA=="
+ },
"node_modules/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
@@ -8926,7 +9005,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
- "dev": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -9965,7 +10043,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dev": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
@@ -9974,7 +10051,6 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -10314,18 +10390,53 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
+ "node_modules/threads": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/threads/-/threads-1.6.4.tgz",
+ "integrity": "sha512-A+9MQFAUha9W8MjIPmrvETy98qVmZFr5Unox9D95y7kvz3fBpGiFS7JOZs07B2KvTHoRNI5MrGudRVeCmv4Alw==",
+ "dependencies": {
+ "callsites": "^3.1.0",
+ "debug": "^4.2.0",
+ "is-observable": "^2.1.0",
+ "observable-fns": "^0.5.1"
+ },
+ "funding": {
+ "url": "https://github.com/andywer/threads.js?sponsor=1"
+ },
+ "optionalDependencies": {
+ "tiny-worker": ">= 2"
+ }
+ },
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
+ "node_modules/through2": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz",
+ "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==",
+ "dependencies": {
+ "inherits": "^2.0.4",
+ "readable-stream": "2 || 3"
+ }
+ },
"node_modules/thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
+ "node_modules/tiny-worker": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tiny-worker/-/tiny-worker-2.3.0.tgz",
+ "integrity": "sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==",
+ "optional": true,
+ "dependencies": {
+ "esm": "^3.2.25"
+ }
+ },
"node_modules/tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
@@ -10423,6 +10534,14 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
},
+ "node_modules/txml": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/txml/-/txml-3.2.5.tgz",
+ "integrity": "sha512-AtN8AgJLiDanttIXJaQlxH8/R0NOCNwto8kcO7BaxdLgsN9b7itM9lnTD7c2O3TadP+hHB9j7ra5XGFRPNnk/g==",
+ "dependencies": {
+ "through2": "^3.0.1"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -10630,8 +10749,7 @@
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "dev": true
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"node_modules/utils-merge": {
"version": "1.0.1",
@@ -11374,8 +11492,7 @@
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yargs": {
"version": "17.1.1",
@@ -12680,6 +12797,15 @@
"minimatch": "^3.0.4"
}
},
+ "@petamoriken/float16": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-1.1.1.tgz",
+ "integrity": "sha512-0r8nE5Q60tj3FbWWYLjAdGnWZgP7CMWXNaI5UsNzypRyxLDb/uvOl5SDw8GcPNu6pSTOt+KSI+0oL6fhSpNOFQ==",
+ "requires": {
+ "lodash": ">=4.17.5 <5.0.0",
+ "lodash-es": ">=4.17.5 <5.0.0"
+ }
+ },
"@rollup/plugin-babel": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz",
@@ -13604,8 +13730,7 @@
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
},
"camelcase": {
"version": "6.2.0",
@@ -13901,6 +14026,11 @@
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"dev": true
},
+ "content-type-parser": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.2.tgz",
+ "integrity": "sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ=="
+ },
"convert-source-map": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
@@ -14082,7 +14212,6 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
- "dev": true,
"requires": {
"ms": "2.1.2"
}
@@ -14675,6 +14804,12 @@
"requires": {
"has-flag": "^4.0.0"
}
+ },
+ "type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true
}
}
},
@@ -14922,6 +15057,12 @@
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
"dev": true
},
+ "esm": {
+ "version": "3.2.25",
+ "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
+ "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
+ "optional": true
+ },
"espree": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz",
@@ -15448,6 +15589,20 @@
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true
},
+ "geotiff": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/geotiff/-/geotiff-1.0.4.tgz",
+ "integrity": "sha512-JmtpvVHlxvyrWgT6Uf0sy7flmXhjWtG0cqVv+G9fMcupV4DAPdTv7tkhsoMnn9RpIIwolveB/VnyII8cRMOD7A==",
+ "requires": {
+ "@petamoriken/float16": "^1.0.7",
+ "content-type-parser": "^1.0.2",
+ "lru-cache": "^6.0.0",
+ "pako": "^1.0.11",
+ "parse-headers": "^2.0.2",
+ "threads": "^1.3.1",
+ "txml": "^3.1.2"
+ }
+ },
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -15895,8 +16050,7 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"internal-ip": {
"version": "6.2.0",
@@ -16071,6 +16225,11 @@
"integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==",
"dev": true
},
+ "is-observable": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-2.1.0.tgz",
+ "integrity": "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw=="
+ },
"is-path-cwd": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
@@ -16802,8 +16961,12 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"lodash.clonedeep": {
"version": "4.5.0",
@@ -16939,7 +17102,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
"requires": {
"yallist": "^4.0.0"
}
@@ -17249,8 +17411,7 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"multicast-dns": {
"version": "6.2.3",
@@ -17441,6 +17602,11 @@
"es-abstract": "^1.18.2"
}
},
+ "observable-fns": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/observable-fns/-/observable-fns-0.5.1.tgz",
+ "integrity": "sha512-wf7g4Jpo1Wt2KIqZKLGeiuLOEMqpaOZ5gJn7DmSdqXgTdxRwSdBhWegQQpPteQ2gZvzCKqNNpwb853wcpA0j7A=="
+ },
"obuf": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
@@ -17599,6 +17765,11 @@
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
+ "pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ },
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -17608,6 +17779,11 @@
"callsites": "^3.0.0"
}
},
+ "parse-headers": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz",
+ "integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA=="
+ },
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
@@ -18178,7 +18354,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
- "dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -19015,7 +19190,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dev": true,
"requires": {
"safe-buffer": "~5.2.0"
},
@@ -19023,8 +19197,7 @@
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}
}
},
@@ -19274,18 +19447,48 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
+ "threads": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/threads/-/threads-1.6.4.tgz",
+ "integrity": "sha512-A+9MQFAUha9W8MjIPmrvETy98qVmZFr5Unox9D95y7kvz3fBpGiFS7JOZs07B2KvTHoRNI5MrGudRVeCmv4Alw==",
+ "requires": {
+ "callsites": "^3.1.0",
+ "debug": "^4.2.0",
+ "is-observable": "^2.1.0",
+ "observable-fns": "^0.5.1",
+ "tiny-worker": ">= 2"
+ }
+ },
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
+ "through2": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz",
+ "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==",
+ "requires": {
+ "inherits": "^2.0.4",
+ "readable-stream": "2 || 3"
+ }
+ },
"thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
+ "tiny-worker": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tiny-worker/-/tiny-worker-2.3.0.tgz",
+ "integrity": "sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==",
+ "optional": true,
+ "requires": {
+ "esm": "^3.2.25"
+ }
+ },
"tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
@@ -19364,6 +19567,14 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
},
+ "txml": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/txml/-/txml-3.2.5.tgz",
+ "integrity": "sha512-AtN8AgJLiDanttIXJaQlxH8/R0NOCNwto8kcO7BaxdLgsN9b7itM9lnTD7c2O3TadP+hHB9j7ra5XGFRPNnk/g==",
+ "requires": {
+ "through2": "^3.0.1"
+ }
+ },
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -19518,8 +19729,7 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "dev": true
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"utils-merge": {
"version": "1.0.1",
@@ -20044,8 +20254,7 @@
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yargs": {
"version": "17.1.1",
diff --git a/package.json b/package.json
index 51a215d57e..f51d355612 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"url": "https://opencollective.com/openlayers"
},
"dependencies": {
+ "geotiff": "^1.0.0-beta.16",
"ol-mapbox-style": "^6.4.1",
"pbf": "3.2.1",
"rbush": "^3.0.1"
diff --git a/src/ol/DataTile.js b/src/ol/DataTile.js
new file mode 100644
index 0000000000..c63eeb5b49
--- /dev/null
+++ b/src/ol/DataTile.js
@@ -0,0 +1,76 @@
+/**
+ * @module ol/DataTile
+ */
+import Tile from './Tile.js';
+import TileState from './TileState.js';
+
+/**
+ * Data that can be used with a DataTile.
+ * @typedef {Uint8Array|Uint8ClampedArray|DataView} Data
+ */
+
+/**
+ * @typedef {Object} Options
+ * @property {import("./tilecoord.js").TileCoord} tileCoord Tile coordinate.
+ * @property {function() : Promise} loader Data loader.
+ * @property {number} [transition=250] A duration for tile opacity
+ * transitions in milliseconds. A duration of 0 disables the opacity transition.
+ * @api
+ */
+
+class DataTile extends Tile {
+ /**
+ * @param {Options} options Tile options.
+ */
+ constructor(options) {
+ const state = TileState.IDLE;
+
+ super(options.tileCoord, state, {transition: options.transition});
+
+ this.loader_ = options.loader;
+ this.data_ = null;
+ this.error_ = null;
+ }
+
+ /**
+ * Get the data for the tile.
+ * @return {Data} Tile data.
+ * @api
+ */
+ getData() {
+ return this.data_;
+ }
+
+ /**
+ * Get any loading error.
+ * @return {Error} Loading error.
+ * @api
+ */
+ getError() {
+ return this.error_;
+ }
+
+ /**
+ * Load not yet loaded URI.
+ * @api
+ */
+ load() {
+ this.state = TileState.LOADING;
+ this.changed();
+
+ const self = this;
+ this.loader_()
+ .then(function (data) {
+ self.data_ = data;
+ self.state = TileState.LOADED;
+ self.changed();
+ })
+ .catch(function (error) {
+ self.error_ = error;
+ self.state = TileState.ERROR;
+ self.changed();
+ });
+ }
+}
+
+export default DataTile;
diff --git a/src/ol/layer/WebGLTile.js b/src/ol/layer/WebGLTile.js
new file mode 100644
index 0000000000..820f42dbbd
--- /dev/null
+++ b/src/ol/layer/WebGLTile.js
@@ -0,0 +1,289 @@
+/**
+ * @module ol/layer/WebGLTile
+ */
+import BaseTileLayer from './BaseTile.js';
+import WebGLTileLayerRenderer, {
+ Attributes,
+ Uniforms,
+} from '../renderer/webgl/TileLayer.js';
+import {
+ ValueTypes,
+ expressionToGlsl,
+ getStringNumberEquivalent,
+ uniformNameForVariable,
+} from '../style/expressions.js';
+import {assign} from '../obj.js';
+
+/**
+ * @typedef {Object} Style
+ * @property {Object} [variables] Style variables. Each variable must hold a number.
+ * @property {import("../style/expressions.js").ExpressionValue} [color] An expression applied to color values.
+ * @property {import("../style/expressions.js").ExpressionValue} [brightness=0] Value used to decrease or increase
+ * the layer brightness. Values range from -1 to 1.
+ * @property {import("../style/expressions.js").ExpressionValue} [contrast=0] Value used to decrease or increase
+ * the layer contrast. Values range from -1 to 1.
+ * @property {import("../style/expressions.js").ExpressionValue} [exposure=0] Value used to decrease or increase
+ * the layer exposure. Values range from -1 to 1.
+ * @property {import("../style/expressions.js").ExpressionValue} [saturation=0] Value used to decrease or increase
+ * the layer saturation. Values range from -1 to 1.
+ * @property {import("../style/expressions.js").ExpressionValue} [gamma=1] Apply a gamma correction to the layer.
+ * Values range from 0 to infinity.
+ */
+
+/**
+ * @typedef {Object} Options
+ * @property {Style} [style] Style to apply to the layer.
+ * @property {string} [className='ol-layer'] A CSS class name to set to the layer element.
+ * @property {number} [opacity=1] Opacity (0, 1).
+ * @property {boolean} [visible=true] Visibility.
+ * @property {import("../extent.js").Extent} [extent] The bounding extent for layer rendering. The layer will not be
+ * rendered outside of this extent.
+ * @property {number} [zIndex] The z-index for layer rendering. At rendering time, the layers
+ * will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed
+ * for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()`
+ * method was used.
+ * @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be
+ * visible.
+ * @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
+ * be visible.
+ * @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
+ * visible.
+ * @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
+ * be visible.
+ * @property {number} [preload=0] Preload. Load low-resolution tiles up to `preload` levels. `0`
+ * means no preloading.
+ * @property {import("../source/Tile.js").default} [source] Source for this layer.
+ * @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
+ * temporary layers. The standard way to add a layer to a map and have it managed by the map is to
+ * use {@link module:ol/Map#addLayer}.
+ * @property {boolean} [useInterimTilesOnError=true] Use interim tiles on error.
+ */
+
+/**
+ * @typedef {Object} ParsedStyle
+ * @property {string} vertexShader The vertex shader.
+ * @property {string} fragmentShader The fragment shader.
+ * @property {Object} uniforms Uniform definitions.
+ */
+
+/**
+ * @param {Style} style The layer style.
+ * @param {number} bandCount The number of bands.
+ * @return {ParsedStyle} Shaders and uniforms generated from the style.
+ */
+function parseStyle(style, bandCount) {
+ const vertexShader = `
+ attribute vec2 ${Attributes.TEXTURE_COORD};
+ uniform mat4 ${Uniforms.TILE_TRANSFORM};
+ uniform float ${Uniforms.DEPTH};
+
+ varying vec2 v_textureCoord;
+
+ void main() {
+ v_textureCoord = ${Attributes.TEXTURE_COORD};
+ gl_Position = ${Uniforms.TILE_TRANSFORM} * vec4(${Attributes.TEXTURE_COORD}, ${Uniforms.DEPTH}, 1.0);
+ }
+ `;
+
+ /**
+ * @type {import("../style/expressions.js").ParsingContext}
+ */
+ const context = {
+ inFragmentShader: true,
+ variables: [],
+ attributes: [],
+ stringLiteralsMap: {},
+ bandCount: bandCount,
+ };
+
+ const pipeline = [];
+
+ if (style.color !== undefined) {
+ const color = expressionToGlsl(context, style.color, ValueTypes.COLOR);
+ pipeline.push(`color = ${color};`);
+ }
+
+ if (style.contrast !== undefined) {
+ const contrast = expressionToGlsl(
+ context,
+ style.contrast,
+ ValueTypes.NUMBER
+ );
+ pipeline.push(
+ `color.rgb = clamp((${contrast} + 1.0) * color.rgb - (${contrast} / 2.0), vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));`
+ );
+ }
+
+ if (style.exposure !== undefined) {
+ const exposure = expressionToGlsl(
+ context,
+ style.exposure,
+ ValueTypes.NUMBER
+ );
+ pipeline.push(
+ `color.rgb = clamp((${exposure} + 1.0) * color.rgb, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));`
+ );
+ }
+
+ if (style.saturation !== undefined) {
+ const saturation = expressionToGlsl(
+ context,
+ style.saturation,
+ ValueTypes.NUMBER
+ );
+ pipeline.push(`
+ float saturation = ${saturation} + 1.0;
+ float sr = (1.0 - saturation) * 0.2126;
+ float sg = (1.0 - saturation) * 0.7152;
+ float sb = (1.0 - saturation) * 0.0722;
+ mat3 saturationMatrix = mat3(
+ sr + saturation, sr, sr,
+ sg, sg + saturation, sg,
+ sb, sb, sb + saturation
+ );
+ color.rgb = clamp(saturationMatrix * color.rgb, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));
+ `);
+ }
+
+ if (style.gamma !== undefined) {
+ const gamma = expressionToGlsl(context, style.gamma, ValueTypes.NUMBER);
+ pipeline.push(`color.rgb = pow(color.rgb, vec3(1.0 / ${gamma}));`);
+ }
+
+ if (style.brightness !== undefined) {
+ const brightness = expressionToGlsl(
+ context,
+ style.brightness,
+ ValueTypes.NUMBER
+ );
+ pipeline.push(
+ `color.rgb = clamp(color.rgb + ${brightness}, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));`
+ );
+ }
+
+ /** @type {Object} */
+ const uniforms = {};
+
+ const numVariables = context.variables.length;
+ if (numVariables > 1 && !style.variables) {
+ throw new Error(
+ `Missing variables in style (expected ${context.variables})`
+ );
+ }
+
+ for (let i = 0; i < numVariables; ++i) {
+ const variableName = context.variables[i];
+ if (!(variableName in style.variables)) {
+ throw new Error(`Missing '${variableName}' in style variables`);
+ }
+ const uniformName = uniformNameForVariable(variableName);
+ uniforms[uniformName] = function () {
+ let value = style.variables[variableName];
+ if (typeof value === 'string') {
+ value = getStringNumberEquivalent(context, value);
+ }
+ return value !== undefined ? value : -9999999; // to avoid matching with the first string literal
+ };
+ }
+
+ const uniformDeclarations = Object.keys(uniforms).map(function (name) {
+ return `uniform float ${name};`;
+ });
+
+ const textureCount = Math.ceil(bandCount / 4);
+ const colorAssignments = new Array(textureCount);
+ for (let textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
+ const uniformName = Uniforms.TILE_TEXTURE_PREFIX + textureIndex;
+ uniformDeclarations.push(`uniform sampler2D ${uniformName};`);
+ colorAssignments[
+ textureIndex
+ ] = `vec4 color${textureIndex} = texture2D(${uniformName}, v_textureCoord);`;
+ }
+
+ const fragmentShader = `
+ #ifdef GL_FRAGMENT_PRECISION_HIGH
+ precision highp float;
+ #else
+ precision mediump float;
+ #endif
+
+ varying vec2 v_textureCoord;
+ uniform float ${Uniforms.TRANSITION_ALPHA};
+
+ ${uniformDeclarations.join('\n')}
+
+ void main() {
+ ${colorAssignments.join('\n')}
+
+ vec4 color = color0;
+
+ ${pipeline.join('\n')}
+
+ if (color.a == 0.0) {
+ discard;
+ }
+
+ gl_FragColor = color * ${Uniforms.TRANSITION_ALPHA};
+ }`;
+
+ return {
+ vertexShader: vertexShader,
+ fragmentShader: fragmentShader,
+ uniforms: uniforms,
+ };
+}
+
+/**
+ * @classdesc
+ * For layer sources that provide pre-rendered, tiled images in grids that are
+ * organized by zoom levels for specific resolutions.
+ * Note that any property set in the options is set as a {@link module:ol/Object~BaseObject}
+ * property on the layer object; for example, setting `title: 'My Title'` in the
+ * options means that `title` is observable, and has get/set accessors.
+ *
+ * @api
+ */
+class WebGLTileLayer extends BaseTileLayer {
+ /**
+ * @param {Options} opt_options Tile layer options.
+ */
+ constructor(opt_options) {
+ const options = opt_options ? assign({}, opt_options) : {};
+
+ const style = options.style || {};
+ delete options.style;
+ super(options);
+
+ const parsedStyle = parseStyle(style || {}, 1); // TODO: get texture count from source
+
+ this.vertexShader_ = parsedStyle.vertexShader;
+ this.fragmentShader_ = parsedStyle.fragmentShader;
+ this.uniforms_ = parsedStyle.uniforms;
+ this.styleVariables_ = style.variables || {};
+ }
+
+ /**
+ * Create a renderer for this layer.
+ * @return {import("../renderer/Layer.js").default} A layer renderer.
+ * @protected
+ */
+ createRenderer() {
+ return new WebGLTileLayerRenderer(this, {
+ vertexShader: this.vertexShader_,
+ fragmentShader: this.fragmentShader_,
+ uniforms: this.uniforms_,
+ });
+ }
+
+ /**
+ * Update any variables used by the layer style and trigger a re-render.
+ * @param {Object} variables Variables to update.
+ */
+ updateStyleVariables(variables) {
+ assign(this.styleVariables_, variables);
+ this.changed();
+ }
+}
+
+export default WebGLTileLayer;
diff --git a/src/ol/renderer/webgl/TileLayer.js b/src/ol/renderer/webgl/TileLayer.js
new file mode 100644
index 0000000000..951bef91d2
--- /dev/null
+++ b/src/ol/renderer/webgl/TileLayer.js
@@ -0,0 +1,487 @@
+/**
+ * @module ol/renderer/webgl/TileLayer
+ */
+import LRUCache from '../../structs/LRUCache.js';
+import State from '../../source/State.js';
+import TileRange from '../../TileRange.js';
+import TileState from '../../TileState.js';
+import TileTexture from '../../webgl/TileTexture.js';
+import WebGLArrayBuffer from '../../webgl/Buffer.js';
+import WebGLLayerRenderer from './Layer.js';
+import {AttributeType} from '../../webgl/Helper.js';
+import {ELEMENT_ARRAY_BUFFER, STATIC_DRAW} from '../../webgl.js';
+import {
+ compose as composeTransform,
+ create as createTransform,
+} from '../../transform.js';
+import {
+ create as createMat4,
+ fromTransform as mat4FromTransform,
+} from '../../vec/mat4.js';
+import {
+ createOrUpdate as createTileCoord,
+ getKeyZXY,
+ getKey as getTileCoordKey,
+} from '../../tilecoord.js';
+import {fromUserExtent} from '../../proj.js';
+import {getIntersection} from '../../extent.js';
+import {getUid} from '../../util.js';
+import {numberSafeCompareFunction} from '../../array.js';
+import {toSize} from '../../size.js';
+
+export const Uniforms = {
+ TILE_TEXTURE_PREFIX: 'u_tileTexture',
+ TILE_TRANSFORM: 'u_tileTransform',
+ TRANSITION_ALPHA: 'u_transitionAlpha',
+ DEPTH: 'u_depth',
+};
+
+export const Attributes = {
+ TEXTURE_COORD: 'a_textureCoord',
+};
+
+/**
+ * @type {Array}
+ */
+const attributeDescriptions = [
+ {
+ name: Attributes.TEXTURE_COORD,
+ size: 2,
+ type: AttributeType.FLOAT,
+ },
+];
+
+/**
+ * Transform a zoom level into a depth value ranging from -1 to 1.
+ * @param {number} z A zoom level.
+ * @return {number} A depth value.
+ */
+function depthForZ(z) {
+ return 2 * (1 - 1 / (z + 1)) - 1;
+}
+
+/**
+ * Add a tile texture to the lookup.
+ * @param {Object>} tileTexturesByZ Lookup of
+ * tile textures by zoom level.
+ * @param {import("../../webgl/TileTexture.js").default} tileTexture A tile texture.
+ * @param {number} z The zoom level.
+ */
+function addTileTextureToLookup(tileTexturesByZ, tileTexture, z) {
+ if (!(z in tileTexturesByZ)) {
+ tileTexturesByZ[z] = [];
+ }
+ tileTexturesByZ[z].push(tileTexture);
+}
+
+/**
+ * @typedef {Object} Options
+ * @property {string} vertexShader Vertex shader source.
+ * @property {string} fragmentShader Fragment shader source.
+ * @property {Object} [uniforms] Additional uniforms
+ * made available to shaders.
+ * @property {string} [className='ol-layer'] A CSS class name to set to the canvas element.
+ */
+
+/**
+ * @classdesc
+ * WebGL renderer for tile layers.
+ * @api
+ */
+class WebGLTileLayerRenderer extends WebGLLayerRenderer {
+ /**
+ * @param {import("../../layer/WebGLTile.js").default} tileLayer Tile layer.
+ * @param {Options} options Options.
+ */
+ constructor(tileLayer, options) {
+ super(tileLayer, {
+ uniforms: options.uniforms,
+ className: options.className,
+ });
+
+ /**
+ * This transform converts tile i, j coordinates to screen coordinates.
+ * @type {import("../../transform.js").Transform}
+ * @private
+ */
+ this.tileTransform_ = createTransform();
+
+ /**
+ * @type {Array}
+ * @private
+ */
+ this.tempMat4_ = createMat4();
+
+ /**
+ * @type {import("../../TileRange.js").default}
+ * @private
+ */
+ this.tempTileRange_ = new TileRange(0, 0, 0, 0);
+
+ /**
+ * @type {import("../../tilecoord.js").TileCoord}
+ * @private
+ */
+ this.tempTileCoord_ = createTileCoord(0, 0, 0);
+
+ /**
+ * @type {import("../../size.js").Size}
+ * @private
+ */
+ this.tempSize_ = [0, 0];
+
+ this.program_ = this.helper.getProgram(
+ options.fragmentShader,
+ options.vertexShader
+ );
+
+ /**
+ * Tiles are rendered as a quad with the following structure:
+ *
+ * [P3]---------[P2]
+ * |` |
+ * | ` B |
+ * | ` |
+ * | ` |
+ * | A ` |
+ * | ` |
+ * [P0]---------[P1]
+ *
+ * Triangle A: P0, P1, P3
+ * Triangle B: P1, P2, P3
+ */
+ const indices = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, STATIC_DRAW);
+ indices.fromArray([0, 1, 3, 1, 2, 3]);
+ this.helper.flushBufferData(indices);
+ this.indices_ = indices;
+
+ this.tileTextureCache_ = new LRUCache(512);
+
+ this.renderedOpacity_ = NaN;
+ }
+
+ /**
+ * Determine whether render should be called.
+ * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
+ * @return {boolean} Layer is ready to be rendered.
+ */
+ prepareFrame(frameState) {
+ const source = this.getLayer().getSource();
+ if (!source) {
+ return false;
+ }
+ return source.getState() === State.READY;
+ }
+
+ /**
+ * Render the layer.
+ * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
+ * @return {HTMLElement} The rendered element.
+ */
+ renderFrame(frameState) {
+ this.preRender(frameState);
+
+ const layerState = frameState.layerStatesArray[frameState.layerIndex];
+ const viewState = frameState.viewState;
+
+ let extent = frameState.extent;
+ if (layerState.extent) {
+ extent = getIntersection(
+ extent,
+ fromUserExtent(layerState.extent, viewState.projection)
+ );
+ }
+
+ const tileLayer = this.getLayer();
+ const tileSource = tileLayer.getSource();
+ const tileGrid = tileSource.getTileGridForProjection(viewState.projection);
+ const z = tileGrid.getZForResolution(
+ viewState.resolution,
+ tileSource.zDirection
+ );
+
+ /**
+ * @type {Object>}
+ */
+ const tileTexturesByZ = {};
+
+ const tileTextureCache = this.tileTextureCache_;
+ const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
+
+ const tileSourceKey = getUid(tileSource);
+ if (!(tileSourceKey in frameState.wantedTiles)) {
+ frameState.wantedTiles[tileSourceKey] = {};
+ }
+
+ const wantedTiles = frameState.wantedTiles[tileSourceKey];
+
+ const tileResolution = tileGrid.getResolution(z);
+
+ for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
+ for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
+ const tileCoord = createTileCoord(z, x, y, this.tempTileCoord_);
+ const tileCoordKey = getTileCoordKey(tileCoord);
+
+ let tileTexture;
+ if (tileTextureCache.containsKey(tileCoordKey)) {
+ tileTexture = tileTextureCache.get(tileCoordKey);
+ } else {
+ const tile = tileSource.getTile(
+ z,
+ x,
+ y,
+ frameState.pixelRatio,
+ viewState.projection
+ );
+ tileTexture = new TileTexture(tile, tileGrid, this.helper);
+ tileTextureCache.set(tileCoordKey, tileTexture);
+ }
+
+ addTileTextureToLookup(tileTexturesByZ, tileTexture, z);
+
+ const tileQueueKey = tileTexture.tile.getKey();
+ wantedTiles[tileQueueKey] = true;
+
+ if (tileTexture.tile.getState() === TileState.IDLE) {
+ if (!frameState.tileQueue.isKeyQueued(tileQueueKey)) {
+ frameState.tileQueue.enqueue([
+ tileTexture.tile,
+ tileSourceKey,
+ tileGrid.getTileCoordCenter(tileCoord),
+ tileResolution,
+ ]);
+ }
+ }
+ }
+ }
+
+ /**
+ * A lookup of alpha values for tiles at the target rendering resolution
+ * for tiles that are in transition. If a tile coord key is absent from
+ * this lookup, the tile should be rendered at alpha 1.
+ * @type {Object}
+ */
+ const alphaLookup = {};
+
+ const uid = getUid(this);
+ const time = frameState.time;
+
+ // look for cached tiles to use if a target tile is not ready
+ const tileTextures = tileTexturesByZ[z];
+ for (let i = 0, ii = tileTextures.length; i < ii; ++i) {
+ const tileTexture = tileTextures[i];
+ const tile = tileTexture.tile;
+ const tileCoord = tile.tileCoord;
+
+ if (tileTexture.loaded) {
+ const alpha = tile.getAlpha(uid, time);
+ if (alpha === 1) {
+ // no need to look for alt tiles
+ tile.endTransition(uid);
+ continue;
+ }
+ const tileCoordKey = getTileCoordKey(tileCoord);
+ alphaLookup[tileCoordKey] = alpha;
+ }
+
+ // first look for child tiles (at z + 1)
+ const coveredByChildren = this.findAltTiles_(
+ tileGrid,
+ tileCoord,
+ z + 1,
+ tileTexturesByZ
+ );
+
+ if (coveredByChildren) {
+ continue;
+ }
+
+ // next look for parent tiles
+ for (let parentZ = z - 1; parentZ >= tileGrid.minZoom; --parentZ) {
+ const coveredByParent = this.findAltTiles_(
+ tileGrid,
+ tileCoord,
+ parentZ,
+ tileTexturesByZ
+ );
+
+ if (coveredByParent) {
+ break;
+ }
+ }
+ }
+
+ this.helper.useProgram(this.program_);
+ this.helper.prepareDraw(frameState);
+
+ const zs = Object.keys(tileTexturesByZ)
+ .map(Number)
+ .sort(numberSafeCompareFunction);
+
+ const gl = this.helper.getGL();
+
+ const centerX = viewState.center[0];
+ const centerY = viewState.center[1];
+
+ for (let j = 0, jj = zs.length; j < jj; ++j) {
+ const tileZ = zs[j];
+ const tileResolution = tileGrid.getResolution(tileZ);
+ const tileSize = toSize(tileGrid.getTileSize(tileZ), this.tempSize_);
+ const tileOrigin = tileGrid.getOrigin(tileZ);
+
+ const centerI =
+ (centerX - tileOrigin[0]) / (tileSize[0] * tileResolution);
+ const centerJ =
+ (tileOrigin[1] - centerY) / (tileSize[1] * tileResolution);
+
+ const tileScale = viewState.resolution / tileResolution;
+
+ const depth = depthForZ(tileZ);
+ const tileTextures = tileTexturesByZ[tileZ];
+ for (let i = 0, ii = tileTextures.length; i < ii; ++i) {
+ const tileTexture = tileTextures[i];
+ if (!tileTexture.loaded) {
+ continue;
+ }
+ const tile = tileTexture.tile;
+ const tileCoord = tile.tileCoord;
+ const tileCoordKey = getTileCoordKey(tileCoord);
+
+ const tileCenterI = tileCoord[1];
+ const tileCenterJ = tileCoord[2];
+
+ composeTransform(
+ this.tileTransform_,
+ 0,
+ 0,
+ 2 / ((frameState.size[0] * tileScale) / tileSize[0]),
+ -2 / ((frameState.size[1] * tileScale) / tileSize[1]),
+ viewState.rotation,
+ -(centerI - tileCenterI),
+ -(centerJ - tileCenterJ)
+ );
+
+ this.helper.setUniformMatrixValue(
+ Uniforms.TILE_TRANSFORM,
+ mat4FromTransform(this.tempMat4_, this.tileTransform_)
+ );
+
+ this.helper.bindBuffer(tileTexture.coords);
+ this.helper.bindBuffer(this.indices_);
+ this.helper.enableAttributes(attributeDescriptions);
+
+ for (
+ let textureIndex = 0;
+ textureIndex < tileTexture.textures.length;
+ ++textureIndex
+ ) {
+ const textureProperty = 'TEXTURE' + textureIndex;
+ const uniformName = Uniforms.TILE_TEXTURE_PREFIX + textureIndex;
+ gl.activeTexture(gl[textureProperty]);
+ gl.bindTexture(gl.TEXTURE_2D, tileTexture.textures[textureIndex]);
+ gl.uniform1i(this.helper.getUniformLocation(uniformName), 0);
+ }
+
+ const alpha =
+ tileCoordKey in alphaLookup ? alphaLookup[tileCoordKey] : 1;
+
+ if (alpha < 1) {
+ frameState.animate = true;
+ }
+
+ this.helper.setUniformFloatValue(Uniforms.TRANSITION_ALPHA, alpha);
+ this.helper.setUniformFloatValue(Uniforms.DEPTH, depth);
+
+ this.helper.drawElements(0, this.indices_.getSize());
+ }
+ }
+
+ this.helper.finalizeDraw(frameState);
+
+ const canvas = this.helper.getCanvas();
+
+ const opacity = layerState.opacity;
+ if (this.renderedOpacity_ !== opacity) {
+ canvas.style.opacity = String(opacity);
+ this.renderedOpacity_ = opacity;
+ }
+
+ while (tileTextureCache.canExpireCache()) {
+ const tileTexture = tileTextureCache.pop();
+ tileTexture.dispose();
+ }
+
+ // TODO: let the renderers manage their own cache instead of managing the source cache
+ if (tileSource.canExpireCache()) {
+ /**
+ * @param {import("../../PluggableMap.js").default} map Map.
+ * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
+ */
+ const postRenderFunction = function (map, frameState) {
+ const tileSourceKey = getUid(tileSource);
+ if (tileSourceKey in frameState.usedTiles) {
+ tileSource.expireCache(
+ frameState.viewState.projection,
+ frameState.usedTiles[tileSourceKey]
+ );
+ }
+ };
+
+ frameState.postRenderFunctions.push(postRenderFunction);
+ }
+
+ this.postRender(frameState);
+ return canvas;
+ }
+
+ /**
+ * Look for tiles covering the provided tile coordinate at an alternate
+ * zoom level. Loaded tiles will be added to the provided tile texture lookup.
+ * @param {import("../../tilegrid/TileGrid.js").default} tileGrid The tile grid.
+ * @param {import("../../tilecoord.js").TileCoord} tileCoord The target tile coordinate.
+ * @param {number} altZ The alternate zoom level.
+ * @param {Object>} tileTexturesByZ Lookup of
+ * tile textures by zoom level.
+ * @return {boolean} The tile coordinate is covered by loaded tiles at the alternate zoom level.
+ * @private
+ */
+ findAltTiles_(tileGrid, tileCoord, altZ, tileTexturesByZ) {
+ const tileRange = tileGrid.getTileRangeForTileCoordAndZ(
+ tileCoord,
+ altZ,
+ this.tempTileRange_
+ );
+
+ if (!tileRange) {
+ return false;
+ }
+
+ let covered = true;
+ const tileTextureCache = this.tileTextureCache_;
+ for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
+ for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
+ const cacheKey = getKeyZXY(altZ, x, y);
+ let loaded = false;
+ if (tileTextureCache.containsKey(cacheKey)) {
+ const tileTexture = tileTextureCache.get(cacheKey);
+ if (tileTexture.loaded) {
+ addTileTextureToLookup(tileTexturesByZ, tileTexture, altZ);
+ loaded = true;
+ }
+ }
+ if (!loaded) {
+ covered = false;
+ }
+ }
+ }
+ return covered;
+ }
+}
+
+/**
+ * @function
+ * @return {import("../../layer/WebGLTile.js").default}
+ */
+WebGLTileLayerRenderer.prototype.getLayer;
+
+export default WebGLTileLayerRenderer;
diff --git a/src/ol/source/DataTile.js b/src/ol/source/DataTile.js
new file mode 100644
index 0000000000..e96524e737
--- /dev/null
+++ b/src/ol/source/DataTile.js
@@ -0,0 +1,142 @@
+/**
+ * @module ol/source/DataTile
+ */
+import DataTile from '../DataTile.js';
+import EventType from '../events/EventType.js';
+import TileEventType from './TileEventType.js';
+import TileSource, {TileSourceEvent} from './Tile.js';
+import TileState from '../TileState.js';
+import {createXYZ, extentFromProjection} from '../tilegrid.js';
+import {getKeyZXY} from '../tilecoord.js';
+import {getUid} from '../util.js';
+
+/**
+ * @typedef {Object} Options
+ * @property {function(number, number, number) : Promise} [loader] Data loader. Called with z, x, and y tile coordinates.
+ * @property {number} [maxZoom=42] Optional max zoom level. Not used if `tileGrid` is provided.
+ * @property {number} [minZoom=0] Optional min zoom level. Not used if `tileGrid` is provided.
+ * @property {number|import("../size.js").Size} [tileSize=[256, 256]] The pixel width and height of the tiles.
+ * @property {number} [maxResolution] Optional tile grid resolution at level zero. Not used if `tileGrid` is provided.
+ * @property {import("../proj.js").ProjectionLike} [projection='EPSG:3857'] Tile projection.
+ * @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid.
+ * @property {boolean} [opaque=false] Whether the layer is opaque.
+ * @property {import("./State.js").default} [state] The source state.
+ * @property {number} [cacheSize] Number of tiles to retain in the cache.
+ * @property {number} [tilePixelRatio] Tile pixel ratio.
+ * @property {boolean} [wrapX=true] Render tiles beyond the antimeridian.
+ * @property {number} [transition] Transition time when fading in new tiles (in miliseconds).
+ */
+
+/**
+ * @classdesc
+ * Base class for sources providing tiles divided into a tile grid over http.
+ *
+ * @fires import("./Tile.js").TileSourceEvent
+ */
+class DataTileSource extends TileSource {
+ /**
+ * @param {Options} options Image tile options.
+ */
+ constructor(options) {
+ const projection =
+ options.projection === undefined ? 'EPSG:3857' : options.projection;
+
+ let tileGrid = options.tileGrid;
+ if (tileGrid === undefined && projection) {
+ tileGrid = createXYZ({
+ extent: extentFromProjection(projection),
+ maxResolution: options.maxResolution,
+ maxZoom: options.maxZoom,
+ minZoom: options.minZoom,
+ tileSize: options.tileSize,
+ });
+ }
+
+ super({
+ cacheSize: options.cacheSize,
+ projection: projection,
+ tileGrid: tileGrid,
+ opaque: options.opaque,
+ state: options.state,
+ tilePixelRatio: options.tilePixelRatio,
+ wrapX: options.wrapX,
+ transition: options.transition,
+ });
+
+ /**
+ * @private
+ * @type {!Object}
+ */
+ this.tileLoadingKeys_ = {};
+
+ /**
+ * @private
+ */
+ this.loader_ = options.loader;
+
+ this.handleTileChange_ = this.handleTileChange_.bind(this);
+ }
+
+ /**
+ * @param {function(number, number, number) : Promise} loader The data loader.
+ * @protected
+ */
+ setLoader(loader) {
+ this.loader_ = loader;
+ }
+
+ /**
+ * @abstract
+ * @param {number} z Tile coordinate z.
+ * @param {number} x Tile coordinate x.
+ * @param {number} y Tile coordinate y.
+ * @param {number} pixelRatio Pixel ratio.
+ * @param {import("../proj/Projection.js").default} projection Projection.
+ * @return {!import("../Tile.js").default} Tile.
+ */
+ getTile(z, x, y, pixelRatio, projection) {
+ const tileCoordKey = getKeyZXY(z, x, y);
+ if (this.tileCache.containsKey(tileCoordKey)) {
+ return this.tileCache.get(tileCoordKey);
+ }
+
+ const sourceLoader = this.loader_;
+ function loader() {
+ return sourceLoader(z, x, y);
+ }
+
+ const tile = new DataTile({tileCoord: [z, x, y], loader: loader});
+ tile.addEventListener(EventType.CHANGE, this.handleTileChange_);
+
+ this.tileCache.set(tileCoordKey, tile);
+ return tile;
+ }
+
+ /**
+ * Handle tile change events.
+ * @param {import("../events/Event.js").default} event Event.
+ */
+ handleTileChange_(event) {
+ const tile = /** @type {import("../Tile.js").default} */ (event.target);
+ const uid = getUid(tile);
+ const tileState = tile.getState();
+ let type;
+ if (tileState == TileState.LOADING) {
+ this.tileLoadingKeys_[uid] = true;
+ type = TileEventType.TILELOADSTART;
+ } else if (uid in this.tileLoadingKeys_) {
+ delete this.tileLoadingKeys_[uid];
+ type =
+ tileState == TileState.ERROR
+ ? TileEventType.TILELOADERROR
+ : tileState == TileState.LOADED
+ ? TileEventType.TILELOADEND
+ : undefined;
+ }
+ if (type) {
+ this.dispatchEvent(new TileSourceEvent(type, tile));
+ }
+ }
+}
+
+export default DataTileSource;
diff --git a/src/ol/source/GeoTIFF.js b/src/ol/source/GeoTIFF.js
new file mode 100644
index 0000000000..9e39ac372e
--- /dev/null
+++ b/src/ol/source/GeoTIFF.js
@@ -0,0 +1,391 @@
+/**
+ * @module ol/source/GeoTIFF
+ */
+import DataTile from './DataTile.js';
+import State from './State.js';
+import TileGrid from '../tilegrid/TileGrid.js';
+import {get as getProjection} from '../proj.js';
+import {fromUrl as tiffFromUrl, fromUrls as tiffFromUrls} from 'geotiff';
+import {toSize} from '../size.js';
+
+/**
+ * @typedef SourceInfo
+ * @property {string} url URL for the source.
+ * @property {Array} [overviews] List of any overview URLs.
+ * @property {number} [min=0] The minimum source data value. Rendered values are scaled from 0 to 1 based on
+ * the configured min and max.
+ * @property {number} [max] The maximum source data value. Rendered values are scaled from 0 to 1 based on
+ * the configured min and max.
+ * @property {number} [nodata] Values to discard.
+ */
+
+/**
+ * @param {import("geotiff/src/geotiff.js").GeoTIFF|import("geotiff/src/geotiff.js").MultiGeoTIFF} tiff A GeoTIFF.
+ * @return {Promise>} Resolves to a list of images.
+ */
+function getImagesForTIFF(tiff) {
+ return tiff.getImageCount().then(function (count) {
+ const requests = new Array(count);
+ for (let i = 0; i < count; ++i) {
+ requests[i] = tiff.getImage(i);
+ }
+ return Promise.all(requests);
+ });
+}
+
+/**
+ * @param {SourceInfo} source The GeoTIFF source.
+ * @return {Promise>} Resolves to a list of images.
+ */
+function getImagesForSource(source) {
+ let request;
+ if (source.overviews) {
+ request = tiffFromUrls(source.url, source.overviews);
+ } else {
+ request = tiffFromUrl(source.url);
+ }
+ return request.then(getImagesForTIFF);
+}
+
+/**
+ * @param {number|Array|Array>} expected Expected value.
+ * @param {number|Array|Array>} got Actual value.
+ * @param {string} message The error message.
+ */
+function assertEqual(expected, got, message) {
+ if (Array.isArray(expected)) {
+ const length = expected.length;
+ if (!Array.isArray(got) || length != got.length) {
+ throw new Error(message);
+ }
+ for (let i = 0; i < length; ++i) {
+ assertEqual(expected[i], got[i], message);
+ }
+ return;
+ }
+
+ if (expected !== got) {
+ throw new Error(message);
+ }
+}
+
+/**
+ * @param {Array} array The data array.
+ * @return {number} The minimum value.
+ */
+function getMinForDataType(array) {
+ if (array instanceof Int8Array) {
+ return -128;
+ }
+ if (array instanceof Int16Array) {
+ return -32768;
+ }
+ if (array instanceof Int32Array) {
+ return -2147483648;
+ }
+ if (array instanceof Float32Array) {
+ return 1.2e-38;
+ }
+ return 0;
+}
+
+/**
+ * @param {Array} array The data array.
+ * @return {number} The maximum value.
+ */
+function getMaxForDataType(array) {
+ if (array instanceof Int8Array) {
+ return 127;
+ }
+ if (array instanceof Uint8Array) {
+ return 255;
+ }
+ if (array instanceof Uint8ClampedArray) {
+ return 255;
+ }
+ if (array instanceof Int16Array) {
+ return 32767;
+ }
+ if (array instanceof Uint16Array) {
+ return 65535;
+ }
+ if (array instanceof Int32Array) {
+ return 2147483647;
+ }
+ if (array instanceof Uint32Array) {
+ return 4294967295;
+ }
+ if (array instanceof Float32Array) {
+ return 3.4e38;
+ }
+ return 255;
+}
+
+/**
+ * @typedef Options
+ * @property {Array} sources List of information about GeoTIFF sources.
+ */
+
+/**
+ * @classdesc
+ * A source for working with GeoTIFF data.
+ */
+class GeoTIFFSource extends DataTile {
+ /**
+ * @param {Options} options Image tile options.
+ */
+ constructor(options) {
+ super({
+ state: State.LOADING,
+ tileGrid: null,
+ projection: null,
+ });
+
+ /**
+ * @type {Array}
+ * @private
+ */
+ this.sourceInfo_ = options.sources;
+
+ const numSources = this.sourceInfo_.length;
+
+ /**
+ * @type {Array>}
+ * @private
+ */
+ this.sourceImagery_ = new Array(numSources);
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.samplesPerPixel_;
+
+ /**
+ * @type {Error}
+ * @private
+ */
+ this.error_ = null;
+
+ const self = this;
+ const requests = new Array(numSources);
+ for (let i = 0; i < numSources; ++i) {
+ requests[i] = getImagesForSource(this.sourceInfo_[i]);
+ }
+ Promise.all(requests)
+ .then(function (sources) {
+ self.configure_(sources);
+ })
+ .catch(function (error) {
+ self.error_ = error;
+ self.setState(State.ERROR);
+ });
+ }
+
+ /**
+ * @return {Error} A source loading error.
+ */
+ getError() {
+ return this.error_;
+ }
+
+ /**
+ * Configure the tile grid based on images within the source GeoTIFFs. Each GeoTIFF
+ * must have the same internal tiled structure.
+ * @param {Array>} sources Each source is a list of images
+ * from a single GeoTIFF.
+ */
+ configure_(sources) {
+ let extent;
+ let origin;
+ let tileSizes;
+ let resolutions;
+ let samplesPerPixel;
+
+ const sourceCount = sources.length;
+ for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
+ const images = sources[sourceIndex];
+ const imageCount = images.length;
+
+ let sourceExtent;
+ let sourceOrigin;
+ const sourceTileSizes = new Array(imageCount);
+ const sourceResolutions = new Array(imageCount);
+
+ for (let imageIndex = 0; imageIndex < imageCount; ++imageIndex) {
+ const image = images[imageIndex];
+ const imageSamplesPerPixel = image.getSamplesPerPixel();
+ if (!samplesPerPixel) {
+ samplesPerPixel = imageSamplesPerPixel;
+ } else {
+ const message = `Band count mismatch for source ${sourceIndex}, got ${imageSamplesPerPixel} but expected ${samplesPerPixel}`;
+ assertEqual(samplesPerPixel, imageSamplesPerPixel, message);
+ }
+ const level = imageCount - (imageIndex + 1);
+
+ if (!sourceExtent) {
+ sourceExtent = image.getBoundingBox();
+ }
+
+ if (!sourceOrigin) {
+ sourceOrigin = image.getOrigin().slice(0, 2);
+ }
+
+ sourceResolutions[level] = image.getResolution(images[0])[0];
+ sourceTileSizes[level] = [image.getTileWidth(), image.getTileHeight()];
+ }
+
+ if (!extent) {
+ extent = sourceExtent;
+ } else {
+ const message = `Extent mismatch for source ${sourceIndex}, got [${sourceExtent}] but expected [${extent}]`;
+ assertEqual(extent, sourceExtent, message);
+ }
+
+ if (!origin) {
+ origin = sourceOrigin;
+ } else {
+ const message = `Origin mismatch for source ${sourceIndex}, got [${sourceOrigin}] but expected [${origin}]`;
+ assertEqual(origin, sourceOrigin, message);
+ }
+
+ if (!tileSizes) {
+ tileSizes = sourceTileSizes;
+ } else {
+ assertEqual(
+ tileSizes,
+ sourceTileSizes,
+ `Tile size mismatch for source ${sourceIndex}`
+ );
+ }
+
+ if (!resolutions) {
+ resolutions = sourceResolutions;
+ } else {
+ const message = `Resolution mismatch for source ${sourceIndex}, got [${sourceResolutions}] but expected [${resolutions}]`;
+ assertEqual(resolutions, sourceResolutions, message);
+ }
+
+ this.sourceImagery_[sourceIndex] = images.reverse();
+ }
+
+ if (!this.getProjection()) {
+ const firstImage = sources[0][0];
+ if (firstImage.geoKeys) {
+ const code =
+ firstImage.geoKeys.ProjectedCSTypeGeoKey ||
+ firstImage.geoKeys.GeographicTypeGeoKey;
+ if (code) {
+ this.projection = getProjection(`EPSG:${code}`);
+ }
+ }
+ }
+
+ if (sourceCount > 1 && samplesPerPixel !== 1) {
+ throw new Error(
+ 'Expected single band GeoTIFFs when using multiple sources'
+ );
+ }
+
+ this.samplesPerPixel_ = samplesPerPixel;
+
+ const tileGrid = new TileGrid({
+ extent: extent,
+ origin: origin,
+ resolutions: resolutions,
+ tileSizes: tileSizes,
+ });
+
+ this.tileGrid = tileGrid;
+
+ this.setLoader(this.loadTile_.bind(this));
+ this.setState(State.READY);
+ }
+
+ loadTile_(z, x, y) {
+ const size = toSize(this.tileGrid.getTileSize(z));
+ const pixelBounds = [
+ x * size[0],
+ y * size[1],
+ (x + 1) * size[0],
+ (y + 1) * size[1],
+ ];
+
+ const sourceCount = this.sourceImagery_.length;
+ const requests = new Array(sourceCount);
+ let addAlpha = false;
+ const sourceInfo = this.sourceInfo_;
+ for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
+ const image = this.sourceImagery_[sourceIndex][z];
+ requests[sourceIndex] = image.readRasters({window: pixelBounds});
+ if (sourceInfo[sourceIndex].nodata !== undefined) {
+ addAlpha = true;
+ }
+ }
+
+ const samplesPerPixel = this.samplesPerPixel_;
+ let additionalBands = 0;
+ if (addAlpha) {
+ if (sourceCount === 2 && samplesPerPixel === 1) {
+ additionalBands = 2;
+ } else {
+ additionalBands = 1;
+ }
+ }
+ const bandCount = samplesPerPixel * sourceCount + additionalBands;
+ const pixelCount = size[0] * size[1];
+ const dataLength = pixelCount * bandCount;
+
+ return Promise.all(requests).then(function (sourceSamples) {
+ const data = new Uint8ClampedArray(dataLength);
+ for (let pixelIndex = 0; pixelIndex < pixelCount; ++pixelIndex) {
+ let transparent = addAlpha;
+ const sourceOffset = pixelIndex * bandCount;
+ for (let sourceIndex = 0; sourceIndex < sourceCount; ++sourceIndex) {
+ const source = sourceInfo[sourceIndex];
+ let min = source.min;
+ if (min === undefined) {
+ min = getMinForDataType(sourceSamples[sourceIndex][0]);
+ }
+ let max = source.max;
+ if (max === undefined) {
+ max = getMaxForDataType(sourceSamples[sourceIndex][0]);
+ }
+
+ const gain = 255 / (max - min);
+ const bias = -min * gain;
+
+ const nodata = source.nodata;
+
+ const sampleOffset = sourceOffset + sourceIndex * samplesPerPixel;
+ for (
+ let sampleIndex = 0;
+ sampleIndex < samplesPerPixel;
+ ++sampleIndex
+ ) {
+ const sourceValue =
+ sourceSamples[sourceIndex][sampleIndex][pixelIndex];
+
+ const value = gain * sourceValue + bias;
+ if (!addAlpha) {
+ data[sampleOffset + sampleIndex] = value;
+ } else {
+ if (sourceValue !== nodata) {
+ transparent = false;
+ data[sampleOffset + sampleIndex] = value;
+ }
+ }
+ }
+
+ if (addAlpha && !transparent) {
+ data[sampleOffset + samplesPerPixel] = 255;
+ }
+ }
+ }
+
+ return data;
+ });
+ }
+}
+
+export default GeoTIFFSource;
diff --git a/src/ol/style/expressions.js b/src/ol/style/expressions.js
index 106190727e..8f9aa6bb1d 100644
--- a/src/ol/style/expressions.js
+++ b/src/ol/style/expressions.js
@@ -170,6 +170,7 @@ export function isTypeUnique(valueType) {
* @property {Array} variables List of variables used in the expression; contains **unprefixed names**
* @property {Array} attributes List of attributes used in the expression; contains **unprefixed names**
* @property {Object} stringLiteralsMap This object maps all encountered string values to a number
+ * @property {number} [bandCount] Number of bands per pixel.
*/
/**
@@ -402,6 +403,27 @@ Operators['var'] = {
},
};
+Operators['band'] = {
+ getReturnType: function (args) {
+ return ValueTypes.NUMBER;
+ },
+ toGlsl: function (context, args) {
+ assertArgsCount(args, 1);
+ const band = args[0];
+ if (typeof band !== 'number') {
+ throw new Error('Band index must be a number');
+ }
+ const zeroBasedBand = band - 1;
+ const colorIndex = Math.floor(zeroBasedBand / 4);
+ let bandIndex = zeroBasedBand % 4;
+ if (band === context.bandCount && bandIndex === 1) {
+ // LUMINANCE_ALPHA - band 1 assigned to rgb and band 2 assigned to alpha
+ bandIndex = 3;
+ }
+ return `color${colorIndex}[${bandIndex}]`;
+ },
+};
+
Operators['time'] = {
getReturnType: function (args) {
return ValueTypes.NUMBER;
@@ -748,17 +770,16 @@ Operators['interpolate'] = {
assertUniqueInferredType(args, outputType);
const input = expressionToGlsl(context, args[1]);
- let result = null;
+ const exponent = numberToGlsl(interpolation);
+
+ let result = '';
for (let i = 2; i < args.length - 2; i += 2) {
const stop1 = expressionToGlsl(context, args[i]);
- const output1 = expressionToGlsl(context, args[i + 1], outputType);
+ const output1 =
+ result || expressionToGlsl(context, args[i + 1], outputType);
const stop2 = expressionToGlsl(context, args[i + 2]);
const output2 = expressionToGlsl(context, args[i + 3], outputType);
- result = `mix(${
- result || output1
- }, ${output2}, pow(clamp((${input} - ${stop1}) / (${stop2} - ${stop1}), 0.0, 1.0), ${numberToGlsl(
- interpolation
- )}))`;
+ result = `mix(${output1}, ${output2}, pow(clamp((${input} - ${stop1}) / (${stop2} - ${stop1}), 0.0, 1.0), ${exponent}))`;
}
return result;
},
diff --git a/src/ol/webgl/TileTexture.js b/src/ol/webgl/TileTexture.js
new file mode 100644
index 0000000000..cad02dc42e
--- /dev/null
+++ b/src/ol/webgl/TileTexture.js
@@ -0,0 +1,195 @@
+/**
+ * @module ol/webgl/TileTexture
+ */
+
+import EventTarget from '../events/Target.js';
+import EventType from '../events/EventType.js';
+import ImageTile from '../ImageTile.js';
+import TileState from '../TileState.js';
+import WebGLArrayBuffer from './Buffer.js';
+import {ARRAY_BUFFER, STATIC_DRAW} from '../webgl.js';
+import {toSize} from '../size.js';
+
+function bindAndConfigure(gl, texture) {
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+}
+
+/**
+ * @param {WebGLRenderingContext} gl The WebGL context.
+ * @param {WebGLTexture} texture The texture.
+ * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image The image.
+ */
+function uploadImageTexture(gl, texture, image) {
+ bindAndConfigure(gl, texture);
+
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
+}
+
+/**
+ * @param {WebGLRenderingContext} gl The WebGL context.
+ * @param {WebGLTexture} texture The texture.
+ * @param {import("../DataTile.js").Data} data The pixel data.
+ * @param {import("../size.js").Size} size The pixel size.
+ * @param {number} bandCount The band count.
+ */
+function uploadDataTexture(gl, texture, data, size, bandCount) {
+ bindAndConfigure(gl, texture);
+
+ let format;
+ switch (bandCount) {
+ case 1: {
+ format = gl.LUMINANCE;
+ break;
+ }
+ case 2: {
+ format = gl.LUMINANCE_ALPHA;
+ break;
+ }
+ case 3: {
+ format = gl.RGB;
+ break;
+ }
+ case 4: {
+ format = gl.RGBA;
+ break;
+ }
+ default: {
+ throw new Error(`Unsupported number of bands: ${bandCount}`);
+ }
+ }
+
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ format,
+ size[0],
+ size[1],
+ 0,
+ format,
+ gl.UNSIGNED_BYTE,
+ data
+ );
+}
+
+class TileTexture extends EventTarget {
+ /**
+ * @param {import("../DataTile.js").default|import("../ImageTile.js").default} tile The tile.
+ * @param {import("../tilegrid/TileGrid.js").default} grid Tile grid.
+ * @param {import("../webgl/Helper.js").default} helper WebGL helper.
+ */
+ constructor(tile, grid, helper) {
+ super();
+
+ this.tile = tile;
+ this.size = toSize(grid.getTileSize(tile.tileCoord[0]));
+ this.loaded = tile.getState() === TileState.LOADED;
+
+ this.bandCount = NaN;
+
+ this.helper_ = helper;
+ this.handleTileChange_ = this.handleTileChange_.bind(this);
+
+ const coords = new WebGLArrayBuffer(ARRAY_BUFFER, STATIC_DRAW);
+ coords.fromArray([
+ 0, // P0
+ 1,
+ 1, // P1
+ 1,
+ 1, // P2
+ 0,
+ 0, // P3
+ 0,
+ ]);
+ helper.flushBufferData(coords);
+ this.coords = coords;
+
+ /**
+ * @type {Array}
+ */
+ this.textures = [];
+
+ if (this.loaded) {
+ this.uploadTile_();
+ } else {
+ tile.addEventListener(EventType.CHANGE, this.handleTileChange_);
+ }
+ }
+
+ uploadTile_() {
+ const gl = this.helper_.getGL();
+ const tile = this.tile;
+
+ if (tile instanceof ImageTile) {
+ const texture = gl.createTexture();
+ this.textures.push(texture);
+ this.bandCount = 4;
+ uploadImageTexture(gl, texture, tile.getImage());
+ return;
+ }
+
+ const data = tile.getData();
+ const pixelCount = this.size[0] * this.size[1];
+ this.bandCount = data.byteLength / pixelCount;
+ const textureCount = Math.ceil(this.bandCount / 4);
+
+ if (textureCount === 1) {
+ const texture = gl.createTexture();
+ this.textures.push(texture);
+ uploadDataTexture(gl, texture, data, this.size, this.bandCount);
+ return;
+ }
+
+ const textureDataArrays = new Array(textureCount);
+ for (let textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
+ const texture = gl.createTexture();
+ this.textures.push(texture);
+
+ const bandCount =
+ textureIndex < textureCount - 1 ? 4 : this.bandCount % 4;
+ textureDataArrays[textureIndex] = new Uint8Array(pixelCount * bandCount);
+ }
+
+ const valueCount = pixelCount * this.bandCount;
+ for (let dataIndex = 0; dataIndex < valueCount; ++dataIndex) {
+ const bandIndex = dataIndex % this.bandCount;
+ const textureBandIndex = bandIndex % 4;
+ const textureIndex = Math.floor(bandIndex / 4);
+ const bandCount =
+ textureIndex < textureCount - 1 ? 4 : this.bandCount % 4;
+ const pixelIndex = Math.floor(dataIndex / this.bandCount);
+ textureDataArrays[textureIndex][
+ pixelIndex * bandCount + textureBandIndex
+ ] = data[dataIndex];
+ }
+
+ for (let textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
+ const bandCount =
+ textureIndex < textureCount - 1 ? 4 : this.bandCount % 4;
+ const texture = this.textures[textureIndex];
+ const data = textureDataArrays[textureIndex];
+ uploadDataTexture(gl, texture, data, this.size, bandCount);
+ }
+ }
+
+ handleTileChange_() {
+ if (this.tile.getState() === TileState.LOADED) {
+ this.loaded = true;
+ this.uploadTile_();
+ this.dispatchEvent(EventType.CHANGE);
+ }
+ }
+
+ disposeInternal() {
+ const gl = this.helper_.getGL();
+ this.helper_.deleteBuffer(this.coords);
+ for (let i = 0; i < this.textures.length; ++i) {
+ gl.deleteTexture(this.textures[i]);
+ }
+ this.tile.removeEventListener(EventType.CHANGE, this.handleTileChange_);
+ }
+}
+
+export default TileTexture;