diff options
Diffstat (limited to 'src/popover.js')
-rw-r--r-- | src/popover.js | 1348 |
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'); - }); - }); -})(); |