aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2025-06-14 22:12:57 +0200
committerGuilhem Moulin <guilhem@fripost.org>2025-06-15 18:34:05 +0200
commitfd662f3d3fac0f8b11e8e883409b1828cbfca3bd (patch)
tree3f5b8b1760f8483f5ba1e4714debeee79e52d09b
parent0fc7bdd8bf374c36fa0ba27702d1fafed09277ac (diff)
Undo splitting out to multiple files.
The reason is that we want the different modules to produce side-effects (to avoid creating functions and keeping references to it) and we therefore need to control the order in which they are inlined during `vite build`. Unfortunately this doesn't seem to be possible right now, cf. https://github.com/storybookjs/storybook/issues/30768 . This reverts commits 670bba058d83620abdb3e8db5fd4ea89dba08142, 05a018f27aba3a20fd581cb88daa8afbbd3407de and 0fc7bdd8bf374c36fa0ba27702d1fafed09277ac.
-rw-r--r--main.js3989
-rw-r--r--src/layers.js1908
-rw-r--r--src/map.js103
-rw-r--r--src/popover.js1348
-rw-r--r--style.css (renamed from src/style.css)0
5 files changed, 3645 insertions, 3703 deletions
diff --git a/main.js b/main.js
index 2cdc19a..102e489 100644
--- a/main.js
+++ b/main.js
@@ -15,14 +15,22 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**********************************************************************/
+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';
import ScaleLine from 'ol/control/ScaleLine.js';
import Zoom from 'ol/control/Zoom.js';
import ZoomSlider from 'ol/control/ZoomSlider.js';
+import Overlay from 'ol/Overlay.js';
+
import MVT from 'ol/format/MVT.js';
import VectorTileLayer from 'ol/layer/VectorTile.js';
import VectorTile from 'ol/source/VectorTile.js';
@@ -33,173 +41,130 @@ import Polygon from 'ol/geom/Polygon.js';
import LineString from 'ol/geom/LineString.js';
import Point from 'ol/geom/Point.js';
+import CircleStyle from 'ol/style/Circle.js';
import Fill from 'ol/style/Fill.js';
import Icon from 'ol/style/Icon.js';
+import RegularShape from 'ol/style/RegularShape.js';
+import Stroke from 'ol/style/Stroke.js';
+import Style from 'ol/style/Style.js';
-import { Modal } from 'bootstrap';
-
-import { map, baseMapSource, extent, projection } from './src/map.js';
-import { layers } from './src/layers.js';
-import { disposePopover } from './src/popover.js';
-import './src/style.css';
-
-const age_filter_settings = {
- active: false,
- type: 'relative',
- operator: '<=',
- quantity: 1,
- unit: 'y',
- show_unknown: false,
- get_relative_date: function(quantity, unit) {
- if (quantity == null || isNaN(quantity) || unit == null) {
- return null;
- }
- /* use today noon localtime to avoid issues due to DST when substracting dates */
- const d = new Date();
- d.setHours(12, 0, 0, 0);
- switch (unit) {
- case 'd':
- d.setDate(d.getDate() - quantity);
- break;
- case 'w':
- d.setDate(d.getDate() - 7 * quantity);
- break;
- case 'm':
- d.setMonth(d.getMonth() - quantity);
- break;
- case 'y':
- d.setFullYear(d.getFullYear() - quantity);
- break;
- default:
- return null;
- }
- return d;
- },
- _min_ts: null,
- _max_ts: null,
- _date_to_ts: function(d) {
- if (d == null) {
- return null;
- }
- /* number of days since 1970-01-01; take both dates at 00:00:00.0 UTC */
- return Math.floor(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())/86_400_000);
- },
- setup_minmax: function() {
- this._min_ts = this._max_ts = null;
- switch (this.type) {
- case 'relative': {
- const date = this.get_relative_date(this.quantity, this.unit);
- const prop = {'<=':'_min_ts', '>=':'_max_ts'}[this.operator];
- this[prop] = this._date_to_ts(date);
- break;
- }
- case 'interval': {
- this._min_ts = this._date_to_ts(this.from);
- this._max_ts = this._date_to_ts(this.to);
- break;
- }
- }
- },
-};
+import proj4 from 'proj4';
+import { get as getProjection } from 'ol/proj.js';
+import { register as registerProjection } from 'ol/proj/proj4.js';
-let baseMapLayer = 'topowebb_nedtonad';
-(function() {
- const params = new URLSearchParams(window.location.hash.substring(1));
- const x = parseFloat(params.get('x'));
- const y = parseFloat(params.get('y'));
- if (!isNaN(x) && !isNaN(y)) {
- map.getView().setCenter([x, y]);
- }
- const z = parseFloat(params.get('z'));
- if (!isNaN(z)) {
- map.getView().setZoom(z);
- }
- if (!params.has('layers') || (!params.get('layers').match(/^\s*$/) &&
- /* compat redirect/layer subst for old non-hierachical names */
- !params.get('layers').split(' ').some((l) => l.includes('.')))) {
- params.set('layers', [
- 'svk.ledningar',
- 'svk.stolpar',
- 'svk.stationer',
- 'svk.transmissionsnatsprojekt',
- 'misc.gigafactories',
- 'misc.dammar',
- 'mrr.appr_ec',
- 'mrr.appl_ec',
- 'mrr.appr_ogd',
- 'mrr.appl_ogd',
- 'mrr.appr_met',
- 'mrr.appl_met',
- 'mrr.appr_dl',
- 'vbk.area_current',
- 'vbk.area_notcurrent',
- ].join(' '));
- location.hash = '#' + params.toString();
- }
+import { Modal, Popover } from 'bootstrap';
- if (params.has('basemap')) {
- baseMapLayer = params.get('basemap');
- }
- baseMapSource.setUrl(`https://minkarta.lantmateriet.se/map/topowebbcache?LAYER=${encodeURIComponent(baseMapLayer)}`);
+import './style.css';
- if (params.has('age-filter')) {
- (function(param) {
- if (param === '') {
- return;
- }
- /* eslint-disable-next-line no-useless-escape */
- const m0 = /^([ +\-]?)([0-9]+)([dwmy])$/.exec(param);
- if (m0 != null) {
- age_filter_settings.type = 'relative';
- age_filter_settings.operator = (m0[1] === ' ' || m0[1] === '+' || m0[1] === '') ? '>='
- : m0[1] === '-' ? '<='
- : null;
- age_filter_settings.quantity = parseInt(m0[2], 10);
- age_filter_settings.unit = m0[3];
- age_filter_settings.setup_minmax();
- age_filter_settings.active = true;
- return;
- }
- const m1 = /^([0-9]{8})-([0-9]{8})$/.exec(param); /* YYYYMMDD */
- if (m1 != null) {
- const parse_date = (m) => new Date(
- parseInt(m.slice(0,4), 10),
- parseInt(m.slice(4,6), 10)-1,
- parseInt(m.slice(6,8), 10),
- 12 /* use 12:00:00.0 like for the <input type="date"> */
- );
- age_filter_settings.type = 'interval';
- age_filter_settings.from = parse_date(m1[1]);
- age_filter_settings.to = parse_date(m1[2]);
- age_filter_settings.setup_minmax();
- age_filter_settings.active = true;
- return;
- }
- console.log(`Ignoring invalid value for 'age-filter' parameter: ${param}`);
- })(params.get('age-filter'));
- }
- if (params.has('show-unknown-age')) {
- const param = params.get('show-unknown-age');
- if (param === '0') {
- age_filter_settings.show_unknown = false;
- } else if (param === '1') {
- age_filter_settings.show_unknown = true;
- }
- }
-})();
-map.setTarget(document.getElementById('map'));
+proj4.defs('EPSG:3006', '+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs');
+registerProjection(proj4);
+
+const PROJECTION = getProjection('EPSG:3006');
+
+/* Lantmäteriet uses a tile-scheme where the origin (upper-left corner) is at
+ * N8500000 E-1200000 (SWEREF99 TM), where each tile is 256×256 pixels, and where
+ * the resolution at level 0 is 4096m per pixel (each side is 1048.576km long).
+ *
+ * https://www.lantmateriet.se/globalassets/geodata/geodatatjanster/tb_twk_visning_cache_v1.1.0.pdf
+ * https://www.lantmateriet.se/globalassets/geodata/geodatatjanster/tb_twk_visning-oversiktlig_v1.0.3.pdf
+ *
+ * We set the extent to a 4×4 tiles square at level 2 (1024px = 1048.576km per
+ * side) somehow centered on Norrbotten and Västerbotten, and zoom in from there.
+ * This represent a TILEROW (x) offset of 5, and a TILECOL (y) offset of 2.
+ */
+const EXTENT = [110720, 6927136, 1159296, 7975712];
+
+/* XXX using the topowebbcache WMTS is fine for testing (as it doesn't require
+ * authentication) but not in production in a public instance as doing so would
+ * violate its current terms of use (as of January 2024 it's not CC0 open data).
+ * See
+ *
+ * https://www.lantmateriet.se/sv/om-lantmateriet/Rattsinformation/upphovsratt-och-publicering-av-lantmateriets-geografiska-information/
+ * https://www.lantmateriet.se/sv/kartor/vara-karttjanster/min-karta/#anchor-2
+ * https://help.locusmap.eu/topic/support-for-swedish-lantmateriets-min-karta-wms
+ *
+ * More precise background maps might be available in the future as open data,
+ * though:
+ *
+ * https://www.lantmateriet.se/sv/om-lantmateriet/press/nyheter/lantmateriets-arbete-mot-oppna-data-i-full-gang/
+ * https://ext-geodatakatalog.lansstyrelsen.se/GeodataKatalogen/srv/swe/catalog.search#/map uses
+ * https://api.lantmateriet.se/open/topowebb-ccby/v1/wmts/token/3c3a9cf47e7cb5ea24542d40d19698/?layer=topowebb&style=default&tilematrixset=3006&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image/png&TileMatrix=7&TileCol=237&TileRow=155
+ */
+const [BASEMAP, MAP] = (function() {
+ const param = 'basemap';
+ const baseMap = Object.seal({
+ _layer: new URLSearchParams(location.hash.substring(1))?.get?.(param) ?? 'topowebb_nedtonad',
+ get layer() {
+ return this._layer;
+ },
+ get url() {
+ return 'https://minkarta.lantmateriet.se/map/topowebbcache?' +
+ 'LAYER=' + encodeURIComponent(this.layer);
+ },
+ set layer(layername) {
+ this._layer = layername;
+ baseMapSource.setUrl(this.url);
+ const searchParams = new URLSearchParams(location.hash.substring(1));
+ searchParams.set(param, layername);
+ location.hash = '#' + searchParams.toString();
+ },
+ });
+ const baseMapSource = new WMTS({
+ url: baseMap.url,
+ version: '1.0.0',
+ style: 'default',
+ matrixSet: '3006',
+ format: 'image/png',
+ tileGrid: new WMTSTileGrid({
+ extent: EXTENT,
+ // https://www.lantmateriet.se/globalassets/geodata/geodatatjanster/tb_twk_visning-oversiktlig_v1.0.3.pdf
+ tileSize: 256,
+ origin: [-1200000, 8500000],
+ resolutions: [4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8],
+ matrixIds: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+ }),
+ projection: PROJECTION,
+ wrapX: false,
+ crossOrigin: 'anonymous',
+ });
+
+ const view = new View({
+ projection: PROJECTION,
+ extent: EXTENT,
+ showFullExtent: true,
+ /* center of the bbox of the Norrbotten and Västerbotten geometries */
+ center: [694767.48, 7338176.57],
+ zoom: 1,
+ enableRotation: false,
+ resolutions: [1024, 512, 256, 128, 64, 32, 16, 8],
+ constrainResolution: false,
+ });
+ return [
+ baseMap,
+ new Map({
+ controls: [],
+ view: view,
+ layers: [
+ new TileLayer({
+ source: baseMapSource
+ }),
+ ],
+ target: document.getElementById('map'),
+ }),
+ ];
+})();
/* move the control container to the viewport */
-const container = document.getElementById('map-control-container');
+const CONTAINER_MAP = document.getElementById('map-control-container');
+const CONTAINER_STOPEVENT = MAP.getViewport().getElementsByClassName('ol-overlaycontainer-stopevent')[0];
(function() {
- const container0 = map.getViewport().getElementsByClassName('ol-overlaycontainer-stopevent')[0];
- container0.appendChild(document.getElementById('zoom-control'));
- container0.appendChild(container);
- container0.appendChild(document.getElementById('info-modal'));
+ CONTAINER_STOPEVENT.appendChild(document.getElementById('zoom-control'));
+ CONTAINER_STOPEVENT.appendChild(CONTAINER_MAP);
+ CONTAINER_STOPEVENT.appendChild(document.getElementById('info-modal'));
const info_backdrop = document.createElement('div');
- container0.appendChild(info_backdrop);
+ CONTAINER_STOPEVENT.appendChild(info_backdrop);
info_backdrop.id = 'info-modal-backdrop';
const age_filter = document.createElement('div');
@@ -207,10 +172,10 @@ const container = document.getElementById('map-control-container');
age_filter.classList.add('modal');
age_filter.setAttribute('tabindex', '-1');
age_filter.setAttribute('aria-hidden', 'true');
- container0.appendChild(age_filter);
+ CONTAINER_STOPEVENT.appendChild(age_filter);
const age_filter_backdrop = document.createElement('div');
age_filter_backdrop.id = 'age-filter-modal-backdrop';
- container0.appendChild(age_filter_backdrop);
+ CONTAINER_STOPEVENT.appendChild(age_filter_backdrop);
})();
/* zoom in/out */
@@ -233,7 +198,7 @@ const container = document.getElementById('map-control-container');
for (const btn of control.element.getElementsByTagName('button')) {
btn.classList.add('btn', 'btn-light');
}
- map.addControl(control);
+ MAP.addControl(control);
})();
/* zoom slider */
@@ -245,29 +210,29 @@ const container = document.getElementById('map-control-container');
for (const btn of control.element.getElementsByTagName('button')) {
btn.classList.add('btn', 'btn-light');
}
- map.addControl(control);
+ MAP.addControl(control);
})();
/* scale line */
(function() {
- const size = map.getSize();
+ const size = MAP.getSize();
const control = new ScaleLine({
units: 'metric',
minWidth: 150,
maxWidth: size[1] < 350 ? size[1] - 50 : 350,
- target: container,
+ target: CONTAINER_MAP,
});
control.element.classList.add('modal', 'modal-content');
- map.addControl(control);
+ MAP.addControl(control);
})();
-const menu = document.getElementById('map-menu');
+const MENU = document.getElementById('map-menu');
const TRAILING_ZEROES = /\.?0*$/;
/* "open in new tab" button */
if (window.location !== window.parent.location) {
const div = document.createElement('div');
- menu.appendChild(div);
+ MENU.appendChild(div);
div.classList.add('ol-unselectable', 'ol-control');
const btn = document.createElement('button');
@@ -283,12 +248,12 @@ if (window.location !== window.parent.location) {
i.classList.add('bi', 'bi-box-arrow-up-right');
btn.onclick = function() {
- const coordinates = map.getView().getCenter();
+ const coordinates = MAP.getView().getCenter();
const url = new URL(window.location.href);
const searchParams = new URLSearchParams(url.hash.substring(1));
searchParams.set('x', coordinates[0].toFixed(2).replace(TRAILING_ZEROES, ''));
searchParams.set('y', coordinates[1].toFixed(2).replace(TRAILING_ZEROES, ''));
- searchParams.set('z', map.getView().getZoom().toFixed(3).replace(TRAILING_ZEROES, ''));
+ searchParams.set('z', MAP.getView().getZoom().toFixed(3).replace(TRAILING_ZEROES, ''));
url.hash = '#' + searchParams.toString();
return window.open(url.href, '_blank');
};
@@ -302,7 +267,7 @@ if (window.location === window.parent.location) {
{id: 'age-filter', title: 'Filtrera objekt efter ålder', bi: 'clock-history'},
].map(function(x) {
const div = document.createElement('div');
- menu.appendChild(div);
+ MENU.appendChild(div);
div.id = x.id + '-button';
div.classList.add('ol-unselectable', 'ol-control');
@@ -362,16 +327,16 @@ if (window.location === window.parent.location) {
labelActive: labelActive,
tipLabel: titleInactive,
keys: true,
- target: menu,
- })
+ target: MENU,
+ });
const btn = control.element.getElementsByTagName('button')[0];
btn.classList.add('btn', classInactive);
btn.setAttribute('aria-label', btn.title);
- map.addControl(control);
+ MAP.addControl(control);
control.addEventListener('enterfullscreen', function() {
/* dispose popover as entering fullscreen messes up its position */
- disposePopover()
+ disposePopover();
const btn = control.element.getElementsByTagName('button')[0];
btn.classList.replace(classInactive, classActive);
@@ -383,7 +348,7 @@ if (window.location === window.parent.location) {
/* hide export button in fullscreen mode as it exits it */
exp.classList.add('d-none');
}
- })
+ });
control.addEventListener('leavefullscreen', function() {
/* dispose popover as is might overflow the viewport */
disposePopover();
@@ -397,7 +362,7 @@ if (window.location === window.parent.location) {
if (exp !== undefined) {
exp.classList.remove('d-none');
}
- })
+ });
}
/* export/download button */
@@ -416,17 +381,17 @@ if (window.location === window.parent.location) {
const i = document.createElement('i');
btn.appendChild(i);
i.classList.add('bi', 'bi-download');
- menu.appendChild(div);
+ MENU.appendChild(div);
btn.onclick = function() {
- map.once('rendercomplete', function() {
+ MAP.once('rendercomplete', function() {
const canvas0 = document.createElement('canvas');
- const size = map.getSize();
+ const size = MAP.getSize();
canvas0.width = size[0];
canvas0.height = size[1];
const context = canvas0.getContext('2d');
- map.getViewport().querySelectorAll('.ol-layer canvas, canvas.ol-layer').forEach(function(canvas) {
+ MAP.getViewport().querySelectorAll('.ol-layer canvas, canvas.ol-layer').forEach(function(canvas) {
if (canvas.width > 0) {
const opacity = canvas.parentNode.style.opacity || canvas.style.opacity;
context.globalAlpha = opacity === '' ? 1 : Number(opacity);
@@ -447,14 +412,14 @@ if (window.location === window.parent.location) {
});
});
- map.renderSync();
+ MAP.renderSync();
};
}
/* info button */
(function() {
const div = document.createElement('div');
- menu.appendChild(div);
+ MENU.appendChild(div);
div.id = 'info-button';
div.classList.add('ol-unselectable', 'ol-control');
@@ -569,7 +534,7 @@ if (window.location === window.parent.location) {
/* show creation time of the MVT layers */
const li = document.createElement('li');
li.classList.add('list-group-item', 'text-muted');
- ul.appendChild(li)
+ ul.appendChild(li);
const i = document.createElement('i');
i.classList.add('bi', 'bi-map');
li.appendChild(i);
@@ -603,9 +568,9 @@ if (window.location === window.parent.location) {
const li = document.createElement('li');
li.classList.add('list-group-item');
- ul.appendChild(li)
+ ul.appendChild(li);
const h = document.createElement('h6');
- li.appendChild(h)
+ li.appendChild(h);
if (x.description != null) {
const t = document.createTextNode(x.description);
h.appendChild(t);
@@ -613,14 +578,14 @@ if (window.location === window.parent.location) {
if (x.copyright != null) {
const p = document.createElement('p');
- li.appendChild(p)
+ li.appendChild(p);
const t = document.createTextNode(x.copyright);
p.appendChild(t);
}
if (x.license != null) {
const p = document.createElement('p');
- li.appendChild(p)
+ li.appendChild(p);
p.appendChild(document.createTextNode('Licensvillkor: '));
const t = document.createTextNode(x.license.name);
if (x.license.url == null) {
@@ -636,7 +601,7 @@ if (window.location === window.parent.location) {
if (x.product_url != null) {
const p = document.createElement('p');
- li.appendChild(p)
+ li.appendChild(p);
const t = document.createTextNode('Produktlänk ');
const i = document.createElement('i');
i.classList.add('bi', 'bi-box-arrow-up-right');
@@ -675,7 +640,7 @@ if (window.location === window.parent.location) {
const d = new Date(x.last_modified);
const td = document.createTextNode(d.toLocaleDateString('sv-SE'));
p.appendChild(td);
- const t2 = document.createTextNode('.')
+ const t2 = document.createTextNode('.');
p.appendChild(t2);
}
});
@@ -686,9 +651,2046 @@ if (window.location === window.parent.location) {
})();
/* we're all set, show the control container now */
-container.setAttribute('aria-hidden', 'false');
+CONTAINER_MAP.setAttribute('aria-hidden', 'false');
+
+/* age filter settings state */
+const ageFilterSettings = (function() {
+ const dateToTS = function(d) {
+ if (d != null) {
+ /* number of days since 1970-01-01; take both dates at 00:00:00.0 UTC */
+ return Math.floor(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())/86_400_000);
+ }
+ };
+ return Object.seal({
+ active: false,
+ type: 'relative',
+ operator: '<=',
+ quantity: 1,
+ unit: 'y',
+ show_unknown: false,
+ _min_ts: null,
+ _max_ts: null,
+ getRelativeDate: function(quantity, unit) {
+ if (quantity == null || isNaN(quantity) || unit == null) {
+ return null;
+ }
+ /* use today noon localtime to avoid issues due to DST when substracting dates */
+ const d = new Date();
+ d.setHours(12, 0, 0, 0);
+ switch (unit) {
+ case 'd':
+ d.setDate(d.getDate() - quantity);
+ break;
+ case 'w':
+ d.setDate(d.getDate() - 7 * quantity);
+ break;
+ case 'm':
+ d.setMonth(d.getMonth() - quantity);
+ break;
+ case 'y':
+ d.setFullYear(d.getFullYear() - quantity);
+ break;
+ default:
+ return null;
+ }
+ return d;
+ },
+ setupMinMax: function() {
+ this._min_ts = this._max_ts = null;
+ switch (this.type) {
+ case 'relative': {
+ const date = this.getRelativeDate(this.quantity, this.unit);
+ const prop = {'<=':'_min_ts', '>=':'_max_ts'}[this.operator];
+ this[prop] = dateToTS(date);
+ break;
+ }
+ case 'interval': {
+ this._min_ts = dateToTS(this.from);
+ this._max_ts = dateToTS(this.to);
+ break;
+ }
+ }
+ },
+ });
+})();
+
+/* Layer style definitions */
+/* TODO: this should really be refactored… */
+const LAYERS = {
+ 'mrr.appr_ec': {
+ legend: { zoomLevel: 4 },
+ style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ return new Style({
+ zIndex: 22,
+ fill: new Fill({
+ color: [247, 170, 67, Math.max((.2-1)/8 * z + 1, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [151, 173, 23, 1],
+ }),
+ });
+ }),
+ },
+ 'mrr.appl_ec': {
+ legend: { zoomLevel: 4 },
+ style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ return new Style({
+ zIndex: 25,
+ fill: new Fill({
+ color: [247, 170, 67, Math.max((.2-1)/8 * z + 1, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [197, 14, 31, 1],
+ lineDash: width >= 1.5 ? [2 * width] : undefined,
+ }),
+ });
+ }),
+ },
+ 'mrr.appr_met': {
+ legend: { zoomLevel: 4 },
+ style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ return new Style({
+ zIndex: 24,
+ fill: new Fill({
+ color: [0, 0, 0, Math.max((.2-.4)/4 * z + .4, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [151, 173, 23, 1],
+ }),
+ });
+ }),
+ },
+ 'mrr.appl_met': {
+ legend: { zoomLevel: 4 },
+ style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ return new Style({
+ zIndex: 26,
+ fill: new Fill({
+ color: [0, 0, 0, Math.max((.2-.4)/4 * z + .4, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [197, 14, 31, 1],
+ lineDash: width >= 1.5 ? [2 * width] : undefined,
+ }),
+ });
+ }),
+ },
+ 'mrr.appr_ogd': {
+ legend: { zoomLevel: 4 },
+ style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ return new Style({
+ zIndex: 24,
+ fill: new Fill({
+ color: [30, 55, 87, Math.max((.2-.4)/4 * z + .4, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [151, 173, 23, 1],
+ }),
+ });
+ }),
+ },
+ 'mrr.appl_ogd': {
+ legend: { zoomLevel: 4 },
+ style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ return new Style({
+ zIndex: 26,
+ fill: new Fill({
+ color: [30, 55, 87, Math.max((.2-.4)/4 * z + .4, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [197, 14, 31, 1],
+ lineDash: width >= 1.5 ? [2 * width] : undefined,
+ }),
+ });
+ }),
+ },
+ 'mrr.appr_dl': {
+ legend: { zoomLevel: 4 },
+ style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ return new Style({
+ zIndex: 20,
+ fill: new Fill({
+ color: [228, 53, 45, Math.max((.2-1)/6 * z + 1, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [151, 173, 23, 1],
+ }),
+ });
+ }),
+ },
+
+ 'svk.ledningar': {
+ legend: { zoomLevel: 5, type: 'linestring', reuse_canvas: true },
+ style: [1, 1.5, 2, 2, 2, 2, 3, 4, 5, 6, 8, 10].map(function(width) {
+ return new Style({
+ zIndex: 52,
+ stroke: new Stroke({
+ color: 'black',
+ width: width,
+ }),
+ });
+ }),
+ },
+ 'svk.stolpar': {
+ legend: { zoomLevel: 5, type: 'point' },
+ style: [undefined, undefined, undefined, undefined, undefined]
+ .concat([3, 4, 5, 6, 8, 10, 15].map(function(radius) {
+ return new Style({
+ zIndex: 51,
+ image: new CircleStyle({
+ radius: radius,
+ fill: new Fill({
+ color: 'black',
+ }),
+ }),
+ });
+ })),
+ },
+ 'svk.transmissionsnatsprojekt': {
+ legend: { zoomLevel: 5, type: 'linestring' },
+ style: [1, 1.5, 2, 2, 2, 2, 3, 4, 5, 6, 8, 10].map(function(width) {
+ return new Style({
+ zIndex: 53,
+ stroke: new Stroke({
+ color: 'black',
+ width: width,
+ lineDash: [4 * width],
+ }),
+ });
+ }),
+ },
+ 'svk.stationer': {
+ legend: { zoomLevel: 3, type: 'point' },
+ style: [3, 4, 5, 6, 7, 8.5, 10].map(function(radius) {
+ return new Style({
+ zIndex: 50,
+ image: new RegularShape({
+ radius: radius,
+ points: 4,
+ angle: Math.PI/4,
+ fill: new Fill({
+ color: 'black',
+ }),
+ }),
+ });
+ })
+ .concat([.5, 1, 1.5, 2, 2].map(function(width) {
+ return new Style({
+ zIndex: 50,
+ fill: new Fill({
+ color: 'rgba(128, 128, 128, .7)',
+ }),
+ stroke: new Stroke({
+ width: width,
+ color: 'rgb(0, 0, 0)',
+ }),
+ });
+ })),
+ },
+
+ 'vbk.area_current': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ return new Style({
+ zIndex: 10,
+ fill: new Fill({
+ color: [168, 198, 223, Math.max((.2-1)/8 * z + 1, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [56, 96, 130, 1],
+ }),
+ });
+ }),
+ },
+ 'vbk.area_notcurrent': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ return new Style({
+ zIndex: 10,
+ fill: new Fill({
+ color: [222, 163, 199, Math.max((.2-1)/8 * z + 1, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [148, 55, 112, 1],
+ lineDash: width >= 1.5 ? [2 * width] : undefined,
+ }),
+ });
+ }),
+ },
+ 'vbk.offshore_completed': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 17,
+ fill: new Fill({
+ color: [38, 107, 29, .5],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [38, 107, 29, 1],
+ }),
+ });
+ }),
+ },
+ 'vbk.offshore_approved': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 16,
+ fill: new Fill({
+ color: [56, 160, 44, .5],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [56, 160, 44, 1],
+ }),
+ });
+ }),
+ },
+ 'vbk.offshore_amended': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ const w = z < 4 ? .5 : z <= 5 ? 1.5 : 4;
+ patternCanvas.width = width/2;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'rgba(247, 105, 162, 1)';
+ patternContext.beginPath();
+ patternContext.arc(.75*patternCanvas.width, .75*patternCanvas.height, 1.5*w, 0, 2*Math.PI, true);
+ patternContext.fill();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 17,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: 2*w,
+ color: [247, 105, 162, 1],
+ lineDash: [8 * w],
+ }),
+ });
+ }),
+ },
+ 'vbk.offshore_rejected': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 11,
+ fill: new Fill({
+ color: [227, 26, 28, .5],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [227, 26, 28, 1],
+ }),
+ });
+ }),
+ },
+ 'vbk.offshore_appealed': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 15,
+ fill: new Fill({
+ color: [177, 88, 40, .5],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [177, 88, 40, 1],
+ }),
+ });
+ }),
+ },
+ 'vbk.offshore_applied': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 14,
+ fill: new Fill({
+ color: [255, 127, 0, .5],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [255, 128, 0, 1],
+ }),
+ });
+ }),
+ },
+ 'vbk.offshore_consultation': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 13,
+ fill: new Fill({
+ color: [254, 217, 118, .65],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [254, 183, 82, 1],
+ }),
+ });
+ }),
+ },
+ 'vbk.offshore_investigation': {
+ legend: { zoomLevel: 1 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ const w = z < 4 ? .5 : z <= 5 ? 1.5 : 4;
+ patternCanvas.width = width*2;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(68, 90, 166, 1)';
+ patternContext.lineWidth = w;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 12,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: 2*w,
+ color: [68, 90, 166, 1],
+ lineDash: [8 * w],
+ }),
+ });
+ }),
+ },
+ 'vbk.offshore_revoked': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 10,
+ fill: new Fill({
+ color: [105, 61, 154, .5],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [105, 62, 153, 1],
+ }),
+ });
+ }),
+ },
+ 'vbk.station_completed': {
+ legend: { zoomLevel: 7, type: 'point' },
+ style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
+ return scale === undefined ? undefined : new Style({
+ zIndex: 99,
+ image: new Icon({
+ src: '/assets/icons/wind-turbine-completed.svg',
+ declutter: 'none',
+ scale: scale,
+ }),
+ });
+ }),
+ },
+ 'vbk.station_processed': {
+ legend: { zoomLevel: 7, type: 'point' },
+ style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
+ return scale === undefined ? undefined : new Style({
+ zIndex: 99,
+ image: new Icon({
+ src: '/assets/icons/wind-turbine-processed.svg',
+ declutter: 'none',
+ scale: scale,
+ }),
+ });
+ }),
+ },
+ 'vbk.station_approved': {
+ legend: { zoomLevel: 7, type: 'point' },
+ style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
+ return scale === undefined ? undefined : new Style({
+ zIndex: 99,
+ image: new Icon({
+ src: '/assets/icons/wind-turbine-approved.svg',
+ declutter: 'none',
+ scale: scale,
+ }),
+ });
+ }),
+ },
+ 'vbk.station_revoked': {
+ legend: { zoomLevel: 7, type: 'point' },
+ style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
+ return scale === undefined ? undefined : new Style({
+ zIndex: 99,
+ image: new Icon({
+ src: '/assets/icons/wind-turbine-revoked.svg',
+ declutter: 'none',
+ scale: scale,
+ }),
+ });
+ }),
+ },
+ 'vbk.station_rejected': {
+ legend: { zoomLevel: 7, type: 'point' },
+ style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
+ return scale === undefined ? undefined : new Style({
+ zIndex: 99,
+ image: new Icon({
+ src: '/assets/icons/wind-turbine-rejected.svg',
+ declutter: 'none',
+ scale: scale,
+ }),
+ });
+ }),
+ },
+ 'vbk.station_dismounted': {
+ legend: { zoomLevel: 7, type: 'point' },
+ style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
+ return scale === undefined ? undefined : new Style({
+ zIndex: 99,
+ image: new Icon({
+ src: '/assets/icons/wind-turbine-dismounted.svg',
+ declutter: 'none',
+ scale: scale,
+ }),
+ });
+ }),
+ },
+ 'vbk.station_appealed': {
+ legend: { zoomLevel: 7, type: 'point' },
+ style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
+ return scale === undefined ? undefined : new Style({
+ zIndex: 99,
+ image: new Icon({
+ src: '/assets/icons/wind-turbine-appealed.svg',
+ declutter: 'none',
+ scale: scale,
+ }),
+ });
+ }),
+ },
+
+ /* Documentation at
+ * https://www.skogsstyrelsen.se/globalassets/sjalvservice/karttjanster/geodatatjanster/produktbeskrivningar/utforda-avverkningar---produktbeskrivning.pdf
+ * */
+ 'avverk.utford': {
+ legend: { zoomLevel: 7 },
+ style: [0, 0, 0, 0, 0, .5, .75, 1, 1, 1, 1, 1].map(function(width, z) {
+ return new Style({
+ zIndex: 10,
+ fill: new Fill({
+ color: [255, 102, 102, Math.max((.2-1)/8 * z + 1, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [204, 0, 0, 1],
+ }),
+ });
+ }),
+ },
+ /* Documentation at
+ * https://www.skogsstyrelsen.se/globalassets/sjalvservice/karttjanster/geodatatjanster/produktbeskrivningar/yttre-granser-for-avverkningsanmalda-omraden---produktbeskrivning.pdf
+ * */
+ 'avverk.anmald': {
+ legend: { zoomLevel: 7 },
+ style: [0, 0, 0, 0, 0, .5, .75, 1, 1, 1, 1, 1].map(function(width, z) {
+ return new Style({
+ zIndex: 10,
+ fill: (width === undefined || width === 0) ?
+ new Fill({ color: [255, 102, 102, Math.max((.2-1)/8 * z + 1, 0)*.75] }) :
+ (function() {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ const slope = 45 * Math.PI/180;
+ const spacing = z < 10 ? z*2 : 40;
+ const len = Math.hypot(1, slope);
+ const w = patternCanvas.width = Math.round(1/len + spacing);
+ const h = patternCanvas.height = Math.round(slope/len + spacing * slope);
+
+ patternContext.fillStyle = 'rgba(255, 102, 102, .1)';
+ patternContext.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
+ patternContext.strokeStyle = 'rgba(204, 0, 0, 1)';
+ patternContext.lineWidth = Math.max(1, width/2);
+ patternContext.beginPath();
+ patternContext.moveTo(0, h);
+ patternContext.lineTo(w, 0);
+ patternContext.moveTo(-w, h);
+ patternContext.lineTo(w, -h);
+ patternContext.moveTo(0, 2*h);
+ patternContext.lineTo(2*w, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Fill({ color: context.createPattern(patternCanvas, 'repeat') });
+ })(),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [204, 0, 0, 1],
+ lineDash: width >= 1.5 ? [2 * width] : undefined,
+ }),
+ });
+ }),
+ },
+
+ 'skydd.tilltradesforbud': {
+ legend: { zoomLevel: 2 },
+ style: [1, 1.5, 2, 3, 3.5, 4, 5, 5, 6, 7, 8, 10].map(function(width) {
+ return new Style({
+ zIndex: 23,
+ fill: new Fill({
+ /* transparent fill so clicking the inside of the polygon triggers a popover */
+ /* XXX could also use a custom renderer but that doesn't seem to work */
+ color: [0, 0, 0, 0],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [255, 0, 0, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.nationalpark': {
+ legend: { zoomLevel: 1 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(0, 55, 0, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 22,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [0, 55, 0, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.naturreservat': {
+ legend: { zoomLevel: 1 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(7, 181, 7, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 21,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [7, 181, 7, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.naturreservat_kommunalt': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(7, 181, 7, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, -patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, 0);
+ patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 20,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [7, 181, 7, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.naturvardsomrade': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(176, 255, 176, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 19,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [176, 255, 176, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.djur_och_vaxtskyddsomrade': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(255, 255, 0, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 18,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [255, 255, 0, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.kulturreservat': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(154, 102, 255, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 17,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [154, 102, 255, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.vattenskyddsomrade': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(0, 105, 212, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 16,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [0, 105, 212, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.landskapsbildsskyddsomrade': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(135, 110, 71, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 15,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [134, 110, 71, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.skogligt_biotopskyddsomrade': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(135, 90, 71, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 14,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 4 ? .5 : z <= 5 ? 1 : 2,
+ color: [134, 90, 71, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.ovrigt_biotopskyddsomrade': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(255, 95, 0, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 13,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 4 ? .5 : z <= 5 ? 1 : 2,
+ color: [255, 95, 0, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.naturminne_yta': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(113, 0, 116, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 12,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [134, 0, 116, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.naturminne_punkt': {
+ legend: { zoomLevel: 6, type: 'point' },
+ style: [undefined, undefined, undefined, undefined].concat([3, 4, 6, 8, 12, 16, 20, 24].map(function(width) {
+ return new Style({
+ zIndex: 12,
+ image: new CircleStyle({
+ radius: width,
+ fill: new Fill({
+ color: 'rgba(113, 0, 116, .5)',
+ }),
+ stroke: new Stroke({
+ width: Math.log2(width)/2,
+ color: 'rgba(113, 0, 116, 1)',
+ }),
+ }),
+ });
+ }))
+ },
+ 'skydd.interimistiskt_forbud': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(168, 0, 0, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 11,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [168, 0, 0, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.fageldirektivet': {
+ legend: { zoomLevel: 1 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width*2;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(230, 0, 0, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.beginPath();
+ patternContext.lineWidth *= 6;
+ patternContext.moveTo(-.5*patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -.5*patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 1.5*patternCanvas.height);
+ patternContext.lineTo(1.5*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 10,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 4 ? .5 : z <= 5 ? 1 : 2,
+ color: [230, 0, 0, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.habitatdirektivet': {
+ legend: { zoomLevel: 1 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width*2;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(0, 77, 168, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, -patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, 0);
+ patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
+ patternContext.stroke();
+ patternContext.beginPath();
+ patternContext.lineWidth *= 6;
+ patternContext.moveTo(0, -.5*patternCanvas.height);
+ patternContext.lineTo(1.5*patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(-.5*patternCanvas.width, 0);
+ patternContext.lineTo(patternCanvas.width, 1.5*patternCanvas.height);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 10,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 4 ? .5 : z <= 5 ? 1 : 2,
+ color: [0, 77, 168, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.helcom': {
+ legend: { zoomLevel: 1 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'rgba(130, 130, 130, 1)';
+ const r = z < 5 ? (z+1)*.75 : z*.5;
+ patternContext.beginPath();
+ patternContext.arc(.5*patternCanvas.width, .5*patternCanvas.height, r, 0, 2*Math.PI, true);
+ patternContext.fill();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 9,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [130, 130, 130, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.ramsar': {
+ legend: { zoomLevel: 1 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'rgba(195, 0, 255, 1)';
+ const r = z < 5 ? (z+1)*.75 : z*.5;
+ patternContext.beginPath();
+ patternContext.arc(.25*patternCanvas.width, .25*patternCanvas.height, r, 0, 2*Math.PI, true);
+ patternContext.fill();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 9,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [195, 0, 255, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.ospar': {
+ legend: { zoomLevel: 1 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'rgba(168, 0, 0, 1)';
+ const r = z < 5 ? (z+1)*.75 : z*.5;
+ patternContext.beginPath();
+ patternContext.arc(.25*patternCanvas.width, .75*patternCanvas.height, r, 0, 2*Math.PI, true);
+ patternContext.fill();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 9,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [168, 0, 0, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.varldsarv': {
+ legend: { zoomLevel: 1 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'rgba(168, 0, 0, 1)';
+ const r = z < 5 ? (z+1)*.75 : z*.5;
+ patternContext.beginPath();
+ patternContext.arc(.75*patternCanvas.width, .25*patternCanvas.height, r, 0, 2*Math.PI, true);
+ patternContext.fill();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 9,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [168, 0, 0, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.biosfarsomraden': {
+ legend: { zoomLevel: 1 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'rgba(131, 0, 219, 1)';
+ const r = z < 5 ? (z+1)*.75 : z*.5;
+ patternContext.beginPath();
+ patternContext.arc(.75*patternCanvas.width, .75*patternCanvas.height, r, 0, 2*Math.PI, true);
+ patternContext.fill();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 9,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [131, 0, 219, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.naturvardsavtal': {
+ legend: { zoomLevel: 1 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(255, 0, 197, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 21,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 4 ? .5 : z <= 5 ? 1 : 2,
+ color: [255, 0, 197, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.naturvardsavtal_skogsstyrelsen': {
+ legend: { zoomLevel: 2 },
+ style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(255, 0, 197, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, -patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, 0);
+ patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 20,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 4 ? .5 : z <= 5 ? 1 : 2,
+ color: [255, 0, 197, 1],
+ }),
+ });
+ }),
+ },
+ 'skydd.atervatningsavtal': {
+ legend: { zoomLevel: 0 },
+ style: [0, 1, 2, 3, 4, 5, 6].map(function(width) {
+ return new Style({
+ zIndex: 5,
+ fill: new Fill({
+ color: [255, 115, 0, .4],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: .5,
+ color: [255, 115, 0, 1],
+ }),
+ });
+ })
+ .concat([7, 8, 9, 10, 11].map(function() {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = 16;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(255, 115, 0, 1)';
+ patternContext.lineWidth = 1;
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, -patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, 0);
+ patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 5,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: new Stroke({
+ width: 1.5,
+ color: [255, 115, 0, 1],
+ }),
+ });
+ })),
+ },
+ 'nv.naturvarde_sks': {
+ legend: { zoomLevel: 0 },
+ style: [0, 1, 2, 3, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 6,
+ fill: new Fill({
+ color: [255, 170, 0, .2],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: .5,
+ color: [255, 170, 0, .8],
+ }),
+ });
+ })
+ .concat([6, 7, 8, 9, 10, 11].map(function() {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = 16;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(255, 170, 0, 1)';
+ patternContext.lineWidth = 1;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 6,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: new Stroke({
+ width: 1.5,
+ color: [255, 170, 0, 1],
+ }),
+ });
+ })),
+ },
+ 'nv.nyckelbiotop': {
+ legend: { zoomLevel: 0 },
+ style: [0, 1, 2, 3, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 6,
+ fill: new Fill({
+ color: [217, 148, 9, .2],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: .5,
+ color: [217, 148, 9, .8],
+ }),
+ });
+ })
+ .concat([6, 7, 8, 9, 10, 11].map(function() {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = 16;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(217, 148, 9, 1)';
+ patternContext.lineWidth = 1;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 6,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: new Stroke({
+ width: 1.5,
+ color: [217, 148, 9, 1],
+ }),
+ });
+ })),
+ },
+ 'nv.nyckelbiotop_storskogsbruk': {
+ legend: { zoomLevel: 0 },
+ style: [0, 1, 2, 3, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 6,
+ fill: new Fill({
+ color: [217, 148, 9, .2],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: .5,
+ color: [217, 148, 9, .8],
+ }),
+ });
+ })
+ .concat([6, 7, 8, 9, 10, 11].map(function() {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = 16;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(217, 148, 9, 1)';
+ patternContext.lineWidth = 1;
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, -patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, 0);
+ patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 6,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: new Stroke({
+ width: 1.5,
+ color: [217, 148, 9, 1],
+ }),
+ });
+ })),
+ },
+ 'nv.sumpskog': {
+ legend: { zoomLevel: 5 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ const w = Math.max(1, width);
+ patternCanvas.width = z < 2 ? 2 : z < 4 ? 4 : z <= 5 ? 6 : 8;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(158, 200, 215, 1)';
+ patternContext.lineWidth = w;
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 5,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: w/2,
+ color: [158, 200, 215, 1],
+ }),
+ });
+ }),
+ },
+ 'nv.pagaende_naturreservatsbildning': {
+ legend: { zoomLevel: 1 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.setLineDash([width/4, width/4]);
+ patternContext.strokeStyle = 'rgba(7, 181, 7, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(width/4, 0);
+ patternContext.lineTo(width/4, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.beginPath();
+ patternContext.lineDashOffset = width/4;
+ patternContext.moveTo(3*width/4, 0);
+ patternContext.lineTo(3*width/4, patternCanvas.height);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 10,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 3 : 4,
+ color: [7, 181, 7, 1],
+ lineDash: [width/8, width/4],
+ }),
+ });
+ }),
+ },
+ 'nv.snus': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
+ return new Style({
+ zIndex: 4,
+ fill: new Fill({
+ color: [168,168,0,.2],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [168,77,0,.75],
+ }),
+ });
+ }),
+ },
+
+ 'ri.naturvard': {
+ legend: { zoomLevel: 0 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(154, 230, 0, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, -patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, 0);
+ patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 8,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [154, 230, 0, 1],
+ }),
+ });
+ }),
+ },
+ 'ri.friluftsliv': {
+ legend: { zoomLevel: 0 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(0, 127, 232, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, -patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, 0);
+ patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 8,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
+ color: [0, 127, 232, 1],
+ }),
+ });
+ }),
+ },
+ 'ri.rorligt_friluftsliv': {
+ legend: { zoomLevel: 0 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'rgba(187, 227, 212, .25)';
+ patternContext.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
+ patternContext.strokeStyle = 'rgba(56, 151, 117, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
+ patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, 2*patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 8,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 2 : z < 4 ? 4 : z <= 5 ? 8 : 16,
+ color: [56, 151, 117, 1],
+ lineDash: [width/4, width/3],
+ }),
+ });
+ }),
+ },
+ 'ri.obruten_kust': {
+ legend: { zoomLevel: 0 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'rgba(227, 227, 187, .25)';
+ patternContext.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
+ patternContext.strokeStyle = 'rgba(156, 158, 56, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, -patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, 0);
+ patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 8,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 2 : z < 4 ? 4 : z <= 5 ? 8 : 16,
+ color: [156, 158, 56, 1],
+ lineDash: [width/4, width/3],
+ }),
+ });
+ }),
+ },
+ 'ri.obrutet_fjall': {
+ legend: { zoomLevel: 0 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = width;
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'rgba(255, 255, 209, .25)';
+ patternContext.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
+ patternContext.strokeStyle = 'rgba(219, 183, 60, 1)';
+ patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(0, -patternCanvas.height);
+ patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
+ patternContext.stroke();
+ patternContext.moveTo(-patternCanvas.width, 0);
+ patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 8,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 2 : z < 4 ? 4 : z <= 5 ? 8 : 16,
+ color: [219, 183, 60, 1],
+ lineDash: [width/4, width/3],
+ }),
+ });
+ }),
+ },
+ 'ri.skyddade_vattendrag': {
+ legend: { zoomLevel: 0 },
+ style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
+ return new Style({
+ zIndex: 8,
+ fill: new Fill({
+ color: [102, 157, 240, .25],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: z < 2 ? 2 : z < 4 ? 4 : z <= 5 ? 8 : 16,
+ color: [41, 109, 197, 1],
+ lineDash: [width/4, width/3],
+ }),
+ });
+ }),
+ },
+
+ 'ren.betesomrade': {
+ legend: { zoomLevel: 0 },
+ style: [1, 1.5, 2, 3, 3.5, 4, 5, 5, 6, 7, 8, 10].map(function(width) {
+ return new Style({
+ zIndex: 4,
+ fill: new Fill({
+ /* transparent fill so clicking the inside of the polygon triggers a popover */
+ /* XXX could also use a custom renderer but that doesn't seem to work */
+ color: [0, 0, 0, 0],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [179, 153, 102, 1],
+ }),
+ });
+ }),
+ },
+ 'ren.flyttled': {
+ legend: { zoomLevel: 2, type: 'linestring' },
+ style: [.75, 1, 1.5, 2, 3, 4, 5, 5, 6, 7, 8, 10].map(function(width) {
+ return new Style({
+ zIndex: 7,
+ stroke: new Stroke({
+ width: 2*width,
+ color: [119, 99, 59, 1],
+ lineDash: [4 * width],
+ }),
+ });
+ }),
+ },
+ 'ren.riks_ren': {
+ legend: { zoomLevel: 1 },
+ style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
+ const patternCanvas = document.createElement('canvas');
+ const patternContext = patternCanvas.getContext('2d');
+ patternCanvas.width = z < 4 ? 4 : z <= 5 ? 8 : Math.pow(2, Math.round(Math.log2(width) + 3));
+ patternCanvas.height = patternCanvas.width;
+ patternContext.fillStyle = 'transparent';
+ patternContext.strokeStyle = 'rgba(179, 153, 102, 1)';
+ patternContext.lineWidth = Math.max(1, width/2);
+ patternContext.beginPath();
+ patternContext.moveTo(0, 0);
+ patternContext.lineTo(patternCanvas.width, 0);
+ patternContext.stroke();
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ return new Style({
+ zIndex: 6,
+ fill: new Fill({
+ color: context.createPattern(patternCanvas, 'repeat'),
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [179, 153, 102, 1],
+ }),
+ });
+ }),
+ },
+ 'ren.omr_riks': {
+ legend: { zoomLevel: 2 },
+ style: [.5, .5, 1, 1, 1, 1.5, 1.5, 1.5, 2, 2, 2, 2].map(function(width, z) {
+ return new Style({
+ zIndex: 5,
+ fill: new Fill({
+ color: [203, 190, 163, Math.max((.3-.5)/8 * z + .5, 0)],
+ }),
+ stroke: width === 0 ? undefined : new Stroke({
+ width: width,
+ color: [179, 153, 102, 1],
+ }),
+ });
+ }),
+ },
+
+ /* Documentation at
+ * https://www.smhi.se/polopoly_fs/1.34541!/dammprod%202013_3%2C%20beskrivning%2C%20SVAR2012_2.pdf
+ * */
+ 'misc.dammar': {
+ legend: { zoomLevel: 5, type: 'point' },
+ style: [2, 3, 4, 4, 4, 6, 8, 8, 8, 10, 16, 32].map(function(width) {
+ return new Style({
+ zIndex: 59,
+ image: new CircleStyle({
+ radius: width,
+ fill: new Fill({
+ color: 'rgb(219, 30, 42)',
+ }),
+ stroke: new Stroke({
+ width: Math.log2(width) * 2/5,
+ color: 'rgb(128, 17, 25)',
+ }),
+ }),
+ });
+ }),
+ },
+
+ 'misc.gigafactories': {
+ legend: { zoomLevel: 1, type: 'point' },
+ style: [4, 6, 7, 8, 10, 12].map(function(width) {
+ return new Style({
+ zIndex: 60,
+ image: new CircleStyle({
+ radius: width,
+ fill: new Fill({
+ color: 'rgb(152, 78, 163)',
+ }),
+ stroke: new Stroke({
+ width: Math.log2(width) * 2/5,
+ color: 'rgb(119, 61, 128)',
+ }),
+ }),
+ });
+ })
+ .concat([1.5, 2, 2, 2, 2, 2].map(function(width) {
+ return new Style({
+ zIndex: 58,
+ fill: new Fill({
+ color: 'rgba(152, 78, 163, .4)',
+ }),
+ stroke: new Stroke({
+ width: width,
+ color: 'rgb(119, 61, 128)',
+ }),
+ });
+ })),
+ },
+
+ '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) */
+};
+
+/* process URL parameters (other than 'basemap') */
+const STYLES = {};
+(function() {
+ const params = new URLSearchParams(window.location.hash.substring(1));
+ const x = parseFloat(params.get('x'));
+ const y = parseFloat(params.get('y'));
+ if (!isNaN(x) && !isNaN(y)) {
+ MAP.getView().setCenter([x, y]);
+ }
+ const z = parseFloat(params.get('z'));
+ if (!isNaN(z)) {
+ MAP.getView().setZoom(z);
+ }
+ if (!params.has('layers') || (!params.get('layers').match(/^\s*$/) &&
+ /* compat redirect/layer subst for old non-hierachical names */
+ !params.get('layers').split(' ').some((l) => l.includes('.')))) {
+ params.set('layers', [
+ 'svk.ledningar',
+ 'svk.stolpar',
+ 'svk.stationer',
+ 'svk.transmissionsnatsprojekt',
+ 'misc.gigafactories',
+ 'misc.dammar',
+ 'mrr.appr_ec',
+ 'mrr.appl_ec',
+ 'mrr.appr_ogd',
+ 'mrr.appl_ogd',
+ 'mrr.appr_met',
+ 'mrr.appl_met',
+ 'mrr.appr_dl',
+ 'vbk.area_current',
+ 'vbk.area_notcurrent',
+ ].join(' '));
+ location.hash = '#' + params.toString();
+ }
+
+ /* map each known parameter to a callback processing its value */
+ Object.entries({
+ 'layers': function(value) {
+ const layersParams = value.split(' ');
+ Object.entries(LAYERS)
+ .filter(([key]) => layersParams.includes(key))
+ .forEach(([key, lyr]) => STYLES[key] = lyr.style);
+ },
+
+ 'age-filter': function(value) {
+ /* eslint-disable-next-line no-useless-escape */
+ const m0 = /^([ +\-]?)([0-9]+)([dwmy])$/.exec(value);
+ if (m0 != null) {
+ ageFilterSettings.type = 'relative';
+ ageFilterSettings.operator = (m0[1] === ' ' || m0[1] === '+' || m0[1] === '') ? '>='
+ : m0[1] === '-' ? '<='
+ : null;
+ ageFilterSettings.quantity = parseInt(m0[2], 10);
+ ageFilterSettings.unit = m0[3];
+ ageFilterSettings.setupMinMax();
+ ageFilterSettings.active = true;
+ return;
+ }
+ const m1 = /^([0-9]{8})-([0-9]{8})$/.exec(value); /* YYYYMMDD */
+ if (m1 != null) {
+ const parse_date = (m) => new Date(
+ parseInt(m.slice(0,4), 10),
+ parseInt(m.slice(4,6), 10)-1,
+ parseInt(m.slice(6,8), 10),
+ 12 /* use 12:00:00.0 like for the <input type="date"> */
+ );
+ ageFilterSettings.type = 'interval';
+ ageFilterSettings.from = parse_date(m1[1]);
+ ageFilterSettings.to = parse_date(m1[2]);
+ ageFilterSettings.setupMinMax();
+ ageFilterSettings.active = true;
+ return;
+ }
+ //console.log(`Ignoring invalid value for 'age-filter' parameter: ${value}`);
+ },
+ 'show-unknown-age': function(value) {
+ if (value === '0') {
+ ageFilterSettings.show_unknown = false;
+ } else if (value === '1') {
+ ageFilterSettings.show_unknown = true;
+ }
+ },
+ })
+ .forEach(function([param, cb]) {
+ if (params.has(param)) {
+ cb(params.get(param));
+ }
+ });
+})();
-map.getView().on('change', function(event) {
+MAP.getView().on('change', function(event) {
const view = event.target;
disposePopover();
@@ -700,6 +2702,107 @@ map.getView().on('change', function(event) {
location.hash = '#' + searchParams.toString();
});
+/* add layers to the map */
+const mapLayers = (function() {
+ const baseurl = '/';
+ const xyz = '/{z}/{x}/{y}.pbf';
+ const tileGrid = createXYZ({
+ extent: EXTENT,
+ tileSize: 1024,
+ maxResolution: 1024, /* = 1048576/1024 */
+ minZoom: 0,
+ maxZoom: 7,
+ });
+ const isVisible = function(groupname) {
+ return Object.keys(LAYERS).some((layername) =>
+ layername.startsWith(groupname + '.') && STYLES[layername] != null);
+ };
+ const canWebGL2 = !!document.createElement('canvas').getContext('webgl2');
+
+ /* 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 */
+ const rasterLayers = ['kskog'];
+ const vectorLayers = ['nv', 'mrr', 'skydd', 'ren', 'ri', 'avverk', 'vbk', 'svk', 'misc'];
+ const canFilterByAge = ['avverk', 'mrr', 'vbk']; /* layers for which features are dated */
+
+ const ret = {};
+ if (!canWebGL2) {
+ rasterLayers.forEach((k) => ret[k] = null);
+ } else {
+ rasterLayers.forEach(function(k) {
+ ret[k] = new TileLayerGL({
+ /* Naturvårdsverket has a WMS server we could use instead, but by serving it ourselves
+ * we can filter on he various kskog classes */
+ source: new GeoTIFF({
+ sources: [{
+ url: baseurl + 'raster/' + k + '.tiff',
+ }],
+ normalize: false,
+ convertToRGB: false,
+ wrapX: false,
+ interpolate: false,
+ /* use the projection found in the source's metadata */
+ }),
+ visible: false,
+ style: null, /* filled later */
+ });
+ MAP.addLayer(ret[k]);
+ });
+ }
+
+ vectorLayers.forEach(function(k) {
+ const canFilterByAge0 = canFilterByAge.includes(k);
+ ret[k] = new VectorTileLayer({
+ source: new VectorTile({
+ url: baseurl + '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: isVisible(k),
+ style: function(feature, resolution) {
+ /* WARN: very hot code path! */
+ const properties = feature.getProperties();
+ if (ageFilterSettings.active) {
+ /* TODO avoid doing this checks for each feature; instead, set up a
+ * different style function if ageFilterSettings.active */
+ const ts = properties.ts;
+ if (ts == null) {
+ if (canFilterByAge0 && !ageFilterSettings.show_unknown) {
+ return null;
+ }
+ } else if ((ageFilterSettings._min_ts !== null && ts < ageFilterSettings._min_ts) ||
+ (ageFilterSettings._max_ts !== null && ts > ageFilterSettings._max_ts)) {
+ return null;
+ }
+ }
+ const style = STYLES[k + '.' + properties.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];
+ }
+ }
+ });
+ ret[k].set('layerGroup', k, true);
+ ret[k].set('canFilterByAge', canFilterByAge0, true);
+ MAP.addLayer(ret[k]);
+ });
+ return ret;
+})();
+
+/* layer hierarchy, for the layer selection, legend and info modal */
const layerHierarchy = [
{
text: 'Transmissionsnät för el',
@@ -1100,117 +3203,6 @@ const layerHierarchy = [
},
];
-const styles = (function() {
- const searchParams = new URLSearchParams(location.hash.substring(1));
- const layersParams = searchParams.has('layers') ? searchParams.get('layers').split(' ') : [];
- return Object.fromEntries(
- Object.entries(layers)
- .filter(([key]) => layersParams.includes(key))
- .map(([key, lyr]) => [key, lyr.style])
- );
-})();
-
-const mapLayers = (function() {
- const baseurl = '/';
- const xyz = '/{z}/{x}/{y}.pbf';
- const tileGrid = createXYZ({
- extent: extent,
- tileSize: 1024,
- maxResolution: 1024, /* = 1048576/1024 */
- minZoom: 0,
- maxZoom: 7,
- });
- const isVisible = function(groupname) {
- return Object.keys(layers).some((layername) =>
- layername.startsWith(groupname + '.') && styles[layername] !== undefined);
- };
- const canWebGL2 = !!document.createElement('canvas').getContext('webgl2');
-
- /* 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 */
- const rasterLayers = ['kskog'];
- const vectorLayers = ['nv', 'mrr', 'skydd', 'ren', 'ri', 'avverk', 'vbk', 'svk', 'misc'];
- const canFilterByAge = ['avverk', 'mrr', 'vbk']; /* layers for which features are dated */
-
- const ret = {};
- if (!canWebGL2) {
- rasterLayers.forEach((k) => ret[k] = null);
- } else {
- rasterLayers.forEach(function(k) {
- ret[k] = new TileLayerGL({
- /* Naturvårdsverket has a WMS server we could use instead, but by serving it ourselves
- * we can filter on he various kskog classes */
- source: new GeoTIFF({
- sources: [{
- url: baseurl + 'raster/' + k + '.tiff',
- }],
- normalize: false,
- convertToRGB: false,
- wrapX: false,
- interpolate: false,
- /* use the projection found in the source's metadata */
- }),
- visible: false,
- style: null, /* filled later */
- });
- map.addLayer(ret[k]);
- });
- }
-
- vectorLayers.forEach(function(k) {
- const canFilterByAge0 = canFilterByAge.includes(k);
- ret[k] = new VectorTileLayer({
- source: new VectorTile({
- url: baseurl + '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: isVisible(k),
- style: function(feature, resolution) {
- /* WARN: very hot code path! */
- const properties = feature.getProperties();
- if (age_filter_settings.active) {
- /* TODO avoid doing this checks for each feature; instead, set up a
- * different style function if age_filter_settings.active */
- const ts = properties.ts;
- if (ts == null) {
- if (canFilterByAge0 && !age_filter_settings.show_unknown) {
- return null;
- }
- } else if ((age_filter_settings._min_ts !== null && ts < age_filter_settings._min_ts) ||
- (age_filter_settings._max_ts !== null && ts > age_filter_settings._max_ts)) {
- return null;
- }
- }
- const style = styles[k + '.' + properties.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];
- }
- }
- });
- ret[k].set('layerGroup', k, true);
- ret[k].set('canFilterByAge', canFilterByAge0, true);
- map.addLayer(ret[k]);
- });
-
- return ret;
-})();
-
-
/* legend panel */
(function() {
const modal = document.getElementById('map-legend-panel');
@@ -1263,11 +3255,11 @@ const mapLayers = (function() {
.forEach(function(layer) {
/* add symbols for each layer */
const layerGroup = layer.split('.', 1)[0];
- if (!layerGroup || !mapLayers[layerGroup] || !layers[layer] || !layers[layer].style) {
+ if (!layerGroup || !mapLayers[layerGroup] || LAYERS[layer]?.style == null) {
console.log(`Could not find symbol for layer ${layer}, skipping`);
return;
}
- const legend = layers[layer].legend || {};
+ const legend = LAYERS[layer]?.legend ?? {};
if (canvas == null || !legend.reuse_canvas) {
canvas = document.createElement('canvas');
div.appendChild(canvas);
@@ -1279,15 +3271,15 @@ const mapLayers = (function() {
if (mapLayers[layerGroup].getSource() instanceof GeoTIFF) {
/* raster source */
render.setFillStrokeStyle(new Fill({
- color: layers[layer].style,
+ color: LAYERS[layer].style,
}));
return render.drawGeometry(symbols.polygon);
}
else if (mapLayers[layerGroup].getSource() instanceof VectorTile) {
/* vector source */
- const style = Array.isArray(layers[layer].style) ?
- layers[layer].style[legend.zoomLevel ?? 5] :
- layers[layer].style;
+ const style = Array.isArray(LAYERS[layer].style) ?
+ LAYERS[layer].style[legend.zoomLevel ?? 5] :
+ LAYERS[layer].style;
const legend_type = legend.type ?? 'polygon';
if (legend_type === 'point' && style.getImage(1) instanceof Icon && style.getImage(1).getSrc()) {
/* use a new <img> element since .setStyle() returns the same one and doesn't work in that case */
@@ -1375,7 +3367,7 @@ const infoMetadataAccordions = [];
const setIndeterminateAndChecked = function(list) {
return list.forEach(function(elem) {
- const layerStyles = elem._layers.map((lyr) => styles[lyr] !== undefined);
+ const layerStyles = elem._layers.map((lyr) => STYLES[lyr] !== undefined);
elem._input.indeterminate = elem._layers.length <= 1 ? false
: layerStyles.slice(1).some((v) => v !== layerStyles[0]);
if (elem._input.indeterminate) {
@@ -1399,15 +3391,15 @@ const infoMetadataAccordions = [];
const result = {};
const nodata = [ 0, 0, 0, .0];
const kskog_palette = [nodata, nodata, nodata, nodata, nodata];
- Object.keys(layers).forEach(function(lyr) {
+ Object.keys(LAYERS).forEach(function(lyr) {
const layerGroup = lyr.split('.', 1)[0];
if (result[layerGroup] === undefined) {
result[layerGroup] = false;
}
- result[layerGroup] ||= styles[lyr] !== undefined;
+ result[layerGroup] ||= STYLES[lyr] !== undefined;
if (layerGroup === 'kskog') {
- const i = parseInt(lyr.slice(layerGroup.length + 1))
- kskog_palette[i] = styles[lyr] ?? nodata;
+ const i = parseInt(lyr.slice(layerGroup.length + 1));
+ kskog_palette[i] = STYLES[lyr] ?? nodata;
}
});
const kskog = mapLayers['kskog'];
@@ -1421,7 +3413,7 @@ const infoMetadataAccordions = [];
mapLayers[lyr]?.setVisible(visible);
});
const btn = document.getElementById('map-legend-button');
- if (Object.values(styles).some((v) => v !== null)) {
+ if (Object.values(STYLES).some((v) => v !== null)) {
btn.classList.remove('disabled');
} else {
btn.classList.add('disabled');
@@ -1438,14 +3430,14 @@ const infoMetadataAccordions = [];
if (mapLayers[lyr.split('.', 1)[0]] == null) {
return; /* keep unexisting layers (eg WebGL layers on a system without WebGL support) unselectable */
}
- styles[lyr] = layers[lyr].style;
+ STYLES[lyr] = LAYERS[lyr].style;
if (!layersParams.includes(lyr)) {
layersParams.push(lyr);
}
});
} else {
layerList.forEach(function(lyr) {
- delete styles[lyr];
+ delete STYLES[lyr];
});
layersParams = layersParams.filter((lyr) => !layerList.includes(lyr));
}
@@ -1584,14 +3576,9 @@ const infoMetadataAccordions = [];
label.setAttribute('for', input.id);
label.innerHTML = 'Nedtonad bakgrund karta';
- input.checked = baseMapLayer === 'topowebb_nedtonad';
+ input.checked = BASEMAP.layer === 'topowebb_nedtonad';
input.onchange = function(event) {
- baseMapLayer = event.target.checked ? 'topowebb_nedtonad' : 'topowebb';
- baseMapSource.setUrl(`https://minkarta.lantmateriet.se/map/topowebbcache?LAYER=${encodeURIComponent(baseMapLayer)}`);
-
- const searchParams = new URLSearchParams(location.hash.substring(1));
- searchParams.set('basemap', baseMapLayer);
- location.hash = '#' + searchParams.toString();
+ BASEMAP.layer = event.target.checked ? 'topowebb_nedtonad' : 'topowebb';
};
})();
@@ -1643,7 +3630,1321 @@ const infoMetadataAccordions = [];
})();
})();
-/* age filter panel */
+/* popup and feature overlays */
+const disposePopover = (function() {
+ /* return an <a> tag with the given URL and optional text */
+ const reURL = new RegExp('^https?://', 'i');
+ const formatLink = function(url, text) {
+ if (url == null || typeof url !== 'string' || !reURL.test(url)) {
+ return url;
+ }
+ const a = document.createElement('a');
+ a.href = url;
+ a.target = '_blank';
+ if (text != null && text !== '') {
+ const t = document.createTextNode(text + ' ');
+ a.appendChild(t);
+ }
+ const i = document.createElement('i');
+ i.classList.add('bi', 'bi-box-arrow-up-right');
+ a.appendChild(i);
+ return a;
+ };
+
+ /* test a condition on the field maps */
+ const condField = function(cond, k) {
+ if (Array.isArray(cond)) {
+ return cond.includes(k);
+ }
+ if (cond instanceof RegExp) {
+ return cond.test(k);
+ }
+ return cond(k);
+ };
+ /* filter fields by condition */
+ const filterFields = function(k, fields) {
+ return fields.map(function(v) {
+ if (v.cond == null || condField(v.cond, k)) {
+ return v;
+ }
+ }).filter((f) => f != null);
+ };
+ /* filter fields using a pre-built map */
+ const mapFields = function(k, fieldMap, fields) {
+ if (fields === undefined) {
+ return fieldMap.map((v) => k[v]);
+ }
+ return fields.map(function(v) {
+ if (!Array.isArray(v)) {
+ return fieldMap[v];
+ } else if (condField(v[1], k)) {
+ return fieldMap[v[0]];
+ }
+ }).filter((f) => f !== undefined);
+ };
+ /* pre-build the field map so we don't need to duplicate objects accross layers */
+ const mkFieldMap = function(fieldMap) {
+ return Object.fromEntries(Object.entries(fieldMap).map(function([k, o]) {
+ if (typeof o === 'string') {
+ return [k, {key: k, desc: o}];
+ } else {
+ return [k, Object.assign(o, {key: k})];
+ }
+ }));
+ };
+
+ const layers = {
+ svk: {
+ ledningar: {
+ title: 'Kraftledning (befintlig)',
+ fields: [
+ { key: 'Placement', desc: 'Förläggning' },
+ { key: 'Voltage', desc: 'Spänning', unit: 'kV' },
+ { key: 'geom_length', desc: 'Ledlängd', fn: 'length' },
+ ],
+ },
+ transmissionsnatsprojekt: {
+ title: 'Transmissionsnätsprojekt',
+ fields: [
+ { key: 'Name', desc: 'Projektnamn' },
+ { key: 'Voltage', desc: 'Spänning', unit: 'kV' },
+ { key: 'Url', desc: 'Länk', fn: formatLink },
+ ],
+ },
+ },
+
+ misc: {
+ gigafactories: {
+ title: 'Stor industrisatsning',
+ fields: [
+ { key: 'Name', desc: 'Namn' },
+ { key: 'Url', desc: 'Länk', fn: formatLink },
+ ],
+ },
+ dammar: {
+ /* Documentation at
+ * https://www.smhi.se/polopoly_fs/1.34541!/dammprod%202013_3%2C%20beskrivning%2C%20SVAR2012_2.pdf */
+ title: 'Damm',
+ fields: [
+ { key: 'DNamn', desc: 'Dammenhetens namn' },
+ { key: 'Namn', desc: 'Dammanläggningens namn' },
+ { key: 'LST_OBJID', desc: 'Länsnr', classes: ['feature-objid'] },
+ { key: 'Status', desc: 'Status', fn: (v) => v === 1 ? 'Befintlig damm' : v === 2 ? 'Fd. damm' : '' },
+ //{ key: 'Regleringstyp', desc: 'Regleringstyp' },
+ { key: 'ByggAr', desc: 'Byggår' },
+ { key: 'DammHojd', desc: 'Dammhöjd', unit: 'm' },
+ { key: 'KronLangd', desc: 'Krönlängd', unit: 'm' },
+ { key: 'Fiskvag', desc: 'Fiskväg', fn: (v) =>
+ v === 1 ? 'Bassängtrappa' :
+ v === 2 ? 'Denilränna' :
+ v === 3 ? 'Slitsränna' :
+ v === 4 ? 'Omlöp' :
+ v === 5 ? 'Inlöp' :
+ v === 6 ? 'Ålledare' :
+ v === 7 ? 'Smoltränna' :
+ v === 8 ? 'Okänd typ' :
+ v === 9 ? 'Ingen' :
+ v === 10 ? 'Annan' :
+ null },
+ { key: 'HARO', desc: 'Huvudavrinningsområdesnummer', classes: ['feature-objid'] },
+ { key: 'Vattendistrikt', desc: 'Vattendistrikt', classes: ['feature-objid'] },
+ { key: 'Verksamhet', desc: 'Verksamhet', fn: (v) =>
+ v === 1 ? 'Kraftproduktion' :
+ v === 2 ? 'Industri' :
+ v === 3 ? 'Sjöfart' :
+ v === 4 ? 'Invallning' :
+ v === 5 ? 'Vattenförsörjning' :
+ v === 6 ? 'Spegeldamm' :
+ v === 7 ? 'Historisk' :
+ v === 8 ? 'Övrigt' :
+ null },
+ { key: 'DG', desc: 'Högsta dämningsgräns', unit: 'm' },
+ { key: 'SG', desc: 'Lägsta sänkningsgräns', unit: 'm' },
+ { key: 'MY', desc: 'Magasinsyta', unit: 'km²' },
+ { key: 'RV', desc: 'Reglerbar volym', unit: 'Mm³' },
+ { key: 'Kommentar', desc: 'Kommentar' },
+ ],
+ },
+ },
+ };
+
+ layers.mrr = {};
+ (function() {
+ const fields = [
+ { key: 'name', desc: 'Namn' },
+ { key: 'mineral', desc: 'Koncessionsmineral', cond: (i) => i < 6 },
+ { key: 'owners', desc: 'Ägare', cond: [0,2,4] },
+ { key: 'owners', desc: 'Sökande', cond: [1,3,5] },
+ { key: 'conc_name', desc: 'Tillhörande bearbetnings\u00ADkoncession(er)', cond: [6] },
+ { key: 'licenceid', desc: 'Tillståndsid', classes: ['feature-attr-mrr-license-id'], cond: [0,2,4,6] },
+ { key: 'geom_area', desc: 'Areal', fn: 'area' },
+ { key: 'validfrom', desc: 'Giltig från', cond: [0,2,4] },
+ { key: 'validto', desc: 'Giltig till', cond: [0,2,4] },
+ { key: 'diarynr', desc: 'Diarienummer', classes: ['feature-attr-dnr'] },
+ { key: 'appl_date', desc: 'Ansökningsdatum' },
+ { key: 'dec_date', desc: 'Beslutsdatum', cond: [0,2,4,6] },
+ ];
+ Object.entries({
+ ec: 'Bearbetningskoncession',
+ met: 'Undersökningstillstånd, metaller och industrimineral',
+ ogd: 'Undersökningstillstånd, olja, gas och diamant',
+ })
+ .flatMap(([k, title]) => [
+ /* don't use Object.entries() to guaranty ordering */
+ ['appr', 'beviljad'], /* even index */
+ ['appl', 'ansökt'], /* odd index */
+ ].map(([a,b]) => [a + '_' + k, title + ' \u2013 ' + b]))
+ .concat([['appr_dl', 'Markanvisning till koncession']]) /* index #6 */
+ .forEach(([k, title], idx) => layers.mrr[k] = { title, fields: filterFields(idx, fields) });
+ })();
+
+ layers.vbk = {};
+ (function() {
+ const fieldMap = mkFieldMap({
+ Projektnamn: 'Projektnamn',
+ OmrID: { desc: 'Områdes-ID', classes: ['feature-objid'] },
+ AntalVerk: 'Aktuella verk',
+ AntalEjXY: 'Antal ej koordinatsatta verk',
+ Projektstatus: 'Projektstatus',
+ Diarienummer: 'Diarienummer',
+ geom_area: { desc: 'Areal', fn: 'area' },
+ Calprod: { desc: 'Beräknad årsproduktion', unit: 'GWh' },
+ PlaneradByggstart: 'Planerad byggstart',
+ PlaneratDrift: 'Planerat drifttagande',
+ AndringsansokanPagar: 'Ändringsansökan pågår',
+ UnderByggnation: 'Under byggnation',
+ Organisationsnamn: 'Verksamhetsutövare',
+ Organisationsnummer: { desc: 'Organisationsnummer', classes: ['feature-orgnr'] },
+ SamradsunderlagInlamnat: 'Samrådsunderlag inlämnat',
+ AnsokanInlamnat: 'Tillståndsansökan inlämnad',
+ AnsokanAterkallad: 'Tillståndsansökan återkallad',
+ AnsokanBeviljad: 'Tillståndsansökan beviljad',
+ AnsokanAvslagen: 'Tillståndsansökan avslagen',
+ AnsokanOverklagad: 'Överklagad',
+ Natura2000_Ansokan: 'Natura2000 ansökan',
+ Natura2000_Beslutdatum: 'Natura2000 beslutsdatum',
+ Uppfort: 'Parken uppförd',
+ PlaneratAntalVerkMin: 'Planerat antal verk (min)',
+ PlaneratAntalVerkMax: 'Planerat antal verk (max)',
+ PlaneradHojdMin: { desc: 'Panerad totalhöjd (min)', unit: 'm' },
+ PlaneradHojdMax: { desc: 'Panerad totalhöjd (max)', unit: 'm' },
+ PlaneradProduktionMin: { desc: 'Planerad årsproduktion (min)', unit: 'GWh' },
+ PlaneradProduktionMax: { desc: 'Planerad årsproduktion (max)', unit: 'GWh' },
+ BeviljatAntalVerk: 'Beviljat antal verk',
+ UppfortAntalVerk: 'Uppfört antal verk',
+ BeviljadMaxhojd: { desc: 'Beviljad maxhöjd', unit: 'm' },
+ InstalleradEffekt: { desc: 'Installerad effekt', unit: 'MW' },
+ ElNamn: 'Elområde',
+ SenasteUppdaterat: 'Senast uppdaterat',
+ });
+
+ Object.entries({
+ current: null,
+ notcurrent: ' \u2013 ej aktuell',
+ })
+ .forEach(([k, title]) => layers.vbk['area_' + k] = {
+ title: 'Landbaserad projekteringsområde för vindkraft' + (title ?? ''),
+ fields: mapFields(k, fieldMap, [
+ 'Projektnamn',
+ 'OmrID',
+ 'AntalVerk',
+ 'AntalEjXY',
+ 'geom_area',
+ 'Calprod',
+ 'PlaneradByggstart',
+ 'PlaneratDrift',
+ 'AndringsansokanPagar',
+ ['UnderByggnation', ['current']],
+ 'Organisationsnamn',
+ 'Organisationsnummer',
+ 'ElNamn',
+ 'SenasteUppdaterat',
+ ]),
+ });
+
+ [
+ ['completed', /* 0 */ 'uppförd'],
+ ['approved', /* 1 */ 'tillståndsansökan beviljad'],
+ ['amended', /* 2 */ 'ändringsansökan'],
+ ['rejected', /* 3 */ 'tillståndsansökan avslagen'],
+ ['appealed', /* 4 */ 'överklagad'],
+ ['applied', /* 5 */ 'tillståndsansökan inlämnad'],
+ ['consultation', /* 6 */ 'samråd inför tillståndsansökan'],
+ ['investigation', /* 7 */ 'inledande undersökningar'],
+ ['revoked', /* 8 */ 'inte aktuell eller återkallad'],
+ ]
+ .forEach(([k, title], idx) => layers.vbk['offshore_' + k] = {
+ title: 'Havsbaserad vindkraft \u2013 ' + title,
+ fields: mapFields(idx, fieldMap, [
+ 'Projektnamn',
+ 'OmrID',
+ 'Organisationsnamn',
+ 'Organisationsnummer',
+ 'Projektstatus',
+ 'Diarienummer',
+ ['AndringsansokanPagar', [1,2,4]],
+ 'geom_area',
+ ['SamradsunderlagInlamnat', (i) => i <= 6 || i === 8],
+ ['AnsokanInlamnat', (i) => i <= 5 || i === 8],
+ ['AnsokanAterkallad', [8]],
+ ['AnsokanBeviljad', [0,1,4,8]],
+ ['AnsokanAvslagen', [3,8]],
+ ['AnsokanOverklagad', [0,1,3,4,8]],
+ ['Natura2000_Ansokan', (i) => i !== 2],
+ ['Natura2000_Beslutdatum', (i) => i !== 2],
+ ['UnderByggnation', [1]],
+ ['PlaneratAntalVerkMin', (i) => i > 0],
+ ['PlaneratAntalVerkMax', (i) => i > 0],
+ ['PlaneradHojdMin', (i) => i > 0],
+ ['PlaneradHojdMax', (i) => i > 0],
+ ['PlaneradProduktionMin', (i) => i > 0],
+ ['PlaneradProduktionMax', (i) => i > 0],
+ ['PlaneradByggstart', (i) => i > 0],
+ ['Uppfort', [0,8]],
+ ['PlaneratDrift', (i) => i > 0],
+ ['BeviljatAntalVerk', [0,1,4,8]],
+ ['UppfortAntalVerk', [0,8]],
+ ['BeviljadMaxhojd', [0,1,4,8]],
+ ['InstalleradEffekt', [0]],
+ ['Calprod', [0]],
+ 'ElNamn',
+ 'SenasteUppdaterat',
+ ]),
+ });
+
+ Object.assign(fieldMap, mkFieldMap({
+ VerkID: { desc: 'Verk-ID', classes: ['feature-objid'] },
+ Status: 'Status',
+ Handlingstyp: 'Handlingstyp',
+ MB_Tillstand: 'Miljöbalken tillstånd tidsbegränsning',
+ Uppfort: 'Uppförandedatum',/* override previous def */
+ Totalhojd: { desc: 'Totalhöjd', unit: 'm' },
+ Navhojd: { desc: 'Navhöjd', unit: 'm' },
+ Rotordiameter: { desc: 'Rotordiameter', unit: 'm' },
+ Maxeffekt: { desc: 'Maxeffekt', unit: 'MW' },
+ Fabrikat: 'Fabrikat',
+ Modell: 'Modell',
+ Placering: 'Placering',
+ }));
+
+ [
+ ['completed', /* 0 */ 'uppfört'],
+ ['approved', /* 1 */ 'beviljat'],
+ ['rejected', /* 2 */ 'avslagit/nekat'],
+ ['processed', /* 3 */ 'handlagt'],
+ ['dismounted', /* 4 */ 'nedmonterat'],
+ ['appealed', /* 5 */ 'överklagat'],
+ ['revoked', /* 6 */ 'inte aktuell eller återkallad'],
+ ]
+ .forEach(([k, title], idx) => layers.vbk['station_' + k] = {
+ title: 'Landbaserad vindkraftverk \u2013 ' + title,
+ fields: mapFields(idx, fieldMap, [
+ 'VerkID',
+ 'OmrID',
+ 'Projektnamn',
+ 'Status',
+ 'Handlingstyp',
+ ['Uppfort', [0,4,6]],
+ 'MB_Tillstand',
+ 'Totalhojd',
+ 'Navhojd',
+ 'Rotordiameter',
+ 'Maxeffekt',
+ 'Calprod',
+ 'Fabrikat',
+ 'Modell',
+ 'Organisationsnamn',
+ 'Organisationsnummer',
+ 'Placering',
+ 'ElNamn',
+ 'SenasteUppdaterat',
+ ]),
+ });
+ })();
+
+ layers.avverk = {};
+ (function() {
+ const zeroIsNull = (v) => v > 0 ? v : null;
+ const fieldMap = mkFieldMap({
+ /* Documentation at
+ * https://www.skogsstyrelsen.se/globalassets/sjalvservice/karttjanster/geodatatjanster/produktbeskrivningar/utforda-avverkningar---produktbeskrivning.pdf
+ * and
+ * https://www.skogsstyrelsen.se/globalassets/sjalvservice/karttjanster/geodatatjanster/produktbeskrivningar/yttre-granser-for-avverkningsanmalda-omraden---produktbeskrivning.pdf
+ */
+ Beteckn: { desc: 'Ärendebeteckning', classes: ['feature-objid'] },
+ ArendeAr: 'Registeringsår',
+ Inkomdatum: 'Inkom datum',
+ Skogstyp: 'Skogstyp',
+ Avvdatum: 'Datum för avverkning',
+ KallaDatum: 'Ursprung för datum för avverkning',
+ AnmaldHa: { desc: 'Areal anmält', unit: 'ha' },
+ NatforHa: { desc: 'Areal naturlig föryngring', unit: 'ha', fn: zeroIsNull },
+ SkogsodlHa: { desc: 'Areal plantering', unit: 'ha', fn: zeroIsNull },
+ AvvSasong: 'Avverkningssäsong',
+ Avverktyp: 'Avverkningstyp',
+ ArendeStatus: 'Ärendestatus',
+ AvvHa: { desc: 'Avverkad areal', unit: 'ha' },
+ geom_area: { desc: 'Areal för ytan', fn: 'area' },
+ });
+
+ layers.avverk.utford = {
+ title: 'Utförd avverkning',
+ fields: mapFields(fieldMap, [
+ 'Beteckn',
+ 'ArendeAr',
+ 'Skogstyp',
+ 'AnmaldHa',
+ 'NatforHa',
+ 'Avverktyp',
+ 'Avvdatum',
+ 'KallaDatum',
+ 'geom_area',
+ ]),
+ };
+
+ layers.avverk.anmald = {
+ title: 'Avverkningsanmälansområde',
+ fields: mapFields(fieldMap, [
+ 'Beteckn',
+ 'Inkomdatum',
+ 'ArendeAr',
+ 'AnmaldHa',
+ 'NatforHa',
+ 'SkogsodlHa',
+ 'AvvSasong',
+ 'ArendeStatus',
+ 'AvvHa',
+ ]),
+ };
+ })();
+
+ layers.skydd = {};
+ (function() {
+ const fieldMap = mkFieldMap({
+ NVRID: { desc: 'NVR-ID', classes: ['feature-objid'] },
+ FORSKRNAMN: 'Föreskriftsområde',
+ OBJEKTNAMN: 'Namn',
+ NAMN: 'Namn',
+ BESLSTAT: 'Beslutsstatus',
+ FORESKRTYP: 'Föreskriftstyp',
+ FORESKRIFT: 'Föreskriftssubtyp',
+ FRANDATUM: 'Från datum',
+ TILLDATUM: 'Till datum',
+ BESKRIVN: 'Beskrivning',
+ geom_area: { desc: 'Areal', fn: 'area' },
+
+ SKYDDSTYP: 'Skyddstyp',
+ BESLSTATUS: 'Beslutsstatus',
+ URSBESLDAT: 'Beslutsdatum (bildande)',
+ URSGALLDAT: 'Ursprungligt gällandedatum',
+ SENGALLDAT: 'Senaste gällandedatum',
+ FORVALTARE: 'Förvaltare',
+ IUCNKAT: 'IUCN-kategori',
+ DIARIENR: { desc: 'Diarienummer', classes: ['feature-attr-dnr'] },
+ LAGRUM: 'Lagrum',
+ BESLMYND: 'Beslutsmyndighet',
+ LAND_HA: { desc: 'Areal land', unit: 'ha' },
+ VATTEN_HA: { desc: 'Areal vatten', unit: 'ha' },
+ SKOG_HA: { desc: 'Skogsmarksareal', unit: 'ha' },
+
+ IKRAFTDATF: 'Ikraftträdandedatum föreskrifter',
+ TILLSYNSMH: 'Tillsynsmyndighet',
+ PROVNMHTIL: 'Prövningsmyndighet tillstånd',
+ PROVNMHDIS: 'Prövningsmyndighet dispens',
+
+ NAME: 'Namn',
+ RAMSAR_ID: { desc: 'Ramsar-ID', classes: ['feature-objid'] },
+ LEGAL_ACT: 'Rättsakt',
+ URSPR_BESL: 'Ursprungligt beslutsdatum',
+ SEN_BESLUT: 'Senaste beslutsdatum',
+ LINK: { desc: 'Länk', fn: formatLink },
+ });
+
+ layers.skydd.tilltradesforbud = {
+ title: 'Tillträdesförbud',
+ fields: mapFields(fieldMap, [
+ 'NVRID',
+ 'FORSKRNAMN',
+ 'OBJEKTNAMN',
+ 'BESLSTAT',
+ 'FORESKRTYP',
+ 'FORESKRIFT',
+ 'FRANDATUM',
+ 'TILLDATUM',
+ 'BESKRIVN',
+ 'geom_area',
+ ]),
+ };
+
+ /* Nationella skyddsformer från Naturvårdsregistret */
+ const isSurface = (k) => !/_punkt$/.test(k);
+ Object.entries({
+ nationalpark: 'Nationalpark',
+ naturreservat: 'Naturreservat',
+ naturreservat_kommunalt: 'Kommunalt naturreservat',
+ naturvardsomrade: 'Naturvårdsområde',
+ djur_och_vaxtskyddsomrade: 'Djur- och växtskyddsområde',
+ kulturreservat: 'Kulturreservat',
+ vattenskyddsomrade: 'Vattenskyddsområden',
+ landskapsbildsskyddsomrade: 'Landskapsbildsskyddsområde',
+ ovrigt_biotopskyddsomrade: 'Biotopskydd utanför skogsmark',
+ naturminne_yta: 'Naturminne (yta)',
+ naturminne_punkt: 'Naturminne (punkt)',
+ interimistiskt_forbud: 'Interimistiskt förbud',
+ })
+ .forEach(([k, title]) => layers.skydd[k] = {
+ title: title,
+ fields: mapFields(k, fieldMap, [
+ 'NVRID',
+ 'NAMN',
+ 'SKYDDSTYP',
+ 'BESLSTATUS',
+ 'URSBESLDAT',
+ ['URSGALLDAT', (k) => k !== 'vattenskyddsomrade'],
+ ['SENGALLDAT', (k) => k !== 'vattenskyddsomrade'],
+ ['FORVALTARE', (k) => k !== 'vattenskyddsomrade'],
+ ['IKRAFTDATF', (k) => k === 'vattenskyddsomrade'],
+ 'IUCNKAT',
+ 'DIARIENR',
+ 'LAGRUM',
+ 'BESLMYND',
+ ['TILLSYNSMH', (k) => k === 'vattenskyddsomrade'],
+ ['PROVNMHTIL', (k) => k === 'vattenskyddsomrade'],
+ ['PROVNMHDIS', (k) => k === 'vattenskyddsomrade'],
+ ['geom_area', isSurface],
+ ['LAND_HA', isSurface],
+ ['VATTEN_HA', isSurface],
+ ['SKOG_HA', isSurface],
+ ]),
+ });
+
+ /* Natura 2000-områden */
+ (function() {
+ const fields = [
+ { key: 'SITE_CODE', desc: 'Områdeskod', classes: ['feature-objid'] },
+ { key: 'NAMN', desc: 'Namn' },
+ { key: 'OMRADESTYP', desc: 'Områdestyp' },
+ { key: 'UPPLAMNARE', desc: 'Uppgiftslämnare' },
+ { key: 'SPA_DATUM', desc: 'SPA-datum' },
+ { key: 'SCI_FORSL', desc: 'SCI-förslagsdatum' },
+ { key: 'SCI_DATUM', desc: 'SCI-datum' },
+ { key: 'SAC_DATUM', desc: 'SAC-datum' },
+ fieldMap.geom_area,
+ { key: 'KVALITET', desc: 'Kvalitet' },
+ { key: 'KARAKTAR', desc: 'Kännetecken för området' },
+ { key: 'ARTER', desc: 'Arter' },
+ { key: 'NATURTYPER', desc: 'Naturtyper' },
+ { key: 'BEVPLAN', desc: 'Bevarandeplan', fn: formatLink },
+ ];
+ Object.entries({
+ fageldirektivet: 'Fågeldirektivet (SPA)',
+ habitatdirektivet: 'Art- och habitatdirektivet (SCI)',
+ })
+ .forEach(([k, title]) => layers.skydd[k] = { title, fields });
+ })();
+
+ /* Områden med internationell status */
+ layers.skydd.helcom = {
+ title: 'Marina skyddade områden (Helcom MPA)',
+ fields: mapFields(fieldMap, [ 'NAME', 'geom_area' ]),
+ };
+
+ layers.skydd.ramsar = {
+ title: 'Ramsar-områden (Våtmarkskonventionen)',
+ fields: mapFields(fieldMap, [
+ 'RAMSAR_ID',
+ 'SKYDDSTYP',
+ 'NAMN',
+ 'geom_area',
+ 'LAND_HA',
+ 'VATTEN_HA',
+ 'SKOG_HA',
+ 'URSPR_BESL',
+ 'SEN_BESLUT',
+ 'LEGAL_ACT',
+ 'LINK',
+ ]),
+ };
+
+ layers.skydd.ospar = {
+ title: 'Marina skyddade områden (Ospar MPA)',
+ fields: [
+ { key: 'ORIGIN', desc: 'Ursprung' },
+ { key: 'NAMN_N2000', desc: 'N2000-namn' },
+ { key: 'MPA_ID', desc: 'MPA-ID', classes: ['feature-objid'] },
+ { key: 'MPA_NAMN', desc: 'MPA-namn' },
+ { key: 'N2000_SITE', desc: 'N2000-ID', classes: ['feature-objid'] },
+ fieldMap.geom_area,
+ ],
+ };
+
+ layers.skydd.varldsarv = {
+ title: 'Världsarv med mycket höga naturvärden (Unesco)',
+ fields: mapFields(fieldMap, [ 'NAMN', 'geom_area' ]),
+ };
+
+ layers.skydd.naturvardsavtal = {
+ title: 'Naturvårdsavtal (Naturvårdsverket, Länsstyrelsen)',
+ fields: [
+ { key: 'ID', desc: 'ID', classes: ['feature-objid'] },
+ { key: 'OBJNAMN', desc: 'Namn' },
+ { key: 'FASTBET', desc: 'Fastighet', classes: ['feature-objid'] },
+ { key: 'DATSTART', desc: 'Giltig från' },
+ { key: 'DATSLUT', desc: 'Giltig till' },
+ { key: 'DIARIENRNV', desc: 'Diarienummer', classes: ['feature-attr-dnr'] },
+ { key: 'STATUS', desc: 'Satus' },
+ fieldMap.geom_area,
+ ],
+ };
+ })();
+
+ (function() {
+ const fieldMap = mkFieldMap({
+ Beteckn: { desc: 'Ärendebeteckning', classes: ['feature-objid'] },
+ Biotyp: { desc: 'Biotopkategori' },
+ Naturtyp: { desc: 'Skogstyp' },
+ ArendeAr: { desc: 'Registeringsår' },
+ geom_area: { desc: 'Areal', fn: 'area' },
+ AreaProd: { desc: 'Skogsmarksareal', unit: 'ha' },
+ Datbeslut: { desc: 'Beslutsdatum' },
+ Url: { desc: 'Länk', fn: (v) => formatLink(v, 'Skogens Pärlor') },
+ NvaTyp: 'Biotopkategori',
+ DatAvtal: 'Avtalsdatum',
+ Undertyp: 'Undertyp',
+ AvtalatDatum: 'Avtalat datum',
+ Objnamn: 'Objektnamn',
+ Datinv: 'Datum för fältinventering',
+ });
+
+ layers.skydd.skogligt_biotopskyddsomrade = {
+ title: 'Biotopskydd i skogsmark',
+ fields: mapFields(fieldMap, [
+ 'Beteckn',
+ 'Biotyp',
+ 'Naturtyp',
+ 'ArendeAr',
+ 'geom_area',
+ 'AreaProd',
+ 'Datbeslut',
+ 'Url',
+ ]),
+ };
+ layers.skydd.naturvardsavtal_skogsstyrelsen = {
+ title: 'Naturvårdsavtal (Skogsstyrelsen)',
+ fields: mapFields(fieldMap, [
+ 'Beteckn',
+ 'ArendeAr',
+ 'NvaTyp',
+ 'Naturtyp',
+ 'DatAvtal',
+ 'geom_area',
+ 'AreaProd',
+ 'Url',
+ 'Undertyp',
+ ]),
+ };
+
+ layers.skydd.atervatningsavtal = {
+ title: 'Återvätningsavtal',
+ fields: mapFields(fieldMap, [
+ 'Beteckn',
+ 'ArendeAr',
+ 'AvtalatDatum',
+ 'geom_area',
+ 'Url',
+ ]),
+ };
+
+ layers.nv = {};
+ Object.assign(fieldMap, mkFieldMap(Object.fromEntries(
+ [1,2,3].map((i) => [`Biotop${i}`, `Biotoptyp #${i}`]).concat(
+ [1,2,3,4,5,6,7,8].map((i) => [`Beskrivn${i}`, `Nyckelord #${i} som beskriver objektet`])
+ ))));
+ layers.nv.naturvarde_sks = {
+ title: 'Objekt med naturvärden (Skogsstyrelsen)',
+ fields: mapFields(fieldMap, [
+ 'Beteckn',
+ 'Objnamn',
+ 'Datinv',
+ 'Biotop1', 'Biotop2', 'Biotop3',
+ 'Beskrivn1', 'Beskrivn2', 'Beskrivn3',
+ 'geom_area',
+ 'Url',
+ ]),
+ };
+ layers.nv.nyckelbiotop = {
+ title: 'Nyckelbiotop (Skogsstyrelsen)',
+ fields: mapFields(fieldMap, [
+ 'Beteckn',
+ 'Objnamn',
+ 'Datinv',
+ 'Biotop1', 'Biotop2', 'Biotop3',
+ 'Beskrivn1', 'Beskrivn2', 'Beskrivn3', 'Beskrivn4', 'Beskrivn5', 'Beskrivn6', 'Beskrivn7', 'Beskrivn8',
+ 'geom_area',
+ 'Url',
+ ]),
+ };
+ layers.nv.nyckelbiotop_storskogsbruk = {
+ title: 'Nyckelbiotop (storskogsbruket)',
+ fields: [
+ { key: 'Org', desc: 'Uppgifter lämnade av' },
+ { key: 'InkomDatum', desc: 'Inkom datum' },
+ fieldMap.geom_area,
+ fieldMap.Url,
+ ],
+ };
+
+ layers.nv.sumpskog = {
+ title: 'Sumpskog',
+ fields: [
+ { key: 'Namn', desc: 'Objektnamn' },
+ { key: 'Tradtext', desc: 'Skogstyp' },
+ { key: 'Hydrtext', desc: 'Hydrologisk typ' },
+ { key: 'Delklass', desc: 'Klass på delobjektet' },
+ { key: 'Klassu', desc: 'Klass på objektet' },
+ { key: 'Lovandel', desc: 'Andel löv' },
+ { key: 'Andelva', desc: 'Andel öppet vatten' },
+ { key: 'Krontakn', desc: 'Krontäckning' },
+ { key: 'Huggklas', desc: 'Huggningsklass' },
+ { key: 'Ingrepp', desc: 'Ingrepp på delobjekt (max 4)' },
+ { key: 'Ingrpavv', desc: 'Grad av påverkan på delobjekt (max 4)' },
+ { key: 'Objnyck', desc: 'Nyckelord på objektnivå' },
+ { key: 'Delnyck', desc: 'Nyckelord på delobjektsnivå' },
+ { key: 'Flygar', desc: 'Flygbildsår' },
+ { key: 'Faltdat', desc: 'Datum för fältbesök' },
+ { key: 'Invtekn', desc: 'Inventeringsteknik' },
+ { key: 'Invdat', desc: 'Inventeringdatum' },
+ { key: 'Ansvmynd', desc: 'Ansvarig myndighet' },
+ fieldMap.geom_area,
+ fieldMap.Url,
+ ],
+ };
+ })();
+
+ layers.nv.pagaende_naturreservatsbildning = {
+ title: 'Pågående naturreservatsbildning',
+ fields: [
+ { key: 'NAMN', desc: 'Objektnamn' },
+ /* XXX unclear what "GRANSJUST" means, just a guess */
+ { key: 'GRANSJUST', desc: 'Senast justerat' },
+ { key: 'geom_area', desc: 'Areal', fn: 'area' },
+ ],
+ };
+
+ layers.nv.snus = {
+ title: 'Skyddsvärd statlig skog',
+ fields: [
+ { key: 'NAMN', desc: 'Objektnamn' },
+ { key: 'AR', desc: 'År' },
+ { key: 'NATURGEOGR', desc: 'Naturgeografisk region', classes: ['feature-objid'] },
+ { key: 'OBJEKTKATE', desc: 'Objektskategori', classes: ['feature-objid'] },
+ { key: 'MARKAGARE', desc: 'Markägare' },
+ { key: 'VARDEKARNA', desc: 'Areal värdekärna', unit: 'ha' },
+ { key: 'UTV_MARK', desc: 'Areal utvecklingsmark', unit: 'ha' },
+ { key: 'TOTAL_AREA', desc: 'Totalareal', unit: 'ha' },
+ { key: 'LAND', desc: 'Areal land', unit: 'ha' },
+ { key: 'VATTEN', desc: 'Areal vatten', unit: 'ha' },
+ { key: 'PROD_SKOG', desc: 'Areal produktiv skogsmark', unit: 'ha' },
+ { key: 'SKOG_O_FJG', desc: 'Areal produktiv skogsmark ovanför fjällnära gräns', unit: 'ha' },
+ { key: 'SKOG_N_FJG', desc: 'Areal produktiv skogsmark nedanför fjällnära gräns', unit: 'ha' },
+ { key: 'SKYDDSZON', desc: 'Areal skyddszon', unit: 'ha' },
+ { key: 'ARRO_MARK', desc: 'Areal arronderingsmark', unit: 'ha' },
+ { key: 'KRITERIER', desc: 'Kriterier för urval' },
+ { key: 'BESKRIVN', desc: 'Beskrivning av området' },
+ { key: 'LST_BEDOMN', desc: 'Länsstyrelsens bedömning' },
+ { key: 'KALLOR', desc: 'Källor' },
+ ],
+ };
+
+ layers.ri = {};
+ (function() {
+ const fieldMap = mkFieldMap({
+ NAMN: 'Namn',
+ SKYDD: 'Skydd',
+ AMNESOMRAD: 'Ämnesområde',
+ AMNESOMR: 'Ämnesområde',
+ OMRADESNR: { desc: 'Områdesnummer', classes: ['feature-objid'] },
+ BESKRIVNIN: { desc: 'Beskrivning', fn: formatLink },
+ LANK_VARDE: { desc: 'Länk värdebeskrivning', fn: formatLink },
+ LAGRUM: 'Lagrum',
+ BESLUTSDAT: 'Beslutsdatum',
+ BESLDATUM: 'Beslutsdatum',
+ ARENDENR: { desc: 'Ärendenummer', classes: ['feature-attr-dnr'] },
+ LANK_BESLU: { desc: 'Länk beslut', fn: formatLink },
+ AKTIVITET: 'Aktivitet',
+ NATURTYP: 'Naturtyp',
+ ORGINALID: { desc: 'Original-ID', classes: ['feature-objid'] },
+ RIKSID: { desc: 'Riks-ID', classes: ['feature-objid'] },
+ geom_area: { desc: 'Areal', fn: 'area' },
+ AREA_LAND_: { desc: 'Areal land', unit: 'ha' },
+ AREA_VATTE: { desc: 'Areal vatten', unit: 'ha' },
+ });
+ layers.ri.naturvard = {
+ title: 'Riksintresse naturvård',
+ fields: mapFields(fieldMap, [
+ 'NAMN',
+ 'SKYDD',
+ 'AMNESOMRAD',
+ 'BESKRIVNIN',
+ 'LAGRUM',
+ 'BESLUTSDAT',
+ 'ORGINALID',
+ 'RIKSID',
+ 'geom_area',
+ ]),
+ };
+ layers.ri.friluftsliv = {
+ title: 'Riksintresse friluftsliv',
+ fields: mapFields(fieldMap, [
+ 'NAMN',
+ 'SKYDD',
+ 'AMNESOMR',
+ 'OMRADESNR',
+ 'LANK_VARDE',
+ 'LAGRUM',
+ 'BESLDATUM',
+ 'ARENDENR',
+ 'LANK_BESLU',
+ 'AKTIVITET',
+ 'NATURTYP',
+ 'geom_area',
+ 'AREA_LAND_',
+ 'AREA_VATTE',
+ ]),
+ };
+
+ Object.assign(fieldMap, mkFieldMap({
+ METODBESKR: 'Metodbeskrivning',
+ TILLKDATUM: 'Tillkomstdatum',
+ REVDATUM: 'Revisionsdatum',
+ ANM: 'Anmärkning',
+ OBJEKTLANK: { desc: 'Objektlänk', fn: formatLink },
+ REFERENS: 'Referens',
+ OBJTYP: 'Objekttyp',
+ ORIGINALID: fieldMap.ORGINALID,
+ DIG_SKALA: { desc: 'Digitaliseringsskala', fn: (v) => v > 0 ? v : null },
+ }));
+ [
+ ['rorligt_friluftsliv', /* 0 */ 'rörligt friluftsliv (MB 4 kap 1§ och 2§)'],
+ ['obruten_kust', /* 1 */ 'obruten kust (MB 4 kap 3§)'],
+ ['obrutet_fjall', /* 2 */ 'obrutet fjäll (MB 4 kap 5§)'],
+ ['skyddade_vattendrag', /* 3 */ 'skyddade vattendrag (MB 4 kap 6§)'],
+ ]
+ .forEach(([k, title], idx) => layers.ri[k] = {
+ title: 'Riksintresse ' + title,
+ fields: mapFields(idx, fieldMap, [
+ 'NAMN',
+ 'BESKRIVNIN',
+ 'METODBESKR',
+ 'TILLKDATUM',
+ 'REVDATUM',
+ ['OBJTYP', [1]],
+ ['ANM', [0,1,3]],
+ ['DIG_SKALA', [3]],
+ 'OBJEKTLANK',
+ 'geom_area',
+ 'ORIGINALID',
+ 'REFERENS',
+ ]),
+ });
+ })();
+
+ layers.ren = {
+ betesomrade: {
+ title: 'Samebyarnas betesområde',
+ fields: [
+ { key: 'NAMN', desc: 'Sameby' },
+ { key: 'SAMEBY_TYP', desc: 'Samebys typ' },
+ { key: 'SIGNATUR', desc: 'Signatur' },
+ { key: 'AKTUALITET', desc: 'Aktualitet' },
+ { key: 'geom_area', desc: 'Areal', fn: 'area' },
+ ],
+ },
+ flyttled: {
+ title: 'Samebyarnas markanvändningsredovisning \u2013 flyttled',
+ fields: [
+ { key: 'LED_ID', desc: 'Led-ID', classes: ['feature-objid'], fn: (v) => v > 0 ? v : null },
+ { key: 'SAMEBY1', desc: 'Sameby #1' },
+ { key: 'SAMEBY2', desc: 'Sameby #2' },
+ { key: 'SAMEBY3', desc: 'Sameby #3' },
+ { key: 'BESKRIVNIN', desc: 'Beskrivning' },
+ { key: 'ARSTID', desc: 'Årstid' },
+ { key: 'RIKSINTR', desc: 'Riksintresse' },
+ { key: 'FAST_LED', desc: 'Fast led' },
+ { key: 'AKTUALITET', desc: 'Aktualitet' },
+ { key: 'SIGNATUR', desc: 'Signatur' },
+ { key: 'geom_length', desc: 'Ledlängd', fn: 'length' },
+ ],
+ },
+ riks_ren: {
+ title: 'Riksintresse rennäring',
+ fields: [
+ { key: 'LAGRUM', desc: 'Lagrum' },
+ { key: 'AKTUALITET', desc: 'Aktualitet' },
+ { key: 'SIGNATUR', desc: 'Signatur' },
+ { key: 'geom_area', desc: 'Areal', fn: 'area' },
+ ],
+ },
+ omr_riks: {
+ title: '(Kärn)områden av riksintresse rennäring',
+ fields: [
+ { key: 'OMR_NR', desc: 'Områdes-ID', classes: ['feature-objid'] },
+ { key: 'LANK', desc: 'Länk' },
+ { key: 'ARET_RUNT', desc: 'Årets runt' },
+ { key: 'SAMEBY', desc: 'Sameby' },
+ { key: 'ANSVARIG', desc: 'Ansvarig' },
+ { key: 'AKTUALITET', desc: 'Aktualitet' },
+ { key: 'SIGNATUR', desc: 'Signatur' },
+ { key: 'geom_area', desc: 'Areal', fn: 'area' },
+ ],
+ },
+ };
+
+ /* format value to HTML */
+ const formatValue = function(value, options) {
+ let unit = options?.unit;
+ if (options?.fn == null) {
+ /* no-op */
+ } else if (typeof options.fn === 'function') {
+ value = options.fn(value);
+ } else if (options.fn === 'length' && typeof value === 'number' && unit == null) {
+ if (value < 1000) {
+ unit = 'm';
+ } else {
+ value /= 1000;
+ value = Math.round(value*100) / 100;
+ unit = 'km';
+ }
+ } else if (options.fn === 'area' && typeof value === 'number' && unit == null) {
+ if (value < 10000) {
+ unit = 'm²';
+ } else if (value < 10000 * 10000) {
+ value /= 10000;
+ unit = 'ha';
+ } else {
+ value /= 1000000;
+ unit = 'km²';
+ }
+ value = Math.round(value*100) / 100;
+ }
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof HTMLElement) {
+ return value;
+ }
+ switch (typeof value) {
+ case 'boolean':
+ return document.createTextNode(value ? 'Ja' : 'Nej');
+ case 'string':
+ return document.createTextNode(value);
+ case 'number':
+ if (unit != null) {
+ return document.createTextNode(value.toLocaleString('sv-SE') + '\u202F' + unit);
+ }
+ return document.createTextNode(value.toString());
+ default:
+ return null;
+ }
+ };
+
+ /* turn the properties into a fine <table> */
+ const formatFeaturePropertiesToHTML = function(properties) {
+ const table = document.createElement('table');
+ table.classList.add('table', 'table-sm', 'table-borderless', 'table-hover');
+
+ const tbody = document.createElement('tbody');
+ table.appendChild(tbody);
+
+ const def = layers[properties.layer_group][properties.layer];
+ def.fields.forEach(function(field) {
+ const tr = document.createElement('tr');
+ tbody.appendChild(tr);
+
+ const th = document.createElement('th');
+ th.setAttribute('scope', 'row');
+ tr.appendChild(th);
+ const textDesc = document.createTextNode(field.desc);
+ th.appendChild(textDesc);
+
+ const td = document.createElement('td');
+ tr.appendChild(td);
+ const v = formatValue(properties[field.key], field);
+ if (v != null) {
+ td.appendChild(v);
+ }
+ field.classes?.forEach?.((c) => td.classList.add(c));
+ });
+
+ const content = document.createElement('div');
+ if (def.title != null) {
+ const h = document.createElement('h6');
+ content.appendChild(h);
+ const textNode = document.createTextNode(def.title);
+ h.appendChild(textNode);
+ }
+
+ content.appendChild(table);
+ return content;
+ };
+
+ /* initialize popup overlay */
+ const popupOverlay = new Overlay({
+ stopEvent: true,
+ element: document.getElementById('popup'),
+ });
+ MAP.addOverlay(popupOverlay);
+
+ let featureOverlayLayer = null;
+ let overlayAttributes = [],
+ overlayAttrIdx = 0,
+ mapSources = {};
+ /* clear the highlighted feature list and make the overlay layer invisible */
+ const disposeFeatureOverlay = function() {
+ if (featureOverlayLayer?.getVisible?.()) {
+ featureOverlayLayer.setVisible(false);
+ featureOverlayLayer.changed();
+ }
+ /* clear the overlay list */
+ overlayAttributes = [];
+ overlayAttrIdx = 0;
+ mapSources = {};
+ }
+
+ let popover = null;
+ /* clear overlay layer and dispose popover */
+ const disposePopover = function() {
+ disposeFeatureOverlay();
+ if (popover?.tip != null) {
+ popover.dispose();
+ }
+ };
+
+ /* initialize popover */
+ featureOverlayLayer = new VectorTileLayer({
+ zIndex: 65535,
+ declutter: false,
+ visible: false,
+ renderMode: 'vector',
+ style: null,
+ map: MAP,
+ });
+
+ const header = document.createElement('div');
+ header.classList.add('d-flex');
+
+ const headerGrabbingArea = document.createElement('div');
+ headerGrabbingArea.classList.add('flex-grow-1', 'grabbing-area', 'pe-2', 'me-2');
+ header.appendChild(headerGrabbingArea);
+
+ const pageNode = document.createElement('h6');
+ headerGrabbingArea.appendChild(pageNode);
+
+ headerGrabbingArea.onmousedown = function(event) {
+ /* move the popover around */
+ if (event.button != 0) {
+ return;
+ }
+ const popoverTip = popover.tip;
+ if (popoverTip.classList.contains('popover-maximized')) {
+ return;
+ }
+ headerGrabbingArea.classList.add('grabbing-area-grabbed');
+
+ if (!popoverTip.classList.contains('popover-detached')) {
+ /* detach popover tip */
+ popoverTip.classList.add('popover-detached');
+ const rect = popoverTip.getBoundingClientRect();
+ const style = popoverTip.style;
+ style.display = 'none'; /* avoid reflows between the following assignments */
+ style.position = 'absolute';
+ style.transform = '';
+ style.inset = `${rect.top}px auto auto ${rect.left}px`;
+ style.display = '';
+ }
+
+ let clientX = event.clientX, clientY = event.clientY;
+ document.onmousemove = function(event) {
+ const offsetX = clientX - event.clientX;
+ const offsetY = clientY - event.clientY;
+ clientX = event.clientX;
+ clientY = event.clientY;
+ popoverTip.style.top = (popoverTip.offsetTop - offsetY).toString() + 'px';
+ popoverTip.style.left = (popoverTip.offsetLeft - offsetX).toString() + 'px';
+ };
+
+ document.onmouseup = function(event) {
+ /* done moving around */
+ if (event.button != 0) {
+ return;
+ }
+ headerGrabbingArea.classList.remove('grabbing-area-grabbed');
+ document.onmousemove = null;
+ document.onmouseup = null;
+ };
+ };
+
+ /* current number page and total page count */
+ const pageNum = document.createElement('span');
+ const pageCount = document.createElement('span');
+ pageNode.appendChild(document.createTextNode('Träff '));
+ pageNode.appendChild(pageNum);
+ pageNode.appendChild(document.createTextNode(' av '));
+ pageNode.appendChild(pageCount);
+
+ /* highlight a feature */
+ const featureOverlayStyle = new Style({
+ stroke: new Stroke({
+ color: 'rgba(0, 255, 255, .8)',
+ width: 3,
+ }),
+ });
+ const highlightFeature = function(layer_group, layer, id) {
+ const source = mapSources[layer_group];
+ if (source == null) {
+ return;
+ }
+ if (featureOverlayLayer.getSource() !== source) {
+ /* console.log('Updating source for feature overlay layer'); */
+ featureOverlayLayer.setVisible(false);
+ featureOverlayLayer.setSource(source);
+ }
+ featureOverlayLayer.setStyle(function(feature) {
+ if (feature.getId() === id && feature.getProperties().layer === layer) {
+ return featureOverlayStyle;
+ }
+ });
+ featureOverlayLayer.setVisible(true);
+ featureOverlayLayer.changed();
+ };
+ /* highlight the feature at index overlayAttrIdx within the CGI reply list */
+ const refreshPopover = function() {
+ const attr = overlayAttributes[overlayAttrIdx];
+ highlightFeature(attr.layer_group, attr.layer, attr.ogc_fid);
+
+ pageNum.innerHTML = (overlayAttrIdx + 1).toString();
+ const content = formatFeaturePropertiesToHTML(attr);
+ popover.tip.getElementsByClassName('popover-body')[0].replaceChildren(content);
+ };
+ /* go back/forward in the overlayAttributes list */
+ const onClickPageChange = function(event, offset) {
+ const btn = event.target;
+ if (btn.classList.contains('disabled') || popover?.tip == null) {
+ return;
+ }
+ if (overlayAttrIdx + offset < 0 || overlayAttrIdx + offset > overlayAttributes.length - 1) {
+ return; /* out of range */
+ }
+
+ overlayAttrIdx += offset;
+ if (overlayAttrIdx < 1) {
+ btnPrev.classList.add('disabled');
+ } else {
+ btnPrev.classList.remove('disabled');
+ }
+ if (overlayAttrIdx < overlayAttributes.length - 1) {
+ btnNext.classList.remove('disabled');
+ } else {
+ btnNext.classList.add('disabled');
+ }
+
+ refreshPopover();
+ setTimeout(function() { btn.blur() }, 100);
+ };
+
+ /* control buttons */
+ const btnPrev = document.createElement('button');
+ btnPrev.classList.add('popover-button', 'popover-button-prev');
+ btnPrev.setAttribute('type', 'button');
+ btnPrev.title = 'Föregående träff';
+ btnPrev.setAttribute('aria-label', btnPrev.title);
+ btnPrev.onclick = function(event) {
+ return onClickPageChange(event, -1);
+ };
+
+ const btnNext = document.createElement('button');
+ btnNext.classList.add('popover-button', 'popover-button-next');
+ btnNext.setAttribute('type', 'button');
+ btnNext.title = 'Nästa träff';
+ btnNext.setAttribute('aria-label', btnNext.title);
+ btnNext.onclick = function(event) {
+ return onClickPageChange(event, +1);
+ };
+
+ const btnExpand = document.createElement('button');
+ btnExpand.classList.add('popover-button', 'popover-button-expand');
+ btnExpand.setAttribute('type', 'button');
+ const btnExpandTitle = 'Förstora';
+ const btnExpandTitle2 = 'Förminska';
+ btnExpand.setAttribute('aria-label', btnExpand.title);
+ btnExpand.onclick = function() { /* maximize or reduce the popover */
+ if (popover?.tip == null) {
+ return;
+ }
+ if (!popover.tip.classList.contains('popover-maximized')) {
+ popover.tip.classList.add('popover-maximized');
+ btnExpand.classList.replace('popover-button-expand', 'popover-button-reduce');
+ btnExpand.title = btnExpandTitle2;
+ btnExpand.setAttribute('aria-label', btnExpand.title);
+ } else {
+ popover.tip.classList.remove('popover-maximized');
+ btnExpand.classList.replace('popover-button-reduce', 'popover-button-expand');
+ btnExpand.title = btnExpandTitle;
+ btnExpand.setAttribute('aria-label', btnExpand.title);
+ }
+ setTimeout(function() { btnExpand.blur() }, 100);
+ };
+
+ const btnClose = document.createElement('button');
+ btnClose.classList.add('popover-button', 'popover-button-close');
+ btnClose.setAttribute('type', 'button');
+ btnClose.title = 'Stäng';
+ btnClose.setAttribute('aria-label', btnClose.title);
+ btnClose.onclick = disposePopover;
+
+ header.appendChild(btnPrev);
+ header.appendChild(btnNext);
+ header.appendChild(btnExpand);
+ header.appendChild(btnClose);
+
+ MAP.on('singleclick', function(event) {
+ disposeFeatureOverlay();
+
+ /* dispose any pre-existing popover if not in detached mode */
+ popover = Popover.getInstance(popupOverlay.element);
+ if (popover?.tip != null && !popover.tip.classList.contains('popover-detached')) {
+ popover.dispose();
+ }
+
+ const size = event.map.getSize();
+ if (size[0] < 576 || size[1] < 576) {
+ return; /* skip popover if the map is too small */
+ }
+
+ /* unclear how many feature we'll find, don't render prev/next buttons for now */
+ pageNode.classList.add('d-none');
+ btnPrev.classList.add('d-none', 'disabled');
+ btnNext.classList.add('d-none', 'disabled');
+
+ /* never start in maximized mode */
+ if (popover?.tip != null) {
+ popover.tip.classList.remove('popover-maximized');
+ }
+ btnExpand.classList.replace('popover-button-reduce', 'popover-button-expand');
+ btnExpand.title = btnExpandTitle;
+ btnExpand.setAttribute('aria-label', btnExpand.title);
+
+ const fetch_body = [];
+ event.map.forEachFeatureAtPixel(event.pixel, function(feature, layer) {
+ const layerGroup = layer.get('layerGroup');
+ const layerName = feature.getProperties().layer;
+ mapSources[layerGroup] ??= layer.getSource();
+ const def = layerName != null ? layers[layerGroup][layerName] : null;
+ if (def?.fields == null) {
+ /* skip layers which didn't opt-in for popover */
+ return false;
+ }
+ if (fetch_body.length === 0) {
+ /* first feature in the list, mark cursor and detached popover as in-progress */
+ document.body.classList.add('inprogress');
+ popover?.tip?.classList?.add?.('inprogress');
+ }
+ fetch_body.push({
+ layer_group: layerGroup,
+ layer: layerName,
+ fid: feature.getId() ?? -1,
+ });
+ if (fetch_body.length >= 100) {
+ return true; /* enough matches already, stop detection here */
+ }
+ }, {
+ hitTolerance: 5,
+ checkWrapped: false,
+ layerFilter: (lyr) => lyr.get('layerGroup') != null,
+ });
+
+ if (fetch_body.length === 0) {
+ /* no feature at pixel (or only within layers which didn't opt-in for popover) */
+ if (popover?.tip != null) {
+ /* dispose pre-detached popover */
+ popover.dispose();
+ }
+ return;
+ }
+
+ fetch('/q', {
+ method: 'POST',
+ body: JSON.stringify(fetch_body),
+ headers: {
+ 'Content-Type': 'application/json; charset=UTF-8',
+ },
+ })
+ .then(function(resp) {
+ if (resp.status === 200) {
+ return resp.json();
+ } else {
+ throw new Error(`${resp.url} [${resp.status}]`);
+ }
+ })
+ .then(function(data) {
+ /* the data is received from the CGI in the order it was sent */
+ /* TODO optimizations on the CGI would break the above assumption, so the
+ * decoded JSON response would need to be reordered to match fetch_body */
+ overlayAttributes = data;
+ if (overlayAttributes.length === 0) {
+ /* couldn't fetch any attribute for feature(s) at pixel */
+ if (popover?.tip != null) {
+ /* dispose pre-detached popover */
+ popover.dispose();
+ }
+ return;
+ }
+
+ pageCount.innerHTML = overlayAttributes.length.toString();
+ if (overlayAttributes.length >= 2) {
+ /* render prev/pre buttons */
+ btnNext.classList.remove('d-none', 'disabled');
+ btnPrev.classList.remove('d-none');
+ pageNode.classList.remove('d-none');
+ }
+ if (popover?.tip == null) {
+ /* create a new popover (we're not already showing one in detached mode) */
+ pageNum.innerHTML = (overlayAttrIdx + 1).toString();
+ popupOverlay.setPosition(event.coordinate);
+
+ const attr = overlayAttributes[0];
+ highlightFeature(attr.layer_group, attr.layer, attr.ogc_fid);
+ popover = new Popover(popupOverlay.element, {
+ template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div>' +
+ '<div class="popover-header"></div><div class="popover-body"></div></div>',
+ title: header,
+ content: formatFeaturePropertiesToHTML(attr),
+ html: true,
+ placement: 'right',
+ fallbackPlacements: ['right', 'left', 'bottom', 'top'],
+ container: CONTAINER_STOPEVENT,
+ });
+ popover.show();
+ }
+ else if (popover.tip.classList.contains('popover-detached')) {
+ /* update existing detached mode popover */
+ refreshPopover();
+ popover.tip.classList.remove('inprogress');
+ }
+ })
+ .catch(function(e) {
+ console.log(e);
+ })
+ .finally(function() {
+ /* remove in-progress marking on the cursor */
+ document.body.classList.remove('inprogress');
+ });
+ });
+
+ return disposePopover;
+})();
+
+/* age filter dialog */
(function() {
const panel = document.getElementById('age-filter-modal');
@@ -1834,7 +5135,7 @@ const infoMetadataAccordions = [];
p.appendChild(document.createTextNode('.'));
type_choice._update_helptext = function() {
- const d = age_filter_settings.get_relative_date(
+ const d = ageFilterSettings.getRelativeDate(
parseInt(type_choice.quantity.value, 10),
type_choice.unit[0].value
);
@@ -1957,7 +5258,7 @@ const infoMetadataAccordions = [];
checkbox.type = 'checkbox';
checkbox.id = 'age-filter-show-unknown';
checkbox.setAttribute('role', 'switch');
- checkbox.checked = age_filter_settings.show_unknown;
+ checkbox.checked = ageFilterSettings.show_unknown;
div.appendChild(checkbox);
const lbl = document.createElement('label');
@@ -1988,7 +5289,7 @@ const infoMetadataAccordions = [];
btn_cancel.onclick = function() {
/* deactivate deactivate the filter but preserve its settings */
- age_filter_settings.active = false;
+ ageFilterSettings.active = false;
Object.values(mapLayers).forEach(function(lyr) {
if (lyr?.get('canFilterByAge')) {
lyr.changed();
@@ -2007,29 +5308,29 @@ const infoMetadataAccordions = [];
event.preventDefault();
const [filter_type, filter_settings] = Object.entries(type_choices).filter( (x) => x[1].radio.checked )[0];
let param;
- age_filter_settings._min_ts = age_filter_settings._max_ts = null;
+ ageFilterSettings._min_ts = ageFilterSettings._max_ts = null;
switch (filter_type) {
case 'relative': {
- const operator = age_filter_settings.operator = filter_settings.operator[0].value;
- age_filter_settings.quantity = parseInt(filter_settings.quantity.value, 10);
- age_filter_settings.unit = filter_settings.unit[0].value;
+ const operator = ageFilterSettings.operator = filter_settings.operator[0].value;
+ ageFilterSettings.quantity = parseInt(filter_settings.quantity.value, 10);
+ ageFilterSettings.unit = filter_settings.unit[0].value;
param = {'<=':'-', '>=':''}[operator];
- param += age_filter_settings.quantity.toString() + age_filter_settings.unit;
+ param += ageFilterSettings.quantity.toString() + ageFilterSettings.unit;
break;
}
case 'interval': {
- const date1 = age_filter_settings.from = parse_date(filter_settings.from.value);
- const date2 = age_filter_settings.to = parse_date(filter_settings.to.value);
+ const date1 = ageFilterSettings.from = parse_date(filter_settings.from.value);
+ const date2 = ageFilterSettings.to = parse_date(filter_settings.to.value);
param = format_date(date1, '') + '-' + format_date(date2, '');
break;
}
default:
return;
}
- age_filter_settings.type = filter_type;
- age_filter_settings.show_unknown = show_unknown_age.checked;
- age_filter_settings.setup_minmax();
- age_filter_settings.active = true;
+ ageFilterSettings.type = filter_type;
+ ageFilterSettings.show_unknown = show_unknown_age.checked;
+ ageFilterSettings.setupMinMax();
+ ageFilterSettings.active = true;
/* TODO auto update the filter passed midnight (if active) */
Object.values(mapLayers).forEach(function(lyr) {
if (lyr?.get('canFilterByAge')) {
@@ -2049,24 +5350,24 @@ const infoMetadataAccordions = [];
* button to show the relevant <div>, mark its fields as required and
* fills them. The function is run whenever the modal is shown. */
return function() {
- const type_choice = type_choices[age_filter_settings.type];
+ const type_choice = type_choices[ageFilterSettings.type];
type_choice.radio.click();
- switch (age_filter_settings.type) {
+ switch (ageFilterSettings.type) {
case 'relative': {
Object.entries(type_choice.operator[1]).map(function([id, option]) {
- option.selected = id === age_filter_settings.operator;
+ option.selected = id === ageFilterSettings.operator;
});
- type_choice.quantity.value = age_filter_settings.quantity.toString();
+ type_choice.quantity.value = ageFilterSettings.quantity.toString();
Object.entries(type_choice.unit[1]).map(function([id, option]) {
- option.selected = id === age_filter_settings.unit;
+ option.selected = id === ageFilterSettings.unit;
});
type_choice.quantity.dispatchEvent(new Event('change')); /* propagate to absolute */
break;
}
case 'interval': {
- type_choice.from.value = format_date(age_filter_settings.from);
- type_choice.to.value = format_date(age_filter_settings.to);
+ type_choice.from.value = format_date(ageFilterSettings.from);
+ type_choice.to.value = format_date(ageFilterSettings.to);
type_choice.from.dispatchEvent(new Event('change')); /* propagate to relative */
break;
}
@@ -2084,7 +5385,7 @@ const infoMetadataAccordions = [];
};
const btn = document.getElementById('age-filter-button').getElementsByTagName('button')[0];
- if (age_filter_settings.active) {
+ if (ageFilterSettings.active) {
btn.classList.replace('btn-light', 'btn-dark');
}
panel.addEventListener('show.bs.modal', function() {
@@ -2100,7 +5401,7 @@ const infoMetadataAccordions = [];
}
});
panel.addEventListener('hidden.bs.modal', function() {
- if (!age_filter_settings.active) {
+ if (!ageFilterSettings.active) {
btn.classList.replace('btn-dark', 'btn-light');
}
btn.setAttribute('aria-expanded', 'false');
diff --git a/src/layers.js b/src/layers.js
deleted file mode 100644
index 688bb44..0000000
--- a/src/layers.js
+++ /dev/null
@@ -1,1908 +0,0 @@
-/***********************************************************************
- * Copyright © 2024-2025 Guilhem Moulin <info@guilhem.se>
- * Vector and raster layer definitions
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- **********************************************************************/
-
-import CircleStyle from 'ol/style/Circle.js';
-import Fill from 'ol/style/Fill.js';
-import Icon from 'ol/style/Icon.js';
-import RegularShape from 'ol/style/RegularShape.js';
-import Stroke from 'ol/style/Stroke.js';
-import Style from 'ol/style/Style.js';
-
-/* TODO: this should really be refactored… */
-export const layers = {
- 'mrr.appr_ec': {
- legend: { zoomLevel: 4 },
- style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- return new Style({
- zIndex: 22,
- fill: new Fill({
- color: [247, 170, 67, Math.max((.2-1)/8 * z + 1, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [151, 173, 23, 1],
- }),
- });
- }),
- },
- 'mrr.appl_ec': {
- legend: { zoomLevel: 4 },
- style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- return new Style({
- zIndex: 25,
- fill: new Fill({
- color: [247, 170, 67, Math.max((.2-1)/8 * z + 1, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [197, 14, 31, 1],
- lineDash: width >= 1.5 ? [2 * width] : undefined,
- }),
- });
- }),
- },
- 'mrr.appr_met': {
- legend: { zoomLevel: 4 },
- style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- return new Style({
- zIndex: 24,
- fill: new Fill({
- color: [0, 0, 0, Math.max((.2-.4)/4 * z + .4, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [151, 173, 23, 1],
- }),
- });
- }),
- },
- 'mrr.appl_met': {
- legend: { zoomLevel: 4 },
- style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- return new Style({
- zIndex: 26,
- fill: new Fill({
- color: [0, 0, 0, Math.max((.2-.4)/4 * z + .4, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [197, 14, 31, 1],
- lineDash: width >= 1.5 ? [2 * width] : undefined,
- }),
- });
- }),
- },
- 'mrr.appr_ogd': {
- legend: { zoomLevel: 4 },
- style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- return new Style({
- zIndex: 24,
- fill: new Fill({
- color: [30, 55, 87, Math.max((.2-.4)/4 * z + .4, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [151, 173, 23, 1],
- }),
- });
- }),
- },
- 'mrr.appl_ogd': {
- legend: { zoomLevel: 4 },
- style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- return new Style({
- zIndex: 26,
- fill: new Fill({
- color: [30, 55, 87, Math.max((.2-.4)/4 * z + .4, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [197, 14, 31, 1],
- lineDash: width >= 1.5 ? [2 * width] : undefined,
- }),
- });
- }),
- },
- 'mrr.appr_dl': {
- legend: { zoomLevel: 4 },
- style: [0, .1, .5, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- return new Style({
- zIndex: 20,
- fill: new Fill({
- color: [228, 53, 45, Math.max((.2-1)/6 * z + 1, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [151, 173, 23, 1],
- }),
- });
- }),
- },
-
- 'svk.ledningar': {
- legend: { zoomLevel: 5, type: 'linestring', reuse_canvas: true },
- style: [1, 1.5, 2, 2, 2, 2, 3, 4, 5, 6, 8, 10].map(function(width) {
- return new Style({
- zIndex: 52,
- stroke: new Stroke({
- color: 'black',
- width: width,
- }),
- });
- }),
- },
- 'svk.stolpar': {
- legend: { zoomLevel: 5, type: 'point' },
- style: [undefined, undefined, undefined, undefined, undefined]
- .concat([3, 4, 5, 6, 8, 10, 15].map(function(radius) {
- return new Style({
- zIndex: 51,
- image: new CircleStyle({
- radius: radius,
- fill: new Fill({
- color: 'black',
- }),
- }),
- });
- })),
- },
- 'svk.transmissionsnatsprojekt': {
- legend: { zoomLevel: 5, type: 'linestring' },
- style: [1, 1.5, 2, 2, 2, 2, 3, 4, 5, 6, 8, 10].map(function(width) {
- return new Style({
- zIndex: 53,
- stroke: new Stroke({
- color: 'black',
- width: width,
- lineDash: [4 * width],
- }),
- });
- }),
- },
- 'svk.stationer': {
- legend: { zoomLevel: 3, type: 'point' },
- style: [3, 4, 5, 6, 7, 8.5, 10].map(function(radius) {
- return new Style({
- zIndex: 50,
- image: new RegularShape({
- radius: radius,
- points: 4,
- angle: Math.PI/4,
- fill: new Fill({
- color: 'black',
- }),
- }),
- });
- })
- .concat([.5, 1, 1.5, 2, 2].map(function(width) {
- return new Style({
- zIndex: 50,
- fill: new Fill({
- color: 'rgba(128, 128, 128, .7)',
- }),
- stroke: new Stroke({
- width: width,
- color: 'rgb(0, 0, 0)',
- }),
- });
- })),
- },
-
- 'vbk.area_current': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- return new Style({
- zIndex: 10,
- fill: new Fill({
- color: [168, 198, 223, Math.max((.2-1)/8 * z + 1, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [56, 96, 130, 1],
- }),
- });
- }),
- },
- 'vbk.area_notcurrent': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- return new Style({
- zIndex: 10,
- fill: new Fill({
- color: [222, 163, 199, Math.max((.2-1)/8 * z + 1, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [148, 55, 112, 1],
- lineDash: width >= 1.5 ? [2 * width] : undefined,
- }),
- });
- }),
- },
- 'vbk.offshore_completed': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
- return new Style({
- zIndex: 17,
- fill: new Fill({
- color: [38, 107, 29, .5],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [38, 107, 29, 1],
- }),
- });
- }),
- },
- 'vbk.offshore_approved': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
- return new Style({
- zIndex: 16,
- fill: new Fill({
- color: [56, 160, 44, .5],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [56, 160, 44, 1],
- }),
- });
- }),
- },
- 'vbk.offshore_amended': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- const w = z < 4 ? .5 : z <= 5 ? 1.5 : 4;
- patternCanvas.width = width/2;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'rgba(247, 105, 162, 1)';
- patternContext.beginPath();
- patternContext.arc(.75*patternCanvas.width, .75*patternCanvas.height, 1.5*w, 0, 2*Math.PI, true)
- patternContext.fill();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 17,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: 2*w,
- color: [247, 105, 162, 1],
- lineDash: [8 * w],
- }),
- });
- }),
- },
- 'vbk.offshore_rejected': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
- return new Style({
- zIndex: 11,
- fill: new Fill({
- color: [227, 26, 28, .5],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [227, 26, 28, 1],
- }),
- });
- }),
- },
- 'vbk.offshore_appealed': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
- return new Style({
- zIndex: 15,
- fill: new Fill({
- color: [177, 88, 40, .5],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [177, 88, 40, 1],
- }),
- });
- }),
- },
- 'vbk.offshore_applied': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
- return new Style({
- zIndex: 14,
- fill: new Fill({
- color: [255, 127, 0, .5],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [255, 128, 0, 1],
- }),
- });
- }),
- },
- 'vbk.offshore_consultation': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
- return new Style({
- zIndex: 13,
- fill: new Fill({
- color: [254, 217, 118, .65],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [254, 183, 82, 1],
- }),
- });
- }),
- },
- 'vbk.offshore_investigation': {
- legend: { zoomLevel: 1 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- const w = z < 4 ? .5 : z <= 5 ? 1.5 : 4;
- patternCanvas.width = width*2;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(68, 90, 166, 1)';
- patternContext.lineWidth = w;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 12,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: 2*w,
- color: [68, 90, 166, 1],
- lineDash: [8 * w],
- }),
- });
- }),
- },
- 'vbk.offshore_revoked': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
- return new Style({
- zIndex: 10,
- fill: new Fill({
- color: [105, 61, 154, .5],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [105, 62, 153, 1],
- }),
- });
- }),
- },
- 'vbk.station_completed': {
- legend: { zoomLevel: 7, type: 'point' },
- style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
- return scale === undefined ? undefined : new Style({
- zIndex: 99,
- image: new Icon({
- src: '/assets/icons/wind-turbine-completed.svg',
- declutter: 'none',
- scale: scale,
- }),
- });
- }),
- },
- 'vbk.station_processed': {
- legend: { zoomLevel: 7, type: 'point' },
- style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
- return scale === undefined ? undefined : new Style({
- zIndex: 99,
- image: new Icon({
- src: '/assets/icons/wind-turbine-processed.svg',
- declutter: 'none',
- scale: scale,
- }),
- });
- }),
- },
- 'vbk.station_approved': {
- legend: { zoomLevel: 7, type: 'point' },
- style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
- return scale === undefined ? undefined : new Style({
- zIndex: 99,
- image: new Icon({
- src: '/assets/icons/wind-turbine-approved.svg',
- declutter: 'none',
- scale: scale,
- }),
- });
- }),
- },
- 'vbk.station_revoked': {
- legend: { zoomLevel: 7, type: 'point' },
- style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
- return scale === undefined ? undefined : new Style({
- zIndex: 99,
- image: new Icon({
- src: '/assets/icons/wind-turbine-revoked.svg',
- declutter: 'none',
- scale: scale,
- }),
- });
- }),
- },
- 'vbk.station_rejected': {
- legend: { zoomLevel: 7, type: 'point' },
- style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
- return scale === undefined ? undefined : new Style({
- zIndex: 99,
- image: new Icon({
- src: '/assets/icons/wind-turbine-rejected.svg',
- declutter: 'none',
- scale: scale,
- }),
- });
- }),
- },
- 'vbk.station_dismounted': {
- legend: { zoomLevel: 7, type: 'point' },
- style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
- return scale === undefined ? undefined : new Style({
- zIndex: 99,
- image: new Icon({
- src: '/assets/icons/wind-turbine-dismounted.svg',
- declutter: 'none',
- scale: scale,
- }),
- });
- }),
- },
- 'vbk.station_appealed': {
- legend: { zoomLevel: 7, type: 'point' },
- style: [undefined, undefined, undefined, undefined, .125, .125, .25, .5, 1, 2, 4, 8].map(function(scale) {
- return scale === undefined ? undefined : new Style({
- zIndex: 99,
- image: new Icon({
- src: '/assets/icons/wind-turbine-appealed.svg',
- declutter: 'none',
- scale: scale,
- }),
- });
- }),
- },
-
- /* Documentation at
- * https://www.skogsstyrelsen.se/globalassets/sjalvservice/karttjanster/geodatatjanster/produktbeskrivningar/utforda-avverkningar---produktbeskrivning.pdf
- * */
- 'avverk.utford': {
- legend: { zoomLevel: 7 },
- style: [0, 0, 0, 0, 0, .5, .75, 1, 1, 1, 1, 1].map(function(width, z) {
- return new Style({
- zIndex: 10,
- fill: new Fill({
- color: [255, 102, 102, Math.max((.2-1)/8 * z + 1, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [204, 0, 0, 1],
- }),
- });
- }),
- },
- /* Documentation at
- * https://www.skogsstyrelsen.se/globalassets/sjalvservice/karttjanster/geodatatjanster/produktbeskrivningar/yttre-granser-for-avverkningsanmalda-omraden---produktbeskrivning.pdf
- * */
- 'avverk.anmald': {
- legend: { zoomLevel: 7 },
- style: [0, 0, 0, 0, 0, .5, .75, 1, 1, 1, 1, 1].map(function(width, z) {
- return new Style({
- zIndex: 10,
- fill: (width === undefined || width === 0) ?
- new Fill({ color: [255, 102, 102, Math.max((.2-1)/8 * z + 1, 0)*.75] }) :
- (function() {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- const slope = 45 * Math.PI/180;
- const spacing = z < 10 ? z*2 : 40;
- const len = Math.hypot(1, slope);
- const w = patternCanvas.width = Math.round(1/len + spacing)
- const h = patternCanvas.height = Math.round(slope/len + spacing * slope);
-
- patternContext.fillStyle = 'rgba(255, 102, 102, .1)';
- patternContext.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
- patternContext.strokeStyle = 'rgba(204, 0, 0, 1)';
- patternContext.lineWidth = Math.max(1, width/2);
- patternContext.beginPath();
- patternContext.moveTo(0, h);
- patternContext.lineTo(w, 0);
- patternContext.moveTo(-w, h);
- patternContext.lineTo(w, -h);
- patternContext.moveTo(0, 2*h);
- patternContext.lineTo(2*w, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Fill({ color: context.createPattern(patternCanvas, 'repeat') });
- })(),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [204, 0, 0, 1],
- lineDash: width >= 1.5 ? [2 * width] : undefined,
- }),
- });
- }),
- },
-
- 'skydd.tilltradesforbud': {
- legend: { zoomLevel: 2 },
- style: [1, 1.5, 2, 3, 3.5, 4, 5, 5, 6, 7, 8, 10].map(function(width) {
- return new Style({
- zIndex: 23,
- fill: new Fill({
- /* transparent fill so clicking the inside of the polygon triggers a popover */
- /* XXX could also use a custom renderer but that doesn't seem to work */
- color: [0, 0, 0, 0],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [255, 0, 0, 1],
- }),
- });
- }),
- },
- 'skydd.nationalpark': {
- legend: { zoomLevel: 1 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(0, 55, 0, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 22,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [0, 55, 0, 1],
- }),
- });
- }),
- },
- 'skydd.naturreservat': {
- legend: { zoomLevel: 1 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(7, 181, 7, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 21,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [7, 181, 7, 1],
- }),
- });
- }),
- },
- 'skydd.naturreservat_kommunalt': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(7, 181, 7, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, -patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, 0);
- patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 20,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [7, 181, 7, 1],
- }),
- });
- }),
- },
- 'skydd.naturvardsomrade': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(176, 255, 176, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 19,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [176, 255, 176, 1],
- }),
- });
- }),
- },
- 'skydd.djur_och_vaxtskyddsomrade': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(255, 255, 0, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 18,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [255, 255, 0, 1],
- }),
- });
- }),
- },
- 'skydd.kulturreservat': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(154, 102, 255, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 17,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [154, 102, 255, 1],
- }),
- });
- }),
- },
- 'skydd.vattenskyddsomrade': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(0, 105, 212, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 16,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [0, 105, 212, 1],
- }),
- });
- }),
- },
- 'skydd.landskapsbildsskyddsomrade': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(135, 110, 71, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 15,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [134, 110, 71, 1],
- }),
- });
- }),
- },
- 'skydd.skogligt_biotopskyddsomrade': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(135, 90, 71, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 14,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 4 ? .5 : z <= 5 ? 1 : 2,
- color: [134, 90, 71, 1],
- }),
- });
- }),
- },
- 'skydd.ovrigt_biotopskyddsomrade': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(255, 95, 0, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 13,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 4 ? .5 : z <= 5 ? 1 : 2,
- color: [255, 95, 0, 1],
- }),
- });
- }),
- },
- 'skydd.naturminne_yta': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(113, 0, 116, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 12,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [134, 0, 116, 1],
- }),
- });
- }),
- },
- 'skydd.naturminne_punkt': {
- legend: { zoomLevel: 6, type: 'point' },
- style: [undefined, undefined, undefined, undefined].concat([3, 4, 6, 8, 12, 16, 20, 24].map(function(width) {
- return new Style({
- zIndex: 12,
- image: new CircleStyle({
- radius: width,
- fill: new Fill({
- color: 'rgba(113, 0, 116, .5)',
- }),
- stroke: new Stroke({
- width: Math.log2(width)/2,
- color: 'rgba(113, 0, 116, 1)',
- }),
- }),
- });
- }))
- },
- 'skydd.interimistiskt_forbud': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(168, 0, 0, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 11,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [168, 0, 0, 1],
- }),
- });
- }),
- },
- 'skydd.fageldirektivet': {
- legend: { zoomLevel: 1 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width*2;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(230, 0, 0, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.beginPath();
- patternContext.lineWidth *= 6;
- patternContext.moveTo(-.5*patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -.5*patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 1.5*patternCanvas.height);
- patternContext.lineTo(1.5*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 10,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 4 ? .5 : z <= 5 ? 1 : 2,
- color: [230, 0, 0, 1],
- }),
- });
- }),
- },
- 'skydd.habitatdirektivet': {
- legend: { zoomLevel: 1 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width*2;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(0, 77, 168, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, -patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, 0);
- patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
- patternContext.stroke();
- patternContext.beginPath();
- patternContext.lineWidth *= 6;
- patternContext.moveTo(0, -.5*patternCanvas.height);
- patternContext.lineTo(1.5*patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(-.5*patternCanvas.width, 0);
- patternContext.lineTo(patternCanvas.width, 1.5*patternCanvas.height);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 10,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 4 ? .5 : z <= 5 ? 1 : 2,
- color: [0, 77, 168, 1],
- }),
- });
- }),
- },
- 'skydd.helcom': {
- legend: { zoomLevel: 1 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'rgba(130, 130, 130, 1)';
- const r = z < 5 ? (z+1)*.75 : z*.5;
- patternContext.beginPath();
- patternContext.arc(.5*patternCanvas.width, .5*patternCanvas.height, r, 0, 2*Math.PI, true)
- patternContext.fill();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 9,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [130, 130, 130, 1],
- }),
- });
- }),
- },
- 'skydd.ramsar': {
- legend: { zoomLevel: 1 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'rgba(195, 0, 255, 1)';
- const r = z < 5 ? (z+1)*.75 : z*.5;
- patternContext.beginPath();
- patternContext.arc(.25*patternCanvas.width, .25*patternCanvas.height, r, 0, 2*Math.PI, true)
- patternContext.fill();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 9,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [195, 0, 255, 1],
- }),
- });
- }),
- },
- 'skydd.ospar': {
- legend: { zoomLevel: 1 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'rgba(168, 0, 0, 1)';
- const r = z < 5 ? (z+1)*.75 : z*.5;
- patternContext.beginPath();
- patternContext.arc(.25*patternCanvas.width, .75*patternCanvas.height, r, 0, 2*Math.PI, true)
- patternContext.fill();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 9,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [168, 0, 0, 1],
- }),
- });
- }),
- },
- 'skydd.varldsarv': {
- legend: { zoomLevel: 1 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'rgba(168, 0, 0, 1)';
- const r = z < 5 ? (z+1)*.75 : z*.5;
- patternContext.beginPath();
- patternContext.arc(.75*patternCanvas.width, .25*patternCanvas.height, r, 0, 2*Math.PI, true)
- patternContext.fill();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 9,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [168, 0, 0, 1],
- }),
- });
- }),
- },
- 'skydd.biosfarsomraden': {
- legend: { zoomLevel: 1 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'rgba(131, 0, 219, 1)';
- const r = z < 5 ? (z+1)*.75 : z*.5;
- patternContext.beginPath();
- patternContext.arc(.75*patternCanvas.width, .75*patternCanvas.height, r, 0, 2*Math.PI, true)
- patternContext.fill();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 9,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [131, 0, 219, 1],
- }),
- });
- }),
- },
- 'skydd.naturvardsavtal': {
- legend: { zoomLevel: 1 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(255, 0, 197, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 21,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 4 ? .5 : z <= 5 ? 1 : 2,
- color: [255, 0, 197, 1],
- }),
- });
- }),
- },
- 'skydd.naturvardsavtal_skogsstyrelsen': {
- legend: { zoomLevel: 2 },
- style: [4, 8, 16, 16, 32, 32, 64, 64, 64, 128, 128, 128].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(255, 0, 197, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, -patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, 0);
- patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 20,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 4 ? .5 : z <= 5 ? 1 : 2,
- color: [255, 0, 197, 1],
- }),
- });
- }),
- },
- 'skydd.atervatningsavtal': {
- legend: { zoomLevel: 0 },
- style: [0, 1, 2, 3, 4, 5, 6].map(function(width) {
- return new Style({
- zIndex: 5,
- fill: new Fill({
- color: [255, 115, 0, .4],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: .5,
- color: [255, 115, 0, 1],
- }),
- });
- })
- .concat([7, 8, 9, 10, 11].map(function() {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = 16;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(255, 115, 0, 1)';
- patternContext.lineWidth = 1;
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, -patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, 0);
- patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 5,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: new Stroke({
- width: 1.5,
- color: [255, 115, 0, 1],
- }),
- });
- })),
- },
- 'nv.naturvarde_sks': {
- legend: { zoomLevel: 0 },
- style: [0, 1, 2, 3, 4, 5].map(function(width) {
- return new Style({
- zIndex: 6,
- fill: new Fill({
- color: [255, 170, 0, .2],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: .5,
- color: [255, 170, 0, .8],
- }),
- });
- })
- .concat([6, 7, 8, 9, 10, 11].map(function() {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = 16;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(255, 170, 0, 1)';
- patternContext.lineWidth = 1;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 6,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: new Stroke({
- width: 1.5,
- color: [255, 170, 0, 1],
- }),
- });
- })),
- },
- 'nv.nyckelbiotop': {
- legend: { zoomLevel: 0 },
- style: [0, 1, 2, 3, 4, 5].map(function(width) {
- return new Style({
- zIndex: 6,
- fill: new Fill({
- color: [217, 148, 9, .2],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: .5,
- color: [217, 148, 9, .8],
- }),
- });
- })
- .concat([6, 7, 8, 9, 10, 11].map(function() {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = 16;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(217, 148, 9, 1)';
- patternContext.lineWidth = 1;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 6,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: new Stroke({
- width: 1.5,
- color: [217, 148, 9, 1],
- }),
- });
- })),
- },
- 'nv.nyckelbiotop_storskogsbruk': {
- legend: { zoomLevel: 0 },
- style: [0, 1, 2, 3, 4, 5].map(function(width) {
- return new Style({
- zIndex: 6,
- fill: new Fill({
- color: [217, 148, 9, .2],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: .5,
- color: [217, 148, 9, .8],
- }),
- });
- })
- .concat([6, 7, 8, 9, 10, 11].map(function() {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = 16;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(217, 148, 9, 1)';
- patternContext.lineWidth = 1;
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, -patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, 0);
- patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 6,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: new Stroke({
- width: 1.5,
- color: [217, 148, 9, 1],
- }),
- });
- })),
- },
- 'nv.sumpskog': {
- legend: { zoomLevel: 5 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- const w = Math.max(1, width);
- patternCanvas.width = z < 2 ? 2 : z < 4 ? 4 : z <= 5 ? 6 : 8;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(158, 200, 215, 1)';
- patternContext.lineWidth = w;
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 5,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: w/2,
- color: [158, 200, 215, 1],
- }),
- });
- }),
- },
- 'nv.pagaende_naturreservatsbildning': {
- legend: { zoomLevel: 1 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.setLineDash([width/4, width/4]);
- patternContext.strokeStyle = 'rgba(7, 181, 7, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(width/4, 0);
- patternContext.lineTo(width/4, patternCanvas.height);
- patternContext.stroke();
- patternContext.beginPath();
- patternContext.lineDashOffset = width/4;
- patternContext.moveTo(3*width/4, 0);
- patternContext.lineTo(3*width/4, patternCanvas.height);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 10,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 3 : 4,
- color: [7, 181, 7, 1],
- lineDash: [width/8, width/4],
- }),
- });
- }),
- },
- 'nv.snus': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width) {
- return new Style({
- zIndex: 4,
- fill: new Fill({
- color: [168,168,0,.2],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [168,77,0,.75],
- }),
- });
- }),
- },
-
- 'ri.naturvard': {
- legend: { zoomLevel: 0 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(154, 230, 0, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, -patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, 0);
- patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 8,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [154, 230, 0, 1],
- }),
- });
- }),
- },
- 'ri.friluftsliv': {
- legend: { zoomLevel: 0 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(0, 127, 232, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, -patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, 0);
- patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 8,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 1 : z < 4 ? 2 : z <= 5 ? 4 : 8,
- color: [0, 127, 232, 1],
- }),
- });
- }),
- },
- 'ri.rorligt_friluftsliv': {
- legend: { zoomLevel: 0 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'rgba(187, 227, 212, .25)';
- patternContext.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
- patternContext.strokeStyle = 'rgba(56, 151, 117, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, patternCanvas.height);
- patternContext.lineTo(patternCanvas.width, -patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, 2*patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 8,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 2 : z < 4 ? 4 : z <= 5 ? 8 : 16,
- color: [56, 151, 117, 1],
- lineDash: [width/4, width/3],
- }),
- });
- }),
- },
- 'ri.obruten_kust': {
- legend: { zoomLevel: 0 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'rgba(227, 227, 187, .25)';
- patternContext.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
- patternContext.strokeStyle = 'rgba(156, 158, 56, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, -patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, 0);
- patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 8,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 2 : z < 4 ? 4 : z <= 5 ? 8 : 16,
- color: [156, 158, 56, 1],
- lineDash: [width/4, width/3],
- }),
- });
- }),
- },
- 'ri.obrutet_fjall': {
- legend: { zoomLevel: 0 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = width;
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'rgba(255, 255, 209, .25)';
- patternContext.fillRect(0, 0, patternCanvas.width, patternCanvas.height);
- patternContext.strokeStyle = 'rgba(219, 183, 60, 1)';
- patternContext.lineWidth = z < 4 ? .5 : z <= 5 ? 1 : 2;
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(0, -patternCanvas.height);
- patternContext.lineTo(2*patternCanvas.width, patternCanvas.height);
- patternContext.stroke();
- patternContext.moveTo(-patternCanvas.width, 0);
- patternContext.lineTo(patternCanvas.width, 2*patternCanvas.height);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 8,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 2 : z < 4 ? 4 : z <= 5 ? 8 : 16,
- color: [219, 183, 60, 1],
- lineDash: [width/4, width/3],
- }),
- });
- }),
- },
- 'ri.skyddade_vattendrag': {
- legend: { zoomLevel: 0 },
- style: [8, 16, 32, 32, 64, 64, 128, 128, 128, 256, 256, 256].map(function(width, z) {
- return new Style({
- zIndex: 8,
- fill: new Fill({
- color: [102, 157, 240, .25],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: z < 2 ? 2 : z < 4 ? 4 : z <= 5 ? 8 : 16,
- color: [41, 109, 197, 1],
- lineDash: [width/4, width/3],
- }),
- });
- }),
- },
-
- 'ren.betesomrade': {
- legend: { zoomLevel: 0 },
- style: [1, 1.5, 2, 3, 3.5, 4, 5, 5, 6, 7, 8, 10].map(function(width) {
- return new Style({
- zIndex: 4,
- fill: new Fill({
- /* transparent fill so clicking the inside of the polygon triggers a popover */
- /* XXX could also use a custom renderer but that doesn't seem to work */
- color: [0, 0, 0, 0],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [179, 153, 102, 1],
- }),
- });
- }),
- },
- 'ren.flyttled': {
- legend: { zoomLevel: 2, type: 'linestring' },
- style: [.75, 1, 1.5, 2, 3, 4, 5, 5, 6, 7, 8, 10].map(function(width) {
- return new Style({
- zIndex: 7,
- stroke: new Stroke({
- width: 2*width,
- color: [119, 99, 59, 1],
- lineDash: [4 * width],
- }),
- });
- }),
- },
- 'ren.riks_ren': {
- legend: { zoomLevel: 1 },
- style: [.5, 1, 1.5, 1.5, 2, 2, 2.5, 2.5, 3, 3.5, 4, 5].map(function(width, z) {
- const patternCanvas = document.createElement('canvas');
- const patternContext = patternCanvas.getContext('2d');
- patternCanvas.width = z < 4 ? 4 : z <= 5 ? 8 : Math.pow(2, Math.round(Math.log2(width) + 3));
- patternCanvas.height = patternCanvas.width;
- patternContext.fillStyle = 'transparent';
- patternContext.strokeStyle = 'rgba(179, 153, 102, 1)';
- patternContext.lineWidth = Math.max(1, width/2);
- patternContext.beginPath();
- patternContext.moveTo(0, 0);
- patternContext.lineTo(patternCanvas.width, 0);
- patternContext.stroke();
-
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- return new Style({
- zIndex: 6,
- fill: new Fill({
- color: context.createPattern(patternCanvas, 'repeat'),
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [179, 153, 102, 1],
- }),
- });
- }),
- },
- 'ren.omr_riks': {
- legend: { zoomLevel: 2 },
- style: [.5, .5, 1, 1, 1, 1.5, 1.5, 1.5, 2, 2, 2, 2].map(function(width, z) {
- return new Style({
- zIndex: 5,
- fill: new Fill({
- color: [203, 190, 163, Math.max((.3-.5)/8 * z + .5, 0)],
- }),
- stroke: width === 0 ? undefined : new Stroke({
- width: width,
- color: [179, 153, 102, 1],
- }),
- });
- }),
- },
-
- /* Documentation at
- * https://www.smhi.se/polopoly_fs/1.34541!/dammprod%202013_3%2C%20beskrivning%2C%20SVAR2012_2.pdf
- * */
- 'misc.dammar': {
- legend: { zoomLevel: 5, type: 'point' },
- style: [2, 3, 4, 4, 4, 6, 8, 8, 8, 10, 16, 32].map(function(width) {
- return new Style({
- zIndex: 59,
- image: new CircleStyle({
- radius: width,
- fill: new Fill({
- color: 'rgb(219, 30, 42)',
- }),
- stroke: new Stroke({
- width: Math.log2(width) * 2/5,
- color: 'rgb(128, 17, 25)',
- }),
- }),
- });
- }),
- },
-
- 'misc.gigafactories': {
- legend: { zoomLevel: 1, type: 'point' },
- style: [4, 6, 7, 8, 10, 12].map(function(width) {
- return new Style({
- zIndex: 60,
- image: new CircleStyle({
- radius: width,
- fill: new Fill({
- color: 'rgb(152, 78, 163)',
- }),
- stroke: new Stroke({
- width: Math.log2(width) * 2/5,
- color: 'rgb(119, 61, 128)',
- }),
- }),
- });
- })
- .concat([1.5, 2, 2, 2, 2, 2].map(function(width) {
- return new Style({
- zIndex: 58,
- fill: new Fill({
- color: 'rgba(152, 78, 163, .4)',
- }),
- stroke: new Stroke({
- width: width,
- color: 'rgb(119, 61, 128)',
- }),
- });
- })),
- },
-
- '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) */
-};
diff --git a/src/map.js b/src/map.js
deleted file mode 100644
index 5a751e6..0000000
--- a/src/map.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/***********************************************************************
- * Copyright © 2024-2025 Guilhem Moulin <info@guilhem.se>
- * Map base setup
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- **********************************************************************/
-
-import Map from 'ol/Map.js';
-import View from 'ol/View.js';
-import TileLayer from 'ol/layer/Tile.js';
-
-import WMTS from 'ol/source/WMTS.js';
-import WMTSTileGrid from 'ol/tilegrid/WMTS.js';
-
-import proj4 from 'proj4';
-import { get as getProjection } from 'ol/proj.js';
-import { register as registerProjection } from 'ol/proj/proj4.js';
-
-proj4.defs('EPSG:3006', '+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs');
-registerProjection(proj4);
-
-export const projection = getProjection('EPSG:3006');
-
-/* Lantmäteriet uses a tile-scheme where the origin (upper-left corner) is at
- * N8500000 E-1200000 (SWEREF99 TM), where each tile is 256×256 pixels, and where
- * the resolution at level 0 is 4096m per pixel (each side is 1048.576km long).
- *
- * https://www.lantmateriet.se/globalassets/geodata/geodatatjanster/tb_twk_visning_cache_v1.1.0.pdf
- * https://www.lantmateriet.se/globalassets/geodata/geodatatjanster/tb_twk_visning-oversiktlig_v1.0.3.pdf
- *
- * We set the extent to a 4×4 tiles square at level 2 (1024px = 1048.576km per
- * side) somehow centered on Norrbotten and Västerbotten, and zoom in from there.
- * This represent a TILEROW (x) offset of 5, and a TILECOL (y) offset of 2.
- */
-export const extent = [110720, 6927136, 1159296, 7975712];
-
-/* XXX using the topowebbcache WMTS is fine for testing (as it doesn't require
- * authentication) but not in production in a public instance as doing so would
- * violate its current terms of use (as of January 2024 it's not CC0 open data).
- * See
- *
- * https://www.lantmateriet.se/sv/om-lantmateriet/Rattsinformation/upphovsratt-och-publicering-av-lantmateriets-geografiska-information/
- * https://www.lantmateriet.se/sv/kartor/vara-karttjanster/min-karta/#anchor-2
- * https://help.locusmap.eu/topic/support-for-swedish-lantmateriets-min-karta-wms
- *
- * More precise background maps might be available in the future as open data,
- * though:
- *
- * https://www.lantmateriet.se/sv/om-lantmateriet/press/nyheter/lantmateriets-arbete-mot-oppna-data-i-full-gang/
- * https://ext-geodatakatalog.lansstyrelsen.se/GeodataKatalogen/srv/swe/catalog.search#/map uses
- * https://api.lantmateriet.se/open/topowebb-ccby/v1/wmts/token/3c3a9cf47e7cb5ea24542d40d19698/?layer=topowebb&style=default&tilematrixset=3006&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image/png&TileMatrix=7&TileCol=237&TileRow=155
- */
-export const baseMapSource = new WMTS({
- url: undefined,
- version: '1.0.0',
- style: 'default',
- matrixSet: '3006',
- format: 'image/png',
- tileGrid: new WMTSTileGrid({
- extent: extent,
- // https://www.lantmateriet.se/globalassets/geodata/geodatatjanster/tb_twk_visning-oversiktlig_v1.0.3.pdf
- tileSize: 256,
- origin: [-1200000, 8500000],
- resolutions: [4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8],
- matrixIds: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
- }),
- projection: projection,
- wrapX: false,
- crossOrigin: 'anonymous',
-});
-
-const view = new View({
- projection: projection,
- extent: extent,
- showFullExtent: true,
- /* center of the bbox of the Norrbotten and Västerbotten geometries */
- center: [694767.48, 7338176.57],
- zoom: 1,
- enableRotation: false,
- resolutions: [1024, 512, 256, 128, 64, 32, 16, 8],
- constrainResolution: false,
-});
-
-export const map = new Map({
- controls: [],
- view: view,
- layers: [
- new TileLayer({
- source: baseMapSource
- }),
- ],
-});
diff --git a/src/popover.js b/src/popover.js
deleted file mode 100644
index 7080711..0000000
--- a/src/popover.js
+++ /dev/null
@@ -1,1348 +0,0 @@
-/***********************************************************************
- * Copyright © 2024-2025 Guilhem Moulin <info@guilhem.se>
- * Popup and feature overlays
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- **********************************************************************/
-
-import Overlay from 'ol/Overlay.js';
-import Stroke from 'ol/style/Stroke.js';
-import Style from 'ol/style/Style.js';
-import VectorTileLayer from 'ol/layer/VectorTile.js';
-
-import { Popover } from 'bootstrap';
-
-import { map } from './map.js';
-
-/* return an <a> tag with the given URL and optional text */
-const reURL = new RegExp('^https?://', 'i');
-const formatLink = function(url, text) {
- if (url == null || typeof url !== 'string' || !reURL.test(url)) {
- return url;
- }
- const a = document.createElement('a');
- a.href = url;
- a.target = '_blank';
- if (text != null && text !== '') {
- const t = document.createTextNode(text + ' ');
- a.appendChild(t);
- }
- const i = document.createElement('i');
- i.classList.add('bi', 'bi-box-arrow-up-right');
- a.appendChild(i);
- return a;
-};
-
-/* test a condition on the field maps */
-const condField = function(cond, k) {
- if (Array.isArray(cond)) {
- return cond.includes(k);
- }
- if (cond instanceof RegExp) {
- return cond.test(k);
- }
- return cond(k);
-};
-/* filter fields by condition */
-const filterFields = function(k, fields) {
- return fields.map(function(v) {
- if (v.cond == null || condField(v.cond, k)) {
- return v;
- }
- }).filter((f) => f != null);
-};
-/* filter fields using a pre-built map */
-const mapFields = function(k, fieldMap, fields) {
- if (fields === undefined) {
- return fieldMap.map((v) => k[v]);
- }
- return fields.map(function(v) {
- if (!Array.isArray(v)) {
- return fieldMap[v];
- } else if (condField(v[1], k)) {
- return fieldMap[v[0]];
- }
- }).filter((f) => f !== undefined);
-};
-/* pre-build the field map so we don't need to duplicate objects accross layers */
-const mkFieldMap = function(fieldMap) {
- return Object.fromEntries(Object.entries(fieldMap).map(function([k, o]) {
- if (typeof o === 'string') {
- return [k, {key: k, desc: o}];
- } else {
- return [k, Object.assign(o, {key: k})];
- }
- }));
-};
-
-const LAYERS = {
- svk: {
- ledningar: {
- title: 'Kraftledning (befintlig)',
- fields: [
- { key: 'Placement', desc: 'Förläggning' },
- { key: 'Voltage', desc: 'Spänning', unit: 'kV' },
- { key: 'geom_length', desc: 'Ledlängd', fn: 'length' },
- ],
- },
- transmissionsnatsprojekt: {
- title: 'Transmissionsnätsprojekt',
- fields: [
- { key: 'Name', desc: 'Projektnamn' },
- { key: 'Voltage', desc: 'Spänning', unit: 'kV' },
- { key: 'Url', desc: 'Länk', fn: formatLink },
- ],
- },
- },
-
- misc: {
- gigafactories: {
- title: 'Stor industrisatsning',
- fields: [
- { key: 'Name', desc: 'Namn' },
- { key: 'Url', desc: 'Länk', fn: formatLink },
- ],
- },
- dammar: {
- /* Documentation at
- * https://www.smhi.se/polopoly_fs/1.34541!/dammprod%202013_3%2C%20beskrivning%2C%20SVAR2012_2.pdf */
- title: 'Damm',
- fields: [
- { key: 'DNamn', desc: 'Dammenhetens namn' },
- { key: 'Namn', desc: 'Dammanläggningens namn' },
- { key: 'LST_OBJID', desc: 'Länsnr', classes: ['feature-objid'] },
- { key: 'Status', desc: 'Status', fn: (v) => v === 1 ? 'Befintlig damm' : v === 2 ? 'Fd. damm' : '' },
- //{ key: 'Regleringstyp', desc: 'Regleringstyp' },
- { key: 'ByggAr', desc: 'Byggår' },
- { key: 'DammHojd', desc: 'Dammhöjd', unit: 'm' },
- { key: 'KronLangd', desc: 'Krönlängd', unit: 'm' },
- { key: 'Fiskvag', desc: 'Fiskväg', fn: (v) =>
- v === 1 ? 'Bassängtrappa' :
- v === 2 ? 'Denilränna' :
- v === 3 ? 'Slitsränna' :
- v === 4 ? 'Omlöp' :
- v === 5 ? 'Inlöp' :
- v === 6 ? 'Ålledare' :
- v === 7 ? 'Smoltränna' :
- v === 8 ? 'Okänd typ' :
- v === 9 ? 'Ingen' :
- v === 10 ? 'Annan' :
- null },
- { key: 'HARO', desc: 'Huvudavrinningsområdesnummer', classes: ['feature-objid'] },
- { key: 'Vattendistrikt', desc: 'Vattendistrikt', classes: ['feature-objid'] },
- { key: 'Verksamhet', desc: 'Verksamhet', fn: (v) =>
- v === 1 ? 'Kraftproduktion' :
- v === 2 ? 'Industri' :
- v === 3 ? 'Sjöfart' :
- v === 4 ? 'Invallning' :
- v === 5 ? 'Vattenförsörjning' :
- v === 6 ? 'Spegeldamm' :
- v === 7 ? 'Historisk' :
- v === 8 ? 'Övrigt' :
- null },
- { key: 'DG', desc: 'Högsta dämningsgräns', unit: 'm' },
- { key: 'SG', desc: 'Lägsta sänkningsgräns', unit: 'm' },
- { key: 'MY', desc: 'Magasinsyta', unit: 'km²' },
- { key: 'RV', desc: 'Reglerbar volym', unit: 'Mm³' },
- { key: 'Kommentar', desc: 'Kommentar' },
- ],
- },
- },
-};
-
-
-LAYERS.mrr = {};
-(function() {
- const fields = [
- { key: 'name', desc: 'Namn' },
- { key: 'mineral', desc: 'Koncessionsmineral', cond: (i) => i < 6 },
- { key: 'owners', desc: 'Ägare', cond: [0,2,4] },
- { key: 'owners', desc: 'Sökande', cond: [1,3,5] },
- { key: 'conc_name', desc: 'Tillhörande bearbetnings\u00ADkoncession(er)', cond: [6] },
- { key: 'licenceid', desc: 'Tillståndsid', classes: ['feature-attr-mrr-license-id'], cond: [0,2,4,6] },
- { key: 'geom_area', desc: 'Areal', fn: 'area' },
- { key: 'validfrom', desc: 'Giltig från', cond: [0,2,4] },
- { key: 'validto', desc: 'Giltig till', cond: [0,2,4] },
- { key: 'diarynr', desc: 'Diarienummer', classes: ['feature-attr-dnr'] },
- { key: 'appl_date', desc: 'Ansökningsdatum' },
- { key: 'dec_date', desc: 'Beslutsdatum', cond: [0,2,4,6] },
- ];
- Object.entries({
- ec: 'Bearbetningskoncession',
- met: 'Undersökningstillstånd, metaller och industrimineral',
- ogd: 'Undersökningstillstånd, olja, gas och diamant',
- })
- .flatMap(([k, title]) => [
- /* don't use Object.entries() to guaranty ordering */
- ['appr', 'beviljad'], /* even index */
- ['appl', 'ansökt'], /* odd index */
- ].map(([a,b]) => [a + '_' + k, title + ' \u2013 ' + b]))
- .concat([['appr_dl', 'Markanvisning till koncession']]) /* index #6 */
- .forEach(([k, title], idx) => LAYERS.mrr[k] = { title, fields: filterFields(idx, fields) });
-})();
-
-
-LAYERS.vbk = {};
-(function() {
- const fieldMap = mkFieldMap({
- Projektnamn: 'Projektnamn',
- OmrID: { desc: 'Områdes-ID', classes: ['feature-objid'] },
- AntalVerk: 'Aktuella verk',
- AntalEjXY: 'Antal ej koordinatsatta verk',
- Projektstatus: 'Projektstatus',
- Diarienummer: 'Diarienummer',
- geom_area: { desc: 'Areal', fn: 'area' },
- Calprod: { desc: 'Beräknad årsproduktion', unit: 'GWh' },
- PlaneradByggstart: 'Planerad byggstart',
- PlaneratDrift: 'Planerat drifttagande',
- AndringsansokanPagar: 'Ändringsansökan pågår',
- UnderByggnation: 'Under byggnation',
- Organisationsnamn: 'Verksamhetsutövare',
- Organisationsnummer: { desc: 'Organisationsnummer', classes: ['feature-orgnr'] },
- SamradsunderlagInlamnat: 'Samrådsunderlag inlämnat',
- AnsokanInlamnat: 'Tillståndsansökan inlämnad',
- AnsokanAterkallad: 'Tillståndsansökan återkallad',
- AnsokanBeviljad: 'Tillståndsansökan beviljad',
- AnsokanAvslagen: 'Tillståndsansökan avslagen',
- AnsokanOverklagad: 'Överklagad',
- Natura2000_Ansokan: 'Natura2000 ansökan',
- Natura2000_Beslutdatum: 'Natura2000 beslutsdatum',
- Uppfort: 'Parken uppförd',
- PlaneratAntalVerkMin: 'Planerat antal verk (min)',
- PlaneratAntalVerkMax: 'Planerat antal verk (max)',
- PlaneradHojdMin: { desc: 'Panerad totalhöjd (min)', unit: 'm' },
- PlaneradHojdMax: { desc: 'Panerad totalhöjd (max)', unit: 'm' },
- PlaneradProduktionMin: { desc: 'Planerad årsproduktion (min)', unit: 'GWh' },
- PlaneradProduktionMax: { desc: 'Planerad årsproduktion (max)', unit: 'GWh' },
- BeviljatAntalVerk: 'Beviljat antal verk',
- UppfortAntalVerk: 'Uppfört antal verk',
- BeviljadMaxhojd: { desc: 'Beviljad maxhöjd', unit: 'm' },
- InstalleradEffekt: { desc: 'Installerad effekt', unit: 'MW' },
- ElNamn: 'Elområde',
- SenasteUppdaterat: 'Senast uppdaterat',
- });
-
- Object.entries({
- current: null,
- notcurrent: ' \u2013 ej aktuell',
- })
- .forEach(([k, title]) => LAYERS.vbk['area_' + k] = {
- title: 'Landbaserad projekteringsområde för vindkraft' + (title ?? ''),
- fields: mapFields(k, fieldMap, [
- 'Projektnamn',
- 'OmrID',
- 'AntalVerk',
- 'AntalEjXY',
- 'geom_area',
- 'Calprod',
- 'PlaneradByggstart',
- 'PlaneratDrift',
- 'AndringsansokanPagar',
- ['UnderByggnation', ['current']],
- 'Organisationsnamn',
- 'Organisationsnummer',
- 'ElNamn',
- 'SenasteUppdaterat',
- ]),
- });
-
- [
- ['completed', /* 0 */ 'uppförd'],
- ['approved', /* 1 */ 'tillståndsansökan beviljad'],
- ['amended', /* 2 */ 'ändringsansökan'],
- ['rejected', /* 3 */ 'tillståndsansökan avslagen'],
- ['appealed', /* 4 */ 'överklagad'],
- ['applied', /* 5 */ 'tillståndsansökan inlämnad'],
- ['consultation', /* 6 */ 'samråd inför tillståndsansökan'],
- ['investigation', /* 7 */ 'inledande undersökningar'],
- ['revoked', /* 8 */ 'inte aktuell eller återkallad'],
- ]
- .forEach(([k, title], idx) => LAYERS.vbk['offshore_' + k] = {
- title: 'Havsbaserad vindkraft \u2013 ' + title,
- fields: mapFields(idx, fieldMap, [
- 'Projektnamn',
- 'OmrID',
- 'Organisationsnamn',
- 'Organisationsnummer',
- 'Projektstatus',
- 'Diarienummer',
- ['AndringsansokanPagar', [1,2,4]],
- 'geom_area',
- ['SamradsunderlagInlamnat', (i) => i <= 6 || i === 8],
- ['AnsokanInlamnat', (i) => i <= 5 || i === 8],
- ['AnsokanAterkallad', [8]],
- ['AnsokanBeviljad', [0,1,4,8]],
- ['AnsokanAvslagen', [3,8]],
- ['AnsokanOverklagad', [0,1,3,4,8]],
- ['Natura2000_Ansokan', (i) => i !== 2],
- ['Natura2000_Beslutdatum', (i) => i !== 2],
- ['UnderByggnation', [1]],
- ['PlaneratAntalVerkMin', (i) => i > 0],
- ['PlaneratAntalVerkMax', (i) => i > 0],
- ['PlaneradHojdMin', (i) => i > 0],
- ['PlaneradHojdMax', (i) => i > 0],
- ['PlaneradProduktionMin', (i) => i > 0],
- ['PlaneradProduktionMax', (i) => i > 0],
- ['PlaneradByggstart', (i) => i > 0],
- ['Uppfort', [0,8]],
- ['PlaneratDrift', (i) => i > 0],
- ['BeviljatAntalVerk', [0,1,4,8]],
- ['UppfortAntalVerk', [0,8]],
- ['BeviljadMaxhojd', [0,1,4,8]],
- ['InstalleradEffekt', [0]],
- ['Calprod', [0]],
- 'ElNamn',
- 'SenasteUppdaterat',
- ]),
- });
-
- Object.assign(fieldMap, mkFieldMap({
- VerkID: { desc: 'Verk-ID', classes: ['feature-objid'] },
- Status: 'Status',
- Handlingstyp: 'Handlingstyp',
- MB_Tillstand: 'Miljöbalken tillstånd tidsbegränsning',
- Uppfort: 'Uppförandedatum',/* override previous def */
- Totalhojd: { desc: 'Totalhöjd', unit: 'm' },
- Navhojd: { desc: 'Navhöjd', unit: 'm' },
- Rotordiameter: { desc: 'Rotordiameter', unit: 'm' },
- Maxeffekt: { desc: 'Maxeffekt', unit: 'MW' },
- Fabrikat: 'Fabrikat',
- Modell: 'Modell',
- Placering: 'Placering',
- }));
-
- [
- ['completed', /* 0 */ 'uppfört'],
- ['approved', /* 1 */ 'beviljat'],
- ['rejected', /* 2 */ 'avslagit/nekat'],
- ['processed', /* 3 */ 'handlagt'],
- ['dismounted', /* 4 */ 'nedmonterat'],
- ['appealed', /* 5 */ 'överklagat'],
- ['revoked', /* 6 */ 'inte aktuell eller återkallad'],
- ]
- .forEach(([k, title], idx) => LAYERS.vbk['station_' + k] = {
- title: 'Landbaserad vindkraftverk \u2013 ' + title,
- fields: mapFields(idx, fieldMap, [
- 'VerkID',
- 'OmrID',
- 'Projektnamn',
- 'Status',
- 'Handlingstyp',
- ['Uppfort', [0,4,6]],
- 'MB_Tillstand',
- 'Totalhojd',
- 'Navhojd',
- 'Rotordiameter',
- 'Maxeffekt',
- 'Calprod',
- 'Fabrikat',
- 'Modell',
- 'Organisationsnamn',
- 'Organisationsnummer',
- 'Placering',
- 'ElNamn',
- 'SenasteUppdaterat',
- ]),
- });
-})();
-
-
-LAYERS.avverk = {};
-(function() {
- const zeroIsNull = (v) => v > 0 ? v : null;
- const fieldMap = mkFieldMap({
- /* Documentation at
- * https://www.skogsstyrelsen.se/globalassets/sjalvservice/karttjanster/geodatatjanster/produktbeskrivningar/utforda-avverkningar---produktbeskrivning.pdf
- * and
- * https://www.skogsstyrelsen.se/globalassets/sjalvservice/karttjanster/geodatatjanster/produktbeskrivningar/yttre-granser-for-avverkningsanmalda-omraden---produktbeskrivning.pdf
- */
- Beteckn: { desc: 'Ärendebeteckning', classes: ['feature-objid'] },
- ArendeAr: 'Registeringsår',
- Inkomdatum: 'Inkom datum',
- Skogstyp: 'Skogstyp',
- Avvdatum: 'Datum för avverkning',
- KallaDatum: 'Ursprung för datum för avverkning',
- AnmaldHa: { desc: 'Areal anmält', unit: 'ha' },
- NatforHa: { desc: 'Areal naturlig föryngring', unit: 'ha', fn: zeroIsNull },
- SkogsodlHa: { desc: 'Areal plantering', unit: 'ha', fn: zeroIsNull },
- AvvSasong: 'Avverkningssäsong',
- Avverktyp: 'Avverkningstyp',
- ArendeStatus: 'Ärendestatus',
- AvvHa: { desc: 'Avverkad areal', unit: 'ha' },
- geom_area: { desc: 'Areal för ytan', fn: 'area' },
- });
-
- LAYERS.avverk.utford = {
- title: 'Utförd avverkning',
- fields: mapFields(fieldMap, [
- 'Beteckn',
- 'ArendeAr',
- 'Skogstyp',
- 'AnmaldHa',
- 'NatforHa',
- 'Avverktyp',
- 'Avvdatum',
- 'KallaDatum',
- 'geom_area',
- ]),
- };
-
- LAYERS.avverk.anmald = {
- title: 'Avverkningsanmälansområde',
- fields: mapFields(fieldMap, [
- 'Beteckn',
- 'Inkomdatum',
- 'ArendeAr',
- 'AnmaldHa',
- 'NatforHa',
- 'SkogsodlHa',
- 'AvvSasong',
- 'ArendeStatus',
- 'AvvHa',
- ]),
- };
-})();
-
-
-LAYERS.skydd = {};
-(function() {
- const fieldMap = mkFieldMap({
- NVRID: { desc: 'NVR-ID', classes: ['feature-objid'] },
- FORSKRNAMN: 'Föreskriftsområde',
- OBJEKTNAMN: 'Namn',
- NAMN: 'Namn',
- BESLSTAT: 'Beslutsstatus',
- FORESKRTYP: 'Föreskriftstyp',
- FORESKRIFT: 'Föreskriftssubtyp',
- FRANDATUM: 'Från datum',
- TILLDATUM: 'Till datum',
- BESKRIVN: 'Beskrivning',
- geom_area: { desc: 'Areal', fn: 'area' },
-
- SKYDDSTYP: 'Skyddstyp',
- BESLSTATUS: 'Beslutsstatus',
- URSBESLDAT: 'Beslutsdatum (bildande)',
- URSGALLDAT: 'Ursprungligt gällandedatum',
- SENGALLDAT: 'Senaste gällandedatum',
- FORVALTARE: 'Förvaltare',
- IUCNKAT: 'IUCN-kategori',
- DIARIENR: { desc: 'Diarienummer', classes: ['feature-attr-dnr'] },
- LAGRUM: 'Lagrum',
- BESLMYND: 'Beslutsmyndighet',
- LAND_HA: { desc: 'Areal land', unit: 'ha' },
- VATTEN_HA: { desc: 'Areal vatten', unit: 'ha' },
- SKOG_HA: { desc: 'Skogsmarksareal', unit: 'ha' },
-
- IKRAFTDATF: 'Ikraftträdandedatum föreskrifter',
- TILLSYNSMH: 'Tillsynsmyndighet',
- PROVNMHTIL: 'Prövningsmyndighet tillstånd',
- PROVNMHDIS: 'Prövningsmyndighet dispens',
-
- NAME: 'Namn',
- RAMSAR_ID: { desc: 'Ramsar-ID', classes: ['feature-objid'] },
- LEGAL_ACT: 'Rättsakt',
- URSPR_BESL: 'Ursprungligt beslutsdatum',
- SEN_BESLUT: 'Senaste beslutsdatum',
- LINK: { desc: 'Länk', fn: formatLink },
- });
-
- LAYERS.skydd.tilltradesforbud = {
- title: 'Tillträdesförbud',
- fields: mapFields(fieldMap, [
- 'NVRID',
- 'FORSKRNAMN',
- 'OBJEKTNAMN',
- 'BESLSTAT',
- 'FORESKRTYP',
- 'FORESKRIFT',
- 'FRANDATUM',
- 'TILLDATUM',
- 'BESKRIVN',
- 'geom_area',
- ]),
- };
-
- /* Nationella skyddsformer från Naturvårdsregistret */
- const isSurface = (k) => !/_punkt$/.test(k);
- Object.entries({
- nationalpark: 'Nationalpark',
- naturreservat: 'Naturreservat',
- naturreservat_kommunalt: 'Kommunalt naturreservat',
- naturvardsomrade: 'Naturvårdsområde',
- djur_och_vaxtskyddsomrade: 'Djur- och växtskyddsområde',
- kulturreservat: 'Kulturreservat',
- vattenskyddsomrade: 'Vattenskyddsområden',
- landskapsbildsskyddsomrade: 'Landskapsbildsskyddsområde',
- ovrigt_biotopskyddsomrade: 'Biotopskydd utanför skogsmark',
- naturminne_yta: 'Naturminne (yta)',
- naturminne_punkt: 'Naturminne (punkt)',
- interimistiskt_forbud: 'Interimistiskt förbud',
- })
- .forEach(([k, title]) => LAYERS.skydd[k] = {
- title: title,
- fields: mapFields(k, fieldMap, [
- 'NVRID',
- 'NAMN',
- 'SKYDDSTYP',
- 'BESLSTATUS',
- 'URSBESLDAT',
- ['URSGALLDAT', (k) => k !== 'vattenskyddsomrade'],
- ['SENGALLDAT', (k) => k !== 'vattenskyddsomrade'],
- ['FORVALTARE', (k) => k !== 'vattenskyddsomrade'],
- ['IKRAFTDATF', (k) => k === 'vattenskyddsomrade'],
- 'IUCNKAT',
- 'DIARIENR',
- 'LAGRUM',
- 'BESLMYND',
- ['TILLSYNSMH', (k) => k === 'vattenskyddsomrade'],
- ['PROVNMHTIL', (k) => k === 'vattenskyddsomrade'],
- ['PROVNMHDIS', (k) => k === 'vattenskyddsomrade'],
- ['geom_area', isSurface],
- ['LAND_HA', isSurface],
- ['VATTEN_HA', isSurface],
- ['SKOG_HA', isSurface],
- ]),
- });
-
- /* Natura 2000-områden */
- (function() {
- const fields = [
- { key: 'SITE_CODE', desc: 'Områdeskod', classes: ['feature-objid'] },
- { key: 'NAMN', desc: 'Namn' },
- { key: 'OMRADESTYP', desc: 'Områdestyp' },
- { key: 'UPPLAMNARE', desc: 'Uppgiftslämnare' },
- { key: 'SPA_DATUM', desc: 'SPA-datum' },
- { key: 'SCI_FORSL', desc: 'SCI-förslagsdatum' },
- { key: 'SCI_DATUM', desc: 'SCI-datum' },
- { key: 'SAC_DATUM', desc: 'SAC-datum' },
- fieldMap.geom_area,
- { key: 'KVALITET', desc: 'Kvalitet' },
- { key: 'KARAKTAR', desc: 'Kännetecken för området' },
- { key: 'ARTER', desc: 'Arter' },
- { key: 'NATURTYPER', desc: 'Naturtyper' },
- { key: 'BEVPLAN', desc: 'Bevarandeplan', fn: formatLink },
- ];
- Object.entries({
- fageldirektivet: 'Fågeldirektivet (SPA)',
- habitatdirektivet: 'Art- och habitatdirektivet (SCI)',
- })
- .forEach(([k, title]) => LAYERS.skydd[k] = { title, fields });
- })();
-
- /* Områden med internationell status */
- LAYERS.skydd.helcom = {
- title: 'Marina skyddade områden (Helcom MPA)',
- fields: mapFields(fieldMap, [ 'NAME', 'geom_area' ]),
- };
-
- LAYERS.skydd.ramsar = {
- title: 'Ramsar-områden (Våtmarkskonventionen)',
- fields: mapFields(fieldMap, [
- 'RAMSAR_ID',
- 'SKYDDSTYP',
- 'NAMN',
- 'geom_area',
- 'LAND_HA',
- 'VATTEN_HA',
- 'SKOG_HA',
- 'URSPR_BESL',
- 'SEN_BESLUT',
- 'LEGAL_ACT',
- 'LINK',
- ]),
- };
-
- LAYERS.skydd.ospar = {
- title: 'Marina skyddade områden (Ospar MPA)',
- fields: [
- { key: 'ORIGIN', desc: 'Ursprung' },
- { key: 'NAMN_N2000', desc: 'N2000-namn' },
- { key: 'MPA_ID', desc: 'MPA-ID', classes: ['feature-objid'] },
- { key: 'MPA_NAMN', desc: 'MPA-namn' },
- { key: 'N2000_SITE', desc: 'N2000-ID', classes: ['feature-objid'] },
- fieldMap.geom_area,
- ],
- };
-
- LAYERS.skydd.varldsarv = {
- title: 'Världsarv med mycket höga naturvärden (Unesco)',
- fields: mapFields(fieldMap, [ 'NAMN', 'geom_area' ]),
- };
-
- LAYERS.skydd.naturvardsavtal = {
- title: 'Naturvårdsavtal (Naturvårdsverket, Länsstyrelsen)',
- fields: [
- { key: 'ID', desc: 'ID', classes: ['feature-objid'] },
- { key: 'OBJNAMN', desc: 'Namn' },
- { key: 'FASTBET', desc: 'Fastighet', classes: ['feature-objid'] },
- { key: 'DATSTART', desc: 'Giltig från' },
- { key: 'DATSLUT', desc: 'Giltig till' },
- { key: 'DIARIENRNV', desc: 'Diarienummer', classes: ['feature-attr-dnr'] },
- { key: 'STATUS', desc: 'Satus' },
- fieldMap.geom_area,
- ],
- };
-})();
-
-(function() {
- const fieldMap = mkFieldMap({
- Beteckn: { desc: 'Ärendebeteckning', classes: ['feature-objid'] },
- Biotyp: { desc: 'Biotopkategori' },
- Naturtyp: { desc: 'Skogstyp' },
- ArendeAr: { desc: 'Registeringsår' },
- geom_area: { desc: 'Areal', fn: 'area' },
- AreaProd: { desc: 'Skogsmarksareal', unit: 'ha' },
- Datbeslut: { desc: 'Beslutsdatum' },
- Url: { desc: 'Länk', fn: (v) => formatLink(v, 'Skogens Pärlor') },
- NvaTyp: 'Biotopkategori',
- DatAvtal: 'Avtalsdatum',
- Undertyp: 'Undertyp',
- AvtalatDatum: 'Avtalat datum',
- Objnamn: 'Objektnamn',
- Datinv: 'Datum för fältinventering',
- });
-
- LAYERS.skydd.skogligt_biotopskyddsomrade = {
- title: 'Biotopskydd i skogsmark',
- fields: mapFields(fieldMap, [
- 'Beteckn',
- 'Biotyp',
- 'Naturtyp',
- 'ArendeAr',
- 'geom_area',
- 'AreaProd',
- 'Datbeslut',
- 'Url',
- ]),
- };
- LAYERS.skydd.naturvardsavtal_skogsstyrelsen = {
- title: 'Naturvårdsavtal (Skogsstyrelsen)',
- fields: mapFields(fieldMap, [
- 'Beteckn',
- 'ArendeAr',
- 'NvaTyp',
- 'Naturtyp',
- 'DatAvtal',
- 'geom_area',
- 'AreaProd',
- 'Url',
- 'Undertyp',
- ]),
- };
-
- LAYERS.skydd.atervatningsavtal = {
- title: 'Återvätningsavtal',
- fields: mapFields(fieldMap, [
- 'Beteckn',
- 'ArendeAr',
- 'AvtalatDatum',
- 'geom_area',
- 'Url',
- ]),
- };
-
- LAYERS.nv = {};
- Object.assign(fieldMap, mkFieldMap(Object.fromEntries(
- [1,2,3].map((i) => [`Biotop${i}`, `Biotoptyp #${i}`]).concat(
- [1,2,3,4,5,6,7,8].map((i) => [`Beskrivn${i}`, `Nyckelord #${i} som beskriver objektet`])
- ))));
- LAYERS.nv.naturvarde_sks = {
- title: 'Objekt med naturvärden (Skogsstyrelsen)',
- fields: mapFields(fieldMap, [
- 'Beteckn',
- 'Objnamn',
- 'Datinv',
- 'Biotop1', 'Biotop2', 'Biotop3',
- 'Beskrivn1', 'Beskrivn2', 'Beskrivn3',
- 'geom_area',
- 'Url',
- ]),
- };
- LAYERS.nv.nyckelbiotop = {
- title: 'Nyckelbiotop (Skogsstyrelsen)',
- fields: mapFields(fieldMap, [
- 'Beteckn',
- 'Objnamn',
- 'Datinv',
- 'Biotop1', 'Biotop2', 'Biotop3',
- 'Beskrivn1', 'Beskrivn2', 'Beskrivn3', 'Beskrivn4', 'Beskrivn5', 'Beskrivn6', 'Beskrivn7', 'Beskrivn8',
- 'geom_area',
- 'Url',
- ]),
- };
- LAYERS.nv.nyckelbiotop_storskogsbruk = {
- title: 'Nyckelbiotop (storskogsbruket)',
- fields: [
- { key: 'Org', desc: 'Uppgifter lämnade av' },
- { key: 'InkomDatum', desc: 'Inkom datum' },
- fieldMap.geom_area,
- fieldMap.Url,
- ],
- };
-
- LAYERS.nv.sumpskog = {
- title: 'Sumpskog',
- fields: [
- { key: 'Namn', desc: 'Objektnamn' },
- { key: 'Tradtext', desc: 'Skogstyp' },
- { key: 'Hydrtext', desc: 'Hydrologisk typ' },
- { key: 'Delklass', desc: 'Klass på delobjektet' },
- { key: 'Klassu', desc: 'Klass på objektet' },
- { key: 'Lovandel', desc: 'Andel löv' },
- { key: 'Andelva', desc: 'Andel öppet vatten' },
- { key: 'Krontakn', desc: 'Krontäckning' },
- { key: 'Huggklas', desc: 'Huggningsklass' },
- { key: 'Ingrepp', desc: 'Ingrepp på delobjekt (max 4)' },
- { key: 'Ingrpavv', desc: 'Grad av påverkan på delobjekt (max 4)' },
- { key: 'Objnyck', desc: 'Nyckelord på objektnivå' },
- { key: 'Delnyck', desc: 'Nyckelord på delobjektsnivå' },
- { key: 'Flygar', desc: 'Flygbildsår' },
- { key: 'Faltdat', desc: 'Datum för fältbesök' },
- { key: 'Invtekn', desc: 'Inventeringsteknik' },
- { key: 'Invdat', desc: 'Inventeringdatum' },
- { key: 'Ansvmynd', desc: 'Ansvarig myndighet' },
- fieldMap.geom_area,
- fieldMap.Url,
- ],
- };
-})();
-
-LAYERS.nv.pagaende_naturreservatsbildning = {
- title: 'Pågående naturreservatsbildning',
- fields: [
- { key: 'NAMN', desc: 'Objektnamn' },
- /* XXX unclear what "GRANSJUST" means, just a guess */
- { key: 'GRANSJUST', desc: 'Senast justerat' },
- { key: 'geom_area', desc: 'Areal', fn: 'area' },
- ],
-};
-
-LAYERS.nv.snus = {
- title: 'Skyddsvärd statlig skog',
- fields: [
- { key: 'NAMN', desc: 'Objektnamn' },
- { key: 'AR', desc: 'År' },
- { key: 'NATURGEOGR', desc: 'Naturgeografisk region', classes: ['feature-objid'] },
- { key: 'OBJEKTKATE', desc: 'Objektskategori', classes: ['feature-objid'] },
- { key: 'MARKAGARE', desc: 'Markägare' },
- { key: 'VARDEKARNA', desc: 'Areal värdekärna', unit: 'ha' },
- { key: 'UTV_MARK', desc: 'Areal utvecklingsmark', unit: 'ha' },
- { key: 'TOTAL_AREA', desc: 'Totalareal', unit: 'ha' },
- { key: 'LAND', desc: 'Areal land', unit: 'ha' },
- { key: 'VATTEN', desc: 'Areal vatten', unit: 'ha' },
- { key: 'PROD_SKOG', desc: 'Areal produktiv skogsmark', unit: 'ha' },
- { key: 'SKOG_O_FJG', desc: 'Areal produktiv skogsmark ovanför fjällnära gräns', unit: 'ha' },
- { key: 'SKOG_N_FJG', desc: 'Areal produktiv skogsmark nedanför fjällnära gräns', unit: 'ha' },
- { key: 'SKYDDSZON', desc: 'Areal skyddszon', unit: 'ha' },
- { key: 'ARRO_MARK', desc: 'Areal arronderingsmark', unit: 'ha' },
- { key: 'KRITERIER', desc: 'Kriterier för urval' },
- { key: 'BESKRIVN', desc: 'Beskrivning av området' },
- { key: 'LST_BEDOMN', desc: 'Länsstyrelsens bedömning' },
- { key: 'KALLOR', desc: 'Källor' },
- ],
-};
-
-LAYERS.ri = {};
-(function() {
- const fieldMap = mkFieldMap({
- NAMN: 'Namn',
- SKYDD: 'Skydd',
- AMNESOMRAD: 'Ämnesområde',
- AMNESOMR: 'Ämnesområde',
- OMRADESNR: { desc: 'Områdesnummer', classes: ['feature-objid'] },
- BESKRIVNIN: { desc: 'Beskrivning', fn: formatLink },
- LANK_VARDE: { desc: 'Länk värdebeskrivning', fn: formatLink },
- LAGRUM: 'Lagrum',
- BESLUTSDAT: 'Beslutsdatum',
- BESLDATUM: 'Beslutsdatum',
- ARENDENR: { desc: 'Ärendenummer', classes: ['feature-attr-dnr'] },
- LANK_BESLU: { desc: 'Länk beslut', fn: formatLink },
- AKTIVITET: 'Aktivitet',
- NATURTYP: 'Naturtyp',
- ORGINALID: { desc: 'Original-ID', classes: ['feature-objid'] },
- RIKSID: { desc: 'Riks-ID', classes: ['feature-objid'] },
- geom_area: { desc: 'Areal', fn: 'area' },
- AREA_LAND_: { desc: 'Areal land', unit: 'ha' },
- AREA_VATTE: { desc: 'Areal vatten', unit: 'ha' },
- });
- LAYERS.ri.naturvard = {
- title: 'Riksintresse naturvård',
- fields: mapFields(fieldMap, [
- 'NAMN',
- 'SKYDD',
- 'AMNESOMRAD',
- 'BESKRIVNIN',
- 'LAGRUM',
- 'BESLUTSDAT',
- 'ORGINALID',
- 'RIKSID',
- 'geom_area',
- ]),
- };
- LAYERS.ri.friluftsliv = {
- title: 'Riksintresse friluftsliv',
- fields: mapFields(fieldMap, [
- 'NAMN',
- 'SKYDD',
- 'AMNESOMR',
- 'OMRADESNR',
- 'LANK_VARDE',
- 'LAGRUM',
- 'BESLDATUM',
- 'ARENDENR',
- 'LANK_BESLU',
- 'AKTIVITET',
- 'NATURTYP',
- 'geom_area',
- 'AREA_LAND_',
- 'AREA_VATTE',
- ]),
- };
-
- Object.assign(fieldMap, mkFieldMap({
- METODBESKR: 'Metodbeskrivning',
- TILLKDATUM: 'Tillkomstdatum',
- REVDATUM: 'Revisionsdatum',
- ANM: 'Anmärkning',
- OBJEKTLANK: { desc: 'Objektlänk', fn: formatLink },
- REFERENS: 'Referens',
- OBJTYP: 'Objekttyp',
- ORIGINALID: fieldMap.ORGINALID,
- DIG_SKALA: { desc: 'Digitaliseringsskala', fn: (v) => v > 0 ? v : null },
- }));
- [
- ['rorligt_friluftsliv', /* 0 */ 'rörligt friluftsliv (MB 4 kap 1§ och 2§)'],
- ['obruten_kust', /* 1 */ 'obruten kust (MB 4 kap 3§)'],
- ['obrutet_fjall', /* 2 */ 'obrutet fjäll (MB 4 kap 5§)'],
- ['skyddade_vattendrag', /* 3 */ 'skyddade vattendrag (MB 4 kap 6§)'],
- ]
- .forEach(([k, title], idx) => LAYERS.ri[k] = {
- title: 'Riksintresse ' + title,
- fields: mapFields(idx, fieldMap, [
- 'NAMN',
- 'BESKRIVNIN',
- 'METODBESKR',
- 'TILLKDATUM',
- 'REVDATUM',
- ['OBJTYP', [1]],
- ['ANM', [0,1,3]],
- ['DIG_SKALA', [3]],
- 'OBJEKTLANK',
- 'geom_area',
- 'ORIGINALID',
- 'REFERENS',
- ]),
- });
-})();
-
-
-LAYERS.ren = {
- betesomrade: {
- title: 'Samebyarnas betesområde',
- fields: [
- { key: 'NAMN', desc: 'Sameby' },
- { key: 'SAMEBY_TYP', desc: 'Samebys typ' },
- { key: 'SIGNATUR', desc: 'Signatur' },
- { key: 'AKTUALITET', desc: 'Aktualitet' },
- { key: 'geom_area', desc: 'Areal', fn: 'area' },
- ],
- },
- flyttled: {
- title: 'Samebyarnas markanvändningsredovisning \u2013 flyttled',
- fields: [
- { key: 'LED_ID', desc: 'Led-ID', classes: ['feature-objid'], fn: (v) => v > 0 ? v : null },
- { key: 'SAMEBY1', desc: 'Sameby #1' },
- { key: 'SAMEBY2', desc: 'Sameby #2' },
- { key: 'SAMEBY3', desc: 'Sameby #3' },
- { key: 'BESKRIVNIN', desc: 'Beskrivning' },
- { key: 'ARSTID', desc: 'Årstid' },
- { key: 'RIKSINTR', desc: 'Riksintresse' },
- { key: 'FAST_LED', desc: 'Fast led' },
- { key: 'AKTUALITET', desc: 'Aktualitet' },
- { key: 'SIGNATUR', desc: 'Signatur' },
- { key: 'geom_length', desc: 'Ledlängd', fn: 'length' },
- ],
- },
- riks_ren: {
- title: 'Riksintresse rennäring',
- fields: [
- { key: 'LAGRUM', desc: 'Lagrum' },
- { key: 'AKTUALITET', desc: 'Aktualitet' },
- { key: 'SIGNATUR', desc: 'Signatur' },
- { key: 'geom_area', desc: 'Areal', fn: 'area' },
- ],
- },
- omr_riks: {
- title: '(Kärn)områden av riksintresse rennäring',
- fields: [
- { key: 'OMR_NR', desc: 'Områdes-ID', classes: ['feature-objid'] },
- { key: 'LANK', desc: 'Länk' },
- { key: 'ARET_RUNT', desc: 'Årets runt' },
- { key: 'SAMEBY', desc: 'Sameby' },
- { key: 'ANSVARIG', desc: 'Ansvarig' },
- { key: 'AKTUALITET', desc: 'Aktualitet' },
- { key: 'SIGNATUR', desc: 'Signatur' },
- { key: 'geom_area', desc: 'Areal', fn: 'area' },
- ],
- },
-};
-
-
-
-/* format value to HTML */
-const formatValue = function(value, options) {
- let unit = options?.unit;
- if (options?.fn == null) {
- /* no-op */
- } else if (typeof options.fn === 'function') {
- value = options.fn(value);
- } else if (options.fn === 'length' && typeof value === 'number' && unit == null) {
- if (value < 1000) {
- unit = 'm';
- } else {
- value /= 1000;
- value = Math.round(value*100) / 100;
- unit = 'km';
- }
- } else if (options.fn === 'area' && typeof value === 'number' && unit == null) {
- if (value < 10000) {
- unit = 'm²';
- } else if (value < 10000 * 10000) {
- value /= 10000;
- unit = 'ha';
- } else {
- value /= 1000000;
- unit = 'km²';
- }
- value = Math.round(value*100) / 100;
- }
- if (value == null) {
- return null;
- }
- if (value instanceof HTMLElement) {
- return value;
- }
- switch (typeof value) {
- case 'boolean':
- return document.createTextNode(value ? 'Ja' : 'Nej');
- case 'string':
- return document.createTextNode(value);
- case 'number':
- if (unit != null) {
- return document.createTextNode(value.toLocaleString('sv-SE') + '\u202F' + unit);
- }
- return document.createTextNode(value.toString());
- default:
- return null;
- }
-};
-
-/* turn the properties into a fine <table> */
-const formatFeaturePropertiesToHTML = function(properties) {
- const table = document.createElement('table');
- table.classList.add('table', 'table-sm', 'table-borderless', 'table-hover');
-
- const tbody = document.createElement('tbody');
- table.appendChild(tbody);
-
- const def = LAYERS[properties.layer_group][properties.layer];
- def.fields.forEach(function(field) {
- const tr = document.createElement('tr');
- tbody.appendChild(tr);
-
- const th = document.createElement('th');
- th.setAttribute('scope', 'row');
- tr.appendChild(th);
- const textDesc = document.createTextNode(field.desc);
- th.appendChild(textDesc);
-
- const td = document.createElement('td');
- tr.appendChild(td);
- const v = formatValue(properties[field.key], field);
- if (v != null) {
- td.appendChild(v);
- }
- field.classes?.forEach?.((c) => td.classList.add(c));
- });
-
- const content = document.createElement('div');
- if (def.title != null) {
- const h = document.createElement('h6');
- content.appendChild(h);
- const textNode = document.createTextNode(def.title);
- h.appendChild(textNode);
- }
-
- content.appendChild(table);
- return content;
-};
-
-/* Initialize popup overlay with the give map and HTML element */
-let popupOverlay = null;
-(function() {
- popupOverlay = new Overlay({
- stopEvent: true,
- element: document.getElementById('popup'),
- });
- map.addOverlay(popupOverlay);
-})();
-
-let featureOverlayLayer = null;
-let overlayAttributes = [],
- overlayAttrIdx = 0,
- mapSources = {};
-/* Clear the highlighted feature list and make the overlay layer invisible */
-const disposeFeatureOverlay = function() {
- if (featureOverlayLayer?.getVisible?.()) {
- featureOverlayLayer.setVisible(false);
- featureOverlayLayer.changed();
- }
- /* clear the overlay list */
- overlayAttributes = [];
- overlayAttrIdx = 0;
- mapSources = {};
-}
-
-let popover = null;
-/* Clear overlay layer and dispose popover */
-export const disposePopover = function() {
- disposeFeatureOverlay();
- if (popover?.tip != null) {
- popover.dispose();
- }
-};
-
-/* Initialize popover on the given map */
-(function() {
- featureOverlayLayer = new VectorTileLayer({
- zIndex: 65535,
- declutter: false,
- visible: false,
- renderMode: 'vector',
- style: null,
- map: map,
- });
-
- const header = document.createElement('div');
- header.classList.add('d-flex');
-
- const headerGrabbingArea = document.createElement('div');
- headerGrabbingArea.classList.add('flex-grow-1', 'grabbing-area', 'pe-2', 'me-2');
- header.appendChild(headerGrabbingArea);
-
- const pageNode = document.createElement('h6');
- headerGrabbingArea.appendChild(pageNode);
-
- headerGrabbingArea.onmousedown = function(event) {
- /* move the popover around */
- if (event.button != 0) {
- return;
- }
- const popoverTip = popover.tip;
- if (popoverTip.classList.contains('popover-maximized')) {
- return;
- }
- headerGrabbingArea.classList.add('grabbing-area-grabbed');
-
- if (!popoverTip.classList.contains('popover-detached')) {
- /* detach popover tip */
- popoverTip.classList.add('popover-detached');
- const rect = popoverTip.getBoundingClientRect();
- const style = popoverTip.style;
- style.display = 'none'; /* avoid reflows between the following assignments */
- style.position = 'absolute';
- style.transform = '';
- style.inset = `${rect.top}px auto auto ${rect.left}px`;
- style.display = '';
- }
-
- let clientX = event.clientX, clientY = event.clientY;
- document.onmousemove = function(event) {
- const offsetX = clientX - event.clientX;
- const offsetY = clientY - event.clientY;
- clientX = event.clientX;
- clientY = event.clientY;
- popoverTip.style.top = (popoverTip.offsetTop - offsetY).toString() + 'px';
- popoverTip.style.left = (popoverTip.offsetLeft - offsetX).toString() + 'px';
- };
-
- document.onmouseup = function(event) {
- /* done moving around */
- if (event.button != 0) {
- return;
- }
- headerGrabbingArea.classList.remove('grabbing-area-grabbed');
- document.onmousemove = null;
- document.onmouseup = null;
- };
- };
-
- /* current number page and total page count */
- const pageNum = document.createElement('span');
- const pageCount = document.createElement('span');
- pageNode.appendChild(document.createTextNode('Träff '));
- pageNode.appendChild(pageNum);
- pageNode.appendChild(document.createTextNode(' av '));
- pageNode.appendChild(pageCount);
-
- /* highlight a feature */
- const featureOverlayStyle = new Style({
- stroke: new Stroke({
- color: 'rgba(0, 255, 255, .8)',
- width: 3,
- }),
- });
- const highlightFeature = function(layer_group, layer, id) {
- const source = mapSources[layer_group];
- if (source == null) {
- return;
- }
- if (featureOverlayLayer.getSource() !== source) {
- /* console.log('Updating source for feature overlay layer'); */
- featureOverlayLayer.setVisible(false);
- featureOverlayLayer.setSource(source);
- }
- featureOverlayLayer.setStyle(function(feature) {
- if (feature.getId() === id && feature.getProperties().layer === layer) {
- return featureOverlayStyle;
- }
- });
- featureOverlayLayer.setVisible(true);
- featureOverlayLayer.changed();
- };
- /* highlight the feature at index overlayAttrIdx within the CGI reply list */
- const refreshPopover = function() {
- const attr = overlayAttributes[overlayAttrIdx];
- highlightFeature(attr.layer_group, attr.layer, attr.ogc_fid);
-
- pageNum.innerHTML = (overlayAttrIdx + 1).toString();
- const content = formatFeaturePropertiesToHTML(attr);
- popover.tip.getElementsByClassName('popover-body')[0].replaceChildren(content);
- };
- /* go back/forward in the overlayAttributes list */
- const onClickPageChange = function(event, offset) {
- const btn = event.target;
- if (btn.classList.contains('disabled') || popover?.tip == null) {
- return;
- }
- if (overlayAttrIdx + offset < 0 || overlayAttrIdx + offset > overlayAttributes.length - 1) {
- return; /* out of range */
- }
-
- overlayAttrIdx += offset;
- if (overlayAttrIdx < 1) {
- btnPrev.classList.add('disabled');
- } else {
- btnPrev.classList.remove('disabled');
- }
- if (overlayAttrIdx < overlayAttributes.length - 1) {
- btnNext.classList.remove('disabled');
- } else {
- btnNext.classList.add('disabled');
- }
-
- refreshPopover();
- setTimeout(function() { btn.blur() }, 100);
- };
-
- /* control buttons */
- const btnPrev = document.createElement('button');
- btnPrev.classList.add('popover-button', 'popover-button-prev');
- btnPrev.setAttribute('type', 'button');
- btnPrev.title = 'Föregående träff';
- btnPrev.setAttribute('aria-label', btnPrev.title);
- btnPrev.onclick = function(event) {
- return onClickPageChange(event, -1);
- };
-
- const btnNext = document.createElement('button');
- btnNext.classList.add('popover-button', 'popover-button-next');
- btnNext.setAttribute('type', 'button');
- btnNext.title = 'Nästa träff';
- btnNext.setAttribute('aria-label', btnNext.title);
- btnNext.onclick = function(event) {
- return onClickPageChange(event, +1);
- };
-
- const btnExpand = document.createElement('button');
- btnExpand.classList.add('popover-button', 'popover-button-expand');
- btnExpand.setAttribute('type', 'button');
- const btnExpandTitle = 'Förstora';
- const btnExpandTitle2 = 'Förminska';
- btnExpand.setAttribute('aria-label', btnExpand.title);
- btnExpand.onclick = function() { /* maximize or reduce the popover */
- if (popover?.tip == null) {
- return;
- }
- if (!popover.tip.classList.contains('popover-maximized')) {
- popover.tip.classList.add('popover-maximized');
- btnExpand.classList.replace('popover-button-expand', 'popover-button-reduce');
- btnExpand.title = btnExpandTitle2;
- btnExpand.setAttribute('aria-label', btnExpand.title);
- } else {
- popover.tip.classList.remove('popover-maximized');
- btnExpand.classList.replace('popover-button-reduce', 'popover-button-expand');
- btnExpand.title = btnExpandTitle;
- btnExpand.setAttribute('aria-label', btnExpand.title);
- }
- setTimeout(function() { btnExpand.blur() }, 100);
- };
-
- const btnClose = document.createElement('button');
- btnClose.classList.add('popover-button', 'popover-button-close');
- btnClose.setAttribute('type', 'button');
- btnClose.title = 'Stäng';
- btnClose.setAttribute('aria-label', btnClose.title);
- btnClose.onclick = disposePopover;
-
- header.appendChild(btnPrev);
- header.appendChild(btnNext);
- header.appendChild(btnExpand);
- header.appendChild(btnClose);
-
- const container0 = map.getViewport().getElementsByClassName('ol-overlaycontainer-stopevent')[0];
- map.on('singleclick', function(event) {
- disposeFeatureOverlay();
-
- /* dispose any pre-existing popover if not in detached mode */
- popover = Popover.getInstance(popupOverlay.element);
- if (popover?.tip != null && !popover.tip.classList.contains('popover-detached')) {
- popover.dispose();
- }
-
- const size = event.map.getSize();
- if (size[0] < 576 || size[1] < 576) {
- return; /* skip popover if the map is too small */
- }
-
- /* unclear how many feature we'll find, don't render prev/next buttons for now */
- pageNode.classList.add('d-none');
- btnPrev.classList.add('d-none', 'disabled');
- btnNext.classList.add('d-none', 'disabled');
-
- /* never start in maximized mode */
- if (popover?.tip != null) {
- popover.tip.classList.remove('popover-maximized');
- }
- btnExpand.classList.replace('popover-button-reduce', 'popover-button-expand');
- btnExpand.title = btnExpandTitle;
- btnExpand.setAttribute('aria-label', btnExpand.title);
-
- const fetch_body = [];
- event.map.forEachFeatureAtPixel(event.pixel, function(feature, layer) {
- const layerGroup = layer.get('layerGroup');
- const layerName = feature.getProperties().layer;
- mapSources[layerGroup] ??= layer.getSource();
- const def = layerName != null ? LAYERS[layerGroup][layerName] : null;
- if (def?.fields == null) {
- /* skip layers which didn't opt-in for popover */
- return false;
- }
- if (fetch_body.length === 0) {
- /* first feature in the list, mark cursor and detached popover as in-progress */
- document.body.classList.add('inprogress');
- popover?.tip?.classList?.add?.('inprogress');
- }
- fetch_body.push({
- layer_group: layerGroup,
- layer: layerName,
- fid: feature.getId() ?? -1,
- });
- if (fetch_body.length >= 100) {
- return true; /* enough matches already, stop detection here */
- }
- }, {
- hitTolerance: 5,
- checkWrapped: false,
- layerFilter: (lyr) => lyr.get('layerGroup') != null,
- });
-
- if (fetch_body.length === 0) {
- /* no feature at pixel (or only within layers which didn't opt-in for popover) */
- if (popover?.tip != null) {
- /* dispose pre-detached popover */
- popover.dispose();
- }
- return;
- }
-
- fetch('/q', {
- method: 'POST',
- body: JSON.stringify(fetch_body),
- headers: {
- 'Content-Type': 'application/json; charset=UTF-8',
- },
- })
- .then(function(resp) {
- if (resp.status === 200) {
- return resp.json();
- } else {
- throw new Error(`${resp.url} [${resp.status}]`);
- }
- })
- .then(function(data) {
- /* the data is received from the CGI in the order it was sent */
- /* TODO optimizations on the CGI would break the above assumption, so the
- * decoded JSON response would need to be reordered to match fetch_body */
- overlayAttributes = data;
- if (overlayAttributes.length === 0) {
- /* couldn't fetch any attribute for feature(s) at pixel */
- if (popover?.tip != null) {
- /* dispose pre-detached popover */
- popover.dispose();
- }
- return;
- }
-
- pageCount.innerHTML = overlayAttributes.length.toString();
- if (overlayAttributes.length >= 2) {
- /* render prev/pre buttons */
- btnNext.classList.remove('d-none', 'disabled');
- btnPrev.classList.remove('d-none');
- pageNode.classList.remove('d-none');
- }
- if (popover?.tip == null) {
- /* create a new popover (we're not already showing one in detached mode) */
- pageNum.innerHTML = (overlayAttrIdx + 1).toString();
- popupOverlay.setPosition(event.coordinate);
-
- const attr = overlayAttributes[0];
- highlightFeature(attr.layer_group, attr.layer, attr.ogc_fid);
- popover = new Popover(popupOverlay.element, {
- template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div>' +
- '<div class="popover-header"></div><div class="popover-body"></div></div>',
- title: header,
- content: formatFeaturePropertiesToHTML(attr),
- html: true,
- placement: 'right',
- fallbackPlacements: ['right', 'left', 'bottom', 'top'],
- container: container0,
- });
- popover.show();
- }
- else if (popover.tip.classList.contains('popover-detached')) {
- /* update existing detached mode popover */
- refreshPopover();
- popover.tip.classList.remove('inprogress');
- }
- })
- .catch(function(e) {
- console.log(e);
- })
- .finally(function() {
- /* remove in-progress marking on the cursor */
- document.body.classList.remove('inprogress');
- });
- });
-})();
diff --git a/src/style.css b/style.css
index cf7fc86..cf7fc86 100644
--- a/src/style.css
+++ b/style.css