diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2025-06-07 15:43:40 +0200 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2025-06-07 22:57:11 +0200 |
commit | 7fe45e014711edf78d64ead8e44ae9f80e1e6008 (patch) | |
tree | c3d7882e84055a7c7542edb9d639327100928233 | |
parent | 1d89092b7b8725323158c9f06b5a4b0d70f2538a (diff) |
Add (WIP) WebGL layer for kontinuitetsskog.
We rename “vectorLayers” to “mapLayers” as it now contains a raster
layer.
-rw-r--r-- | main.js | 170 |
1 files changed, 121 insertions, 49 deletions
@@ -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; } |