aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2025-06-07 15:43:40 +0200
committerGuilhem Moulin <guilhem@fripost.org>2025-06-07 22:57:11 +0200
commit7fe45e014711edf78d64ead8e44ae9f80e1e6008 (patch)
treec3d7882e84055a7c7542edb9d639327100928233
parent1d89092b7b8725323158c9f06b5a4b0d70f2538a (diff)
Add (WIP) WebGL layer for kontinuitetsskog.
We rename “vectorLayers” to “mapLayers” as it now contains a raster layer.
-rw-r--r--main.js170
1 files changed, 121 insertions, 49 deletions
diff --git a/main.js b/main.js
index fe2a2c0..e2d2d18 100644
--- a/main.js
+++ b/main.js
@@ -18,8 +18,10 @@
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
+import TileLayerGL from 'ol/layer/WebGLTile.js';
import WMTS from 'ol/source/WMTS.js';
+import GeoTIFF from 'ol/source/GeoTIFF.js';
import WMTSTileGrid from 'ol/tilegrid/WMTS.js';
import FullScreen from 'ol/control/FullScreen.js';
@@ -531,19 +533,22 @@ if (window.location === window.parent.location) {
btn.onclick = function(event) {
infoMetadataAccordions.forEach((x) => x.element.replaceChildren());
modal.show();
- Promise.allSettled(Object.entries(vectorLayers).map(function([grp,lyr]) {
- const url = lyr.getSource().getUrls()[0];
- if (url === undefined || url.length <= 16 || url.substr(url.length - 16) !== '/{z}/{x}/{y}.pbf') {
- throw new Error(`Invalid URL ${url}`);
+ Promise.allSettled(Object.entries(mapLayers).map(function([grp,lyr]) {
+ if (lyr.getSource() instanceof VectorTile) {
+ const url = lyr.getSource().getUrls()[0];
+ if (url === undefined || url.length <= 16 || url.substr(url.length - 16) !== '/{z}/{x}/{y}.pbf') {
+ throw new Error(`Invalid URL ${url}`);
+ }
+ return fetch(url.substr(0, url.length - 16) + '/metadata.json')
+ .then(function(resp0) {
+ if (resp0.status === 200) {
+ return resp0.json().then((x) => [grp,x]);
+ } else {
+ throw new Error(`${resp0.url} [${resp0.status}]`);
+ }
+ });
}
- return fetch(url.substr(0, url.length - 16) + '/metadata.json')
- .then(function(resp0) {
- if (resp0.status === 200) {
- return resp0.json().then((x) => [grp,x]);
- } else {
- throw new Error(`${resp0.url} [${resp0.status}]`);
- }
- });
+ return new Promise(() => { throw new Error(`Unknown source for "${grp}"`); });
}))
.then(function(rs) {
const metadata = Object.fromEntries(rs.filter(function(r) {
@@ -3805,7 +3810,12 @@ const layers = {
}),
});
})),
- }
+ },
+
+ 'kskog.1' : { style: [ 56, 168, 0, .2] }, /* #1 Sannolikt kontinuitetsskog (preciserad) */
+ 'kskog.2' : { style: [169, 0, 230, .2] }, /* #2 Sannolikt påverkad kontinuitetsskog (preciserad) */
+ 'kskog.3' : { style: [152, 230, 0, .2] }, /* #3 Sannolikt kontinuitetsskog i fjällen (grövre precisering) */
+ 'kskog.4' : { style: [ 76, 115, 0, .2] }, /* #4 Potentiell kontinuitetsskog (2015) */
};
const layerHierarchy = [
@@ -4123,6 +4133,27 @@ const layerHierarchy = [
text: 'Sumpskogar',
layer: 'nv.sumpskog',
},
+ {
+ text: 'Sannolikt och potentiell kontinuitetsskog',
+ children: [
+ {
+ text: 'Sannolikt kontinuitetsskog (preciserad)',
+ layer: 'kskog.1',
+ },
+ {
+ text: 'Sannolikt påverkad kontinuitetsskog (preciserad)',
+ layer: 'kskog.2',
+ },
+ {
+ text: 'Sannolikt kontinuitetsskog i fjällen (grövre precisering)',
+ layer: 'kskog.3',
+ },
+ {
+ text: 'Potentiell kontinuitetsskog (2015)',
+ layer: 'kskog.4',
+ },
+ ],
+ }
]
},
{
@@ -4190,7 +4221,7 @@ const styles = (function() {
}, {});
})();
-const [vectorLayers, featureOverlayLayer] = (function() {
+const [mapLayers, featureOverlayLayer] = (function() {
const xyz = '/{z}/{x}/{y}.pbf';
const tileGrid = createXYZ({
extent: extent,
@@ -4199,11 +4230,12 @@ const [vectorLayers, featureOverlayLayer] = (function() {
minZoom: 0,
maxZoom: 7,
});
+ const canWebGL2 = !!document.createElement('canvas').getContext('webgl2');
return [
Object.fromEntries(
/* Note: layers are added in the order below, so leave SvK and
* misc at the end so they show up on top of suface features */
- ['nv', 'mrr', 'nvr', 'ren', 'ri', 'sks', 'vbk', 'svk', 'misc']
+ ['kskog', 'nv', 'mrr', 'nvr', 'ren', 'ri', 'sks', 'vbk', 'svk', 'misc']
.map(function(k) {
let visible = false;
Object.keys(layers).forEach(function(lyr) {
@@ -4211,36 +4243,60 @@ const [vectorLayers, featureOverlayLayer] = (function() {
visible ||= styles[lyr] !== undefined;
}
});
- const vectorLayer = new VectorTileLayer({
- source: new VectorTile({
- url: '/tiles/' + k + xyz,
- format: new MVT(),
- projection: projection,
- wrapX: false,
- transition: 0,
- tileGrid: tileGrid,
- }),
- /* XXX switch to 'hybrid' if there are perf issues; but that seems to
- * put lines above points regardless of their respective z-index */
- renderMode: 'hybrid',
- declutter: false,
- visible: visible,
- style: function(feature, resolution) {
- const style = styles[k + '.' + feature.getProperties().layer];
- if (!Array.isArray(style)) {
- return style;
- } else {
- const maxi = style.length - 1;
- const z = 10 /* Math.log2(maxResolution) */ - Math.log2(resolution);
- /* use Math.floor() as VectorTile.js calls getZForResolution(resolution, 1) */
- const i = z <= 0 ? 0 : z >= maxi ? maxi : Math.floor(z);
- // console.log(`resolution=${resolution}, z=${z}, i=${i}`);
- return style[i];
+ let lyr;
+ if (k === 'kskog') {
+ if (!canWebGL2) {
+ return [k, null];
+ }
+ lyr = new TileLayerGL({
+ /* Naturvårdsverket has a WMS server we could use instead */
+ source: new GeoTIFF({
+ sources: [{
+ url: '/raster/' + k + '.tiff',
+ }],
+ normalize: false,
+ convertToRGB: false,
+ projection: projection,
+ wrapX: false,
+ interpolate: false,
+ }),
+ visible: false,
+ style: null,
+ });
+ }
+ else {
+ lyr = new VectorTileLayer({
+ source: new VectorTile({
+ url: '/tiles/' + k + xyz,
+ format: new MVT(),
+ projection: projection,
+ wrapX: false,
+ transition: 0,
+ tileGrid: tileGrid,
+ }),
+ /* XXX switch to 'hybrid' if there are perf issues; but that seems to
+ * put lines above points regardless of their respective z-index */
+ renderMode: 'hybrid',
+ declutter: false,
+ visible: visible,
+ style: function(feature, resolution) {
+ const style = styles[k + '.' + feature.getProperties().layer];
+ if (!Array.isArray(style)) {
+ return style;
+ } else {
+ const maxi = style.length - 1;
+ const z = 10 /* Math.log2(maxResolution) */ - Math.log2(resolution);
+ /* use Math.floor() as VectorTile.js calls getZForResolution(resolution, 1) */
+ const i = z <= 0 ? 0 : z >= maxi ? maxi : Math.floor(z);
+ // console.log(`resolution=${resolution}, z=${z}, i=${i}`);
+ return style[i];
+ }
}
- }});
- vectorLayer.set('layerGroup', k, true);
- map.addLayer(vectorLayer);
- return [k, vectorLayer];
+ });
+ lyr.set('layerGroup', k, true);
+ }
+ map.addLayer(lyr);
+ return [k, lyr];
})),
/* We use a vector tile layer for featureOverlayLayer instead of a simple
@@ -4319,16 +4375,28 @@ const infoMetadataAccordions = [];
};
const fixLayerVisibility = function() {
const result = {}
+ const nodata = [ 0, 0, 0, .0];
+ const kskog_palette = [nodata, nodata, nodata, nodata, nodata];
Object.keys(layers).forEach(function(lyr) {
const layerGroup = lyr.split('.', 1)[0];
if (result[layerGroup] === undefined) {
result[layerGroup] = false;
}
result[layerGroup] ||= styles[lyr] !== undefined;
+ if (layerGroup === 'kskog') {
+ const i = parseInt(lyr.slice(layerGroup.length + 1))
+ kskog_palette[i] = styles[lyr] ?? nodata;
+ }
});
+ const kskog = mapLayers['kskog'];
+ if (kskog !== undefined && kskog !== null) {
+ /* XXX unfortunately calling .setStyle() makes the layer blink */
+ kskog.setStyle({ color: ['palette', ['band', 1], kskog_palette ] });
+ }
+
Object.entries(result).forEach(function([lyr, visible]) {
- const v = vectorLayers[lyr];
- if (v !== undefined) {
+ const v = mapLayers[lyr];
+ if (v !== undefined && v !== null) {
//console.log(lyr, visible);
v.setVisible(visible);
}
@@ -4346,6 +4414,10 @@ const infoMetadataAccordions = [];
layersParams = layersParams.match(/^\s*$/) ? [] : layersParams.split(' ');
if (event.target.checked) {
layerList.forEach(function(lyr) {
+ const l = mapLayers[lyr.split('.', 1)[0]];
+ if (l === undefined || l === null) {
+ return; /* keep unexisting layers (eg WebGL layers on a system without WebGL support) unselectable */
+ }
styles[lyr] = layers[lyr].style;
if (!layersParams.includes(lyr)) {
layersParams.push(lyr);
@@ -4362,8 +4434,8 @@ const infoMetadataAccordions = [];
layerList
.map((l) => l.split('.', 1)[0])
- .filter((v, i, arr) => arr.indexOf(v) === i)
- .forEach((l) => vectorLayers[l].getSource().changed());
+ .filter((v, i, arr) => mapLayers[v] !== undefined && mapLayers[v] !== null && arr.indexOf(v) === i)
+ .forEach((l) => mapLayers[l].getSource().changed());
searchParams.set('layers', layersParams.join(' '));
location.hash = '#' + searchParams.toString();
@@ -4647,7 +4719,7 @@ const infoMetadataAccordions = [];
}),
});
const updateFeatureOverlayLayer = function(layer_group, layer, id) {
- const lyr = vectorLayers[layer_group];
+ const lyr = mapLayers[layer_group];
if (lyr === undefined) {
return;
}