aboutsummaryrefslogtreecommitdiffstats
path: root/src/popover.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/popover.js')
-rw-r--r--src/popover.js1348
1 files changed, 0 insertions, 1348 deletions
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');
- });
- });
-})();