diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2025-06-12 20:04:03 +0200 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2025-06-15 02:30:57 +0200 |
commit | 4a088f5acfa747da8461493b98a2bcb6b1a47886 (patch) | |
tree | 1cf0a8cc1f81621dfb34687c0d08a1ec1a75d171 /src/popover.js | |
parent | 5a0ab32b06fe14d83e01b8f94f5f3030a18d24ab (diff) |
Refactor popover handling.
In particular, make the definition object hierarchical and rename its
fields.
Diffstat (limited to 'src/popover.js')
-rw-r--r-- | src/popover.js | 784 |
1 files changed, 427 insertions, 357 deletions
diff --git a/src/popover.js b/src/popover.js index 81f65c0..bdcd5ae 100644 --- a/src/popover.js +++ b/src/popover.js @@ -1,16 +1,35 @@ +/*********************************************************************** + * 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'; -const popup = document.getElementById('popup'); - /* TODO: this should really be refactored… */ -const layers = { - 'mrr.appr_ec': { - popoverTitle: 'Bearbetningskoncession \u2013 beviljad', - popover: [ +const layers = {} + +layers.mrr = { + appr_ec: { + title: 'Bearbetningskoncession \u2013 beviljad', + fields: [ ['Namn', 'name'], ['Koncessionsmineral', 'mineral'], ['Ägare', 'owners'], @@ -25,9 +44,9 @@ const layers = { //['Län', 'County'], ], }, - 'mrr.appl_ec': { - popoverTitle: 'Bearbetningskoncession \u2013 ansökt', - popover: [ + appl_ec: { + title: 'Bearbetningskoncession \u2013 ansökt', + fields: [ ['Namn', 'name'], ['Koncessionsmineral', 'mineral'], ['Sökande', 'owners'], @@ -38,9 +57,9 @@ const layers = { //['Län', 'County'], ], }, - 'mrr.appr_met': { - popoverTitle: 'Undersökningstillstånd, metaller och industrimineral \u2013 beviljad', - popover: [ + appr_met: { + title: 'Undersökningstillstånd, metaller och industrimineral \u2013 beviljad', + fields: [ ['Namn', 'name'], ['Koncessionsmineral', 'mineral'], ['Ägare', 'owners'], @@ -55,9 +74,9 @@ const layers = { //['Län', 'County'], ], }, - 'mrr.appl_met': { - popoverTitle: 'Undersökningstillstånd, metaller och industrimineral \u2013 ansökt', - popover: [ + appl_met: { + title: 'Undersökningstillstånd, metaller och industrimineral \u2013 ansökt', + fields: [ ['Namn', 'name'], ['Koncessionsmineral', 'mineral'], ['Sökande', 'owners'], @@ -68,9 +87,9 @@ const layers = { //['Län', 'County'], ], }, - 'mrr.appr_ogd': { - popoverTitle: 'Undersökningstillstånd, olja, gas och diamant \u2013 beviljad', - popover: [ + appr_ogd: { + title: 'Undersökningstillstånd, olja, gas och diamant \u2013 beviljad', + fields: [ ['Namn', 'name'], ['Koncessionsmineral', 'mineral'], ['Ägare', 'owners'], @@ -85,9 +104,9 @@ const layers = { //['Län', 'County'], ], }, - 'mrr.appl_ogd': { - popoverTitle: 'Undersökningstillstånd, olja, gas och diamant \u2013 ansökt', - popover: [ + appl_ogd: { + title: 'Undersökningstillstånd, olja, gas och diamant \u2013 ansökt', + fields: [ ['Namn', 'name'], ['Koncessionsmineral', 'mineral'], ['Sökande', 'owners'], @@ -98,9 +117,9 @@ const layers = { //['Län', 'County'], ], }, - 'mrr.appr_dl': { - popoverTitle: 'Markanvisning till koncession', - popover: [ + appr_dl: { + title: 'Markanvisning till koncession', + fields: [ ['Namn', 'name'], ['Tillhörande bearbetnings\u00ADkoncession(er)', 'conc_name'], ['Tillståndsid', 'licenceid', { classes: ['feature-attr-mrr-license-id'] }], @@ -112,18 +131,20 @@ const layers = { //['Län', 'County'], ], }, +}; - 'svk.ledningar': { - popoverTitle: 'Kraftledning (befintlig)', - popover: [ +layers.svk = { + ledningar: { + title: 'Kraftledning (befintlig)', + fields: [ ['Förläggning', 'Placement'], ['Spänning', 'Voltage', { unit: 'kV' }], ['Ledlängd', 'geom_length', { fn: 'length' }], ], }, - 'svk.transmissionsnatsprojekt': { - popoverTitle: 'Transmissionsnätsprojekt', - popover: [ + transmissionsnatsprojekt: { + title: 'Transmissionsnätsprojekt', + fields: [ ['Projektnamn', 'Name'], ['Spänning', 'Voltage', { unit: 'kV' }], ['Länk', 'Url', { fn: function(v) { @@ -140,10 +161,12 @@ const layers = { }}], ], }, +}; - 'vbk.area_current': { - popoverTitle: 'Landbaserad projekteringsområde för vindkraft', - popover: [ +layers.vbk = { + area_current: { + title: 'Landbaserad projekteringsområde för vindkraft', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Aktuella verk', 'AntalVerk'], @@ -162,9 +185,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.area_notcurrent': { - popoverTitle: 'Landbaserad projekteringsområde för vindkraft \u2013 ej aktuell', - popover: [ + area_notcurrent: { + title: 'Landbaserad projekteringsområde för vindkraft \u2013 ej aktuell', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Aktuella verk', 'AntalVerk'], @@ -182,9 +205,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.offshore_completed': { - popoverTitle: 'Havsbaserad vindkraft \u2013 tillståndsansökan uppförd', - popover: [ + offshore_completed: { + title: 'Havsbaserad vindkraft \u2013 tillståndsansökan uppförd', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Verksamhetsutövare', 'Organisationsnamn'], @@ -208,9 +231,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.offshore_approved': { - popoverTitle: 'Havsbaserad vindkraft \u2013 tillståndsansökan beviljad', - popover: [ + offshore_approved: { + title: 'Havsbaserad vindkraft \u2013 tillståndsansökan beviljad', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Verksamhetsutövare', 'Organisationsnamn'], @@ -240,9 +263,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.offshore_amended': { - popoverTitle: 'Havsbaserad vindkraft \u2013 ändringsansökan', - popover: [ + offshore_amended: { + title: 'Havsbaserad vindkraft \u2013 ändringsansökan', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Verksamhetsutövare', 'Organisationsnamn'], @@ -267,9 +290,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.offshore_rejected': { - popoverTitle: 'Havsbaserad vindkraft \u2013 tillståndsansökan avslagen', - popover: [ + offshore_rejected: { + title: 'Havsbaserad vindkraft \u2013 tillståndsansökan avslagen', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Verksamhetsutövare', 'Organisationsnamn'], @@ -294,9 +317,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.offshore_appealed': { - popoverTitle: 'Havsbaserad vindkraft \u2013 överklagad', - popover: [ + offshore_appealed: { + title: 'Havsbaserad vindkraft \u2013 överklagad', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Verksamhetsutövare', 'Organisationsnamn'], @@ -324,9 +347,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.offshore_applied': { - popoverTitle: 'Havsbaserad vindkraft \u2013 tillståndsansökan inlämnad', - popover: [ + offshore_applied: { + title: 'Havsbaserad vindkraft \u2013 tillståndsansökan inlämnad', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Verksamhetsutövare', 'Organisationsnamn'], @@ -350,9 +373,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.offshore_consultation': { - popoverTitle: 'Havsbaserad vindkraft \u2013 samråd inför tillståndsansökan', - popover: [ + offshore_consultation: { + title: 'Havsbaserad vindkraft \u2013 samråd inför tillståndsansökan', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Verksamhetsutövare', 'Organisationsnamn'], @@ -375,9 +398,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.offshore_investigation': { - popoverTitle: 'Havsbaserad vindkraft \u2013 inledande undersökningar', - popover: [ + offshore_investigation: { + title: 'Havsbaserad vindkraft \u2013 inledande undersökningar', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Verksamhetsutövare', 'Organisationsnamn'], @@ -399,9 +422,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.offshore_revoked': { - popoverTitle: 'Havsbaserad vindkraft \u2013 inte aktuell eller återkallad', - popover: [ + offshore_revoked: { + title: 'Havsbaserad vindkraft \u2013 inte aktuell eller återkallad', + fields: [ ['Projektnamn', 'Projektnamn'], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Verksamhetsutövare', 'Organisationsnamn'], @@ -430,9 +453,9 @@ const layers = { ['Senast uppdaterat', 'SenasteUppdaterat'], ], }, - 'vbk.station_completed': { - popoverTitle: 'Landbaserad vindkraftverk \u2013 uppfört', - popover: [ + station_completed: { + title: 'Landbaserad vindkraftverk \u2013 uppfört', + fields: [ ['Verk-ID', 'VerkID', { classes: ['feature-objid'] }], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Projektnamn', 'Projektnamn'], @@ -456,9 +479,9 @@ const layers = { ['Datum för senaste uppdatering av verk', 'SenasteUppdaterat'], ], }, - 'vbk.station_processed': { - popoverTitle: 'Landbaserad vindkraftverk \u2013 handlagt', - popover: [ + station_processed: { + title: 'Landbaserad vindkraftverk \u2013 handlagt', + fields: [ ['Verk-ID', 'VerkID', { classes: ['feature-objid'] }], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Projektnamn', 'Projektnamn'], @@ -480,9 +503,9 @@ const layers = { ['Datum för senaste uppdatering av verk', 'SenasteUppdaterat'], ], }, - 'vbk.station_approved': { - popoverTitle: 'Landbaserad vindkraftverk \u2013 beviljat', - popover: [ + station_approved: { + title: 'Landbaserad vindkraftverk \u2013 beviljat', + fields: [ ['Verk-ID', 'VerkID', { classes: ['feature-objid'] }], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Projektnamn', 'Projektnamn'], @@ -505,9 +528,9 @@ const layers = { ['Datum för senaste uppdatering av verk', 'SenasteUppdaterat'], ], }, - 'vbk.station_revoked': { - popoverTitle: 'Landbaserad vindkraftverk \u2013 inte längre aktuell/återkallat', - popover: [ + station_revoked: { + title: 'Landbaserad vindkraftverk \u2013 inte längre aktuell/återkallat', + fields: [ ['Verk-ID', 'VerkID', { classes: ['feature-objid'] }], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Projektnamn', 'Projektnamn'], @@ -530,9 +553,9 @@ const layers = { ['Datum för senaste uppdatering av verk', 'SenasteUppdaterat'], ], }, - 'vbk.station_rejected': { - popoverTitle: 'Landbaserad vindkraftverk \u2013 avslagit/nekat', - popover: [ + station_rejected: { + title: 'Landbaserad vindkraftverk \u2013 avslagit/nekat', + fields: [ ['Verk-ID', 'VerkID', { classes: ['feature-objid'] }], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Projektnamn', 'Projektnamn'], @@ -555,9 +578,9 @@ const layers = { ['Datum för senaste uppdatering av verk', 'SenasteUppdaterat'], ], }, - 'vbk.station_dismounted': { - popoverTitle: 'Landbaserad vindkraftverk \u2013 nedmonterat', - popover: [ + station_dismounted: { + title: 'Landbaserad vindkraftverk \u2013 nedmonterat', + fields: [ ['Verk-ID', 'VerkID', { classes: ['feature-objid'] }], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Projektnamn', 'Projektnamn'], @@ -580,9 +603,9 @@ const layers = { ['Datum för senaste uppdatering av verk', 'SenasteUppdaterat'], ], }, - 'vbk.station_appealed': { - popoverTitle: 'Landbaserad vindkraftverk \u2013 överklagat', - popover: [ + station_appealed: { + title: 'Landbaserad vindkraftverk \u2013 överklagat', + fields: [ ['Verk-ID', 'VerkID', { classes: ['feature-objid'] }], ['Områdes-ID', 'OmrID', { classes: ['feature-objid'] }], ['Projektnamn', 'Projektnamn'], @@ -604,13 +627,15 @@ const layers = { ['Datum för senaste uppdatering av verk', 'SenasteUppdaterat'], ], }, +}; +layers.avverk = { /* Documentation at * https://www.skogsstyrelsen.se/globalassets/sjalvservice/karttjanster/geodatatjanster/produktbeskrivningar/utforda-avverkningar---produktbeskrivning.pdf * */ - 'avverk.utford': { - popoverTitle: 'Utförd avverkning', - popover: [ + utford: { + title: 'Utförd avverkning', + fields: [ ['Ärendebeteckning', 'Beteckn', { classes: ['feature-objid'] }], ['Registeringsår', 'ArendeAr'], ['Skogstyp', 'Skogstyp'], @@ -629,9 +654,9 @@ const layers = { /* Documentation at * https://www.skogsstyrelsen.se/globalassets/sjalvservice/karttjanster/geodatatjanster/produktbeskrivningar/yttre-granser-for-avverkningsanmalda-omraden---produktbeskrivning.pdf * */ - 'avverk.anmald': { - popoverTitle: 'Avverkningsanmälansområde', - popover: [ + anmald: { + title: 'Avverkningsanmälansområde', + fields: [ ['Ärendebeteckning', 'Beteckn', { classes: ['feature-objid'] }], ['Inkom datum', 'Inkomdatum'], ['Registeringsår', 'ArendeAr'], @@ -648,10 +673,12 @@ const layers = { ['Avverkad areal', 'AvvHa', { unit: 'ha' }], ], }, +}; - 'skydd.tilltradesforbud': { - popoverTitle: 'Tillträdesförbud', - popover: [ +layers.skyyd = { + tilltradesforbud: { + title: 'Tillträdesförbud', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Föreskriftsområde', 'FORSKRNAMN'], ['Namn', 'OBJEKTNAMN'], @@ -664,9 +691,9 @@ const layers = { ['Areal', 'geom_area', { fn: 'area' }], ], }, - 'skydd.nationalpark': { - popoverTitle: 'Nationalpark', - popover: [ + nationalpark: { + title: 'Nationalpark', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -685,9 +712,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.naturreservat': { - popoverTitle: 'Naturreservat', - popover: [ + naturreservat: { + title: 'Naturreservat', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -706,9 +733,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.naturreservat_kommunalt': { - popoverTitle: 'Kommunalt naturreservat', - popover: [ + naturreservat_kommunalt: { + title: 'Kommunalt naturreservat', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -727,9 +754,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.naturvardsomrade': { - popoverTitle: 'Naturvårdsområde', - popover: [ + naturvardsomrade: { + title: 'Naturvårdsområde', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -748,9 +775,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.djur_och_vaxtskyddsomrade': { - popoverTitle: 'Djur- och växtskyddsområde', - popover: [ + djur_och_vaxtskyddsomrade: { + title: 'Djur- och växtskyddsområde', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -769,9 +796,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.kulturreservat': { - popoverTitle: 'Kulturreservat', - popover: [ + kulturreservat: { + title: 'Kulturreservat', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -790,9 +817,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.vattenskyddsomrade': { - popoverTitle: 'Vattenskyddsområden', - popover: [ + vattenskyddsomrade: { + title: 'Vattenskyddsområden', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -811,9 +838,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.landskapsbildsskyddsomrade': { - popoverTitle: 'Landskapsbildsskyddsområde', - popover: [ + landskapsbildsskyddsomrade: { + title: 'Landskapsbildsskyddsområde', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -832,9 +859,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.skogligt_biotopskyddsomrade': { - popoverTitle: 'Biotopskydd i skogsmark', - popover: [ + skogligt_biotopskyddsomrade: { + title: 'Biotopskydd i skogsmark', + fields: [ ['Ärendebeteckning', 'Beteckn', { classes: ['feature-objid'] }], ['Biotopkategori', 'Biotyp'], ['Skogstyp', 'Naturtyp'], @@ -856,9 +883,9 @@ const layers = { }}], ], }, - 'skydd.ovrigt_biotopskyddsomrade': { - popoverTitle: 'Biotopskydd utanför skogsmark', - popover: [ + ovrigt_biotopskyddsomrade: { + title: 'Biotopskydd utanför skogsmark', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -877,9 +904,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.naturminne_yta': { - popoverTitle: 'Naturminne (yta)', - popover: [ + naturminne_yta: { + title: 'Naturminne (yta)', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -898,9 +925,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.naturminne_punkt': { - popoverTitle: 'Naturminne (punkt)', - popover: [ + naturminne_punkt: { + title: 'Naturminne (punkt)', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -916,9 +943,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.interimistiskt_forbud': { - popoverTitle: 'Interimistiskt förbud', - popover: [ + interimistiskt_forbud: { + title: 'Interimistiskt förbud', + fields: [ ['NVR-ID', 'NVRID', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], @@ -937,9 +964,9 @@ const layers = { ['Skogsmarksareal', 'SKOG_HA', { unit: 'ha' }], ], }, - 'skydd.fageldirektivet': { - popoverTitle: 'Fågeldirektivet (SPA)', - popover: [ + fageldirektivet: { + title: 'Fågeldirektivet (SPA)', + fields: [ ['Områdeskod', 'SITE_CODE', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Områdestyp', 'OMRADESTYP'], @@ -964,9 +991,9 @@ const layers = { }}], ], }, - 'skydd.habitatdirektivet': { - popoverTitle: 'Art- och habitatdirektivet (SCI)', - popover: [ + habitatdirektivet: { + title: 'Art- och habitatdirektivet (SCI)', + fields: [ ['Områdeskod', 'SITE_CODE', { classes: ['feature-objid'] }], ['Namn', 'NAMN'], ['Områdestyp', 'OMRADESTYP'], @@ -991,16 +1018,16 @@ const layers = { }}], ], }, - 'skydd.helcom': { - popoverTitle: 'Marina skyddade områden (Helcom MPA)', - popover: [ + helcom: { + title: 'Marina skyddade områden (Helcom MPA)', + fields: [ ['Namn', 'NAME'], ['Areal', 'geom_area', { fn: 'area' }], ], }, - 'skydd.ramsar': { - popoverTitle: 'Ramsar-områden (Våtmarkskonventionen)', - popover: [ + ramsar: { + title: 'Ramsar-områden (Våtmarkskonventionen)', + fields: [ ['Ramsar-ID', 'RAMSAR_ID', { classes: ['feature-objid'] }], ['Skyddstyp', 'SKYDDSTYP'], ['Namn', 'NAMN'], @@ -1025,9 +1052,9 @@ const layers = { }}], ], }, - 'skydd.ospar': { - popoverTitle: 'Marina skyddade områden (Ospar MPA)', - popover: [ + ospar: { + title: 'Marina skyddade områden (Ospar MPA)', + fields: [ ['Ursprung', 'ORIGIN'], ['N2000-namn', 'NAMN_N2000'], ['MPA-ID', 'MPA_ID', { classes: ['feature-objid'] }], @@ -1036,16 +1063,16 @@ const layers = { ['Areal', 'geom_area', { fn: 'area' }], ], }, - 'skydd.varldsarv': { - popoverTitle: 'Världsarv med mycket höga naturvärden (Unesco)', - popover: [ + varldsarv: { + title: 'Världsarv med mycket höga naturvärden (Unesco)', + fields: [ ['Namn', 'NAMN'], ['Areal', 'geom_area', { fn: 'area' }], ], }, - 'skydd.biosfarsomraden': { - popoverTitle: 'Biosfärsområde (Unesco)', - popover: [ + biosfarsomraden: { + title: 'Biosfärsområde (Unesco)', + fields: [ ['Namn', 'NAMN'], ['Skyddstyp', 'SKYDDSTYP'], ['Länk', 'LINK', { fn: function(v) { @@ -1063,9 +1090,9 @@ const layers = { ['Areal', 'geom_area', { fn: 'area' }], ], }, - 'skydd.naturvardsavtal': { - popoverTitle: 'Naturvårdsavtal (Naturvårdsverket, Länsstyrelsen)', - popover: [ + naturvardsavtal: { + title: 'Naturvårdsavtal (Naturvårdsverket, Länsstyrelsen)', + fields: [ ['ID', 'ID', { classes: ['feature-objid'] }], ['Namn', 'OBJNAMN'], ['Fastighet', 'FASTBET', { classes: ['feature-objid'] }], @@ -1076,9 +1103,9 @@ const layers = { ['Areal', 'geom_area', { fn: 'area' }], ], }, - 'skydd.naturvardsavtal_skogsstyrelsen': { - popoverTitle: 'Naturvårdsavtal (Skogsstyrelsen)', - popover: [ + naturvardsavtal_skogsstyrelsen: { + title: 'Naturvårdsavtal (Skogsstyrelsen)', + fields: [ ['Ärendebeteckning', 'Beteckn', { classes: ['feature-objid'] }], ['Registeringsår', 'ArendeAr'], ['Biotopkategori', 'NvaTyp'], @@ -1101,9 +1128,9 @@ const layers = { ['Undertyp', 'Undertyp'], ], }, - 'skydd.atervatningsavtal': { - popoverTitle: 'Återvätningsavtal', - popover: [ + atervatningsavtal: { + title: 'Återvätningsavtal', + fields: [ ['Ärendebeteckning', 'Beteckn', { classes: ['feature-objid'] }], ['Ärendeår', 'ArendeAr'], ['Avtalat datum', 'AvtalatDatum'], @@ -1122,9 +1149,12 @@ const layers = { }}], ], }, - 'nv.naturvarde_sks': { - popoverTitle: 'Objekt med naturvärden (Skogsstyrelsen)', - popover: [ +}; + +layers.nv = { + naturvarde_sks: { + title: 'Objekt med naturvärden (Skogsstyrelsen)', + fields: [ ['Ärendebeteckning', 'Beteckn', { classes: ['feature-objid'] }], ['Namn', 'Objnamn'], ['Datum för fältinventering', 'Datinv'], @@ -1149,9 +1179,9 @@ const layers = { }}], ], }, - 'nv.nyckelbiotop': { - popoverTitle: 'Nyckelbiotop (Skogsstyrelsen)', - popover: [ + nyckelbiotop: { + title: 'Nyckelbiotop (Skogsstyrelsen)', + fields: [ ['Ärendebeteckning', 'Beteckn', { classes: ['feature-objid'] }], ['Namn', 'Objnamn'], ['Datum för fältinventering', 'Datinv'], @@ -1181,9 +1211,9 @@ const layers = { }}], ], }, - 'nv.nyckelbiotop_storskogsbruk': { - popoverTitle: 'Nyckelbiotop (storskogsbruket)', - popover: [ + nyckelbiotop_storskogsbruk: { + title: 'Nyckelbiotop (storskogsbruket)', + fields: [ ['Uppgifter lämnade av', 'Org'], ['Inkom datum', 'InkomDatum'], ['Areal', 'geom_area', { fn: 'area' }], @@ -1201,9 +1231,9 @@ const layers = { }}], ], }, - 'nv.sumpskog': { - popoverTitle: 'Sumpskog', - popover: [ + sumpskog: { + title: 'Sumpskog', + fields: [ ['Objektnamn', 'Namn'], ['Skogstyp', 'Tradtext'], ['Hydrologisk typ', 'Hydrtext'], @@ -1237,17 +1267,17 @@ const layers = { }}], ], }, - 'nv.pagaende_naturreservatsbildning': { - popoverTitle: 'Pågående naturreservatsbildning', - popover: [ + pagaende_naturreservatsbildning: { + title: 'Pågående naturreservatsbildning', + fields: [ ['Objektnamn', 'NAMN'], ['Senast justerat', 'GRANSJUST'], /* XXX unclear what "GRANSJUST" means, just a guess */ ['Areal', 'geom_area', { fn: 'area' }], ], }, - 'nv.snus': { - popoverTitle: 'Skyddsvärd statlig skog', - popover: [ + snus: { + title: 'Skyddsvärd statlig skog', + fields: [ ['Objektnamn', 'NAMN'], ['År', 'AR'], ['Naturgeografisk region', 'NATURGEOGR', { classes: ['feature-objid'] }], @@ -1269,10 +1299,12 @@ const layers = { ['Källor', 'KALLOR'], ], }, +}; - 'ri.naturvard': { - popoverTitle: 'Riksintresse naturvård', - popover: [ +layers.ri = { + naturvard: { + title: 'Riksintresse naturvård', + fields: [ ['Namn', 'NAMN'], ['Skydd', 'SKYDD'], ['Ämnesområde', 'AMNESOMRAD'], @@ -1295,9 +1327,9 @@ const layers = { ['Areal', 'geom_area', { fn: 'area' }], ], }, - 'ri.friluftsliv': { - popoverTitle: 'Riksintresse friluftsliv', - popover: [ + friluftsliv: { + title: 'Riksintresse friluftsliv', + fields: [ ['Namn', 'NAMN'], ['Skydd', 'SKYDD'], ['Ämnesområde', 'AMNESOMR'], @@ -1336,9 +1368,9 @@ const layers = { ['Areal vatten', 'AREA_VATTE', { unit: 'ha' }], ], }, - 'ri.rorligt_friluftsliv': { - popoverTitle: 'Riksintresse rörligt friluftsliv (MB 4 kap 1§ och 2§)', - popover: [ + rorligt_friluftsliv: { + title: 'Riksintresse rörligt friluftsliv (MB 4 kap 1§ och 2§)', + fields: [ ['Namn', 'NAMN'], //['Original-ID', 'ORIGINALID', { classes: ['feature-objid'] }], ['Beskrivning', 'BESKRIVNIN'], @@ -1362,9 +1394,9 @@ const layers = { ['Referens', 'REFERENS'], ], }, - 'ri.obruten_kust': { - popoverTitle: 'Riksintresse obruten kust (MB 4 kap 3§)', - popover: [ + obruten_kust: { + title: 'Riksintresse obruten kust (MB 4 kap 3§)', + fields: [ ['Namn', 'NAMN'], //['Original-ID', 'ORIGINALID', { classes: ['feature-objid'] }], ['Beskrivning', 'BESKRIVNIN'], @@ -1389,9 +1421,9 @@ const layers = { ['Referens', 'REFERENS'], ], }, - 'ri.obrutet_fjall': { - popoverTitle: 'Riksintresse obrutet fjäll (MB 4 kap 5§)', - popover: [ + obrutet_fjall: { + title: 'Riksintresse obrutet fjäll (MB 4 kap 5§)', + fields: [ ['Namn', 'NAMN'], //['Original-ID', 'ORIGINALID', { classes: ['feature-objid'] }], ['Beskrivning', 'BESKRIVNIN'], @@ -1414,9 +1446,9 @@ const layers = { ['Referens', 'REFERENS'], ], }, - 'ri.skyddade_vattendrag': { - popoverTitle: 'Riksintresse skyddade vattendrag (MB 4 kap 6§)', - popover: [ + skyddade_vattendrag: { + title: 'Riksintresse skyddade vattendrag (MB 4 kap 6§)', + fields: [ ['Namn', 'NAMN'], //['Original-ID', 'ORIGINALID', { classes: ['feature-objid'] }], ['Beskrivning', 'BESKRIVNIN'], @@ -1441,19 +1473,21 @@ const layers = { ['Referens', 'REFERENS'], ], }, +}; - 'ren.betesomrade': { - popoverTitle: 'Samebyarnas betesområde', - popover: [ +layers.ren = { + betesomrade: { + title: 'Samebyarnas betesområde', + fields: [ ['Sameby', 'NAMN'], ['Samebys typ', 'SAMEBY_TYP'], ['Signatur', 'SIGNATUR'], ['Aktualitet', 'AKTUALITET'], ], }, - 'ren.flyttled': { - popoverTitle: 'Samebyarnas markanvändningsredovisning \u2013 flyttled', - popover: [ + flyttled: { + title: 'Samebyarnas markanvändningsredovisning \u2013 flyttled', + fields: [ ['Led-ID', 'LED_ID', { classes: ['feature-objid'], fn: (v) => v === 0 ? '' : v }], ['Sameby #1', 'SAMEBY1'], ['Sameby #2', 'SAMEBY2'], @@ -1467,18 +1501,18 @@ const layers = { ['Ledlängd', 'geom_length', { fn: 'length' }], ], }, - 'ren.riks_ren': { - popoverTitle: 'Riksintresse rennäring', - popover: [ + riks_ren: { + title: 'Riksintresse rennäring', + fields: [ ['Lagrum', 'LAGRUM'], ['Aktualitet', 'AKTUALITET'], ['Signatur', 'SIGNATUR'], ['Areal', 'geom_area', { fn: 'area' }], ], }, - 'ren.omr_riks': { - popoverTitle: '(Kärn)områden av riksintresse rennäring', - popover: [ + omr_riks: { + title: '(Kärn)områden av riksintresse rennäring', + fields: [ ['Områdes-ID', 'OMR_NR', { classes: ['feature-objid'] }], ['Länk', 'LANK'], ['Årets runt', 'ARET_RUNT'], @@ -1489,13 +1523,15 @@ const layers = { ['Areal', 'geom_area', { fn: 'area' }], ], }, +}; +layers.misc = { /* Documentation at * https://www.smhi.se/polopoly_fs/1.34541!/dammprod%202013_3%2C%20beskrivning%2C%20SVAR2012_2.pdf * */ - 'misc.dammar': { - popoverTitle: 'Damm', - popover: [ + dammar: { + title: 'Damm', + fields: [ ['Dammenhetens namn', 'DNamn'], ['Dammanläggningens namn', 'Namn'], ['Länsnr', 'LST_OBJID', { classes: ['feature-objid'] }], @@ -1536,9 +1572,9 @@ const layers = { ], }, - 'misc.gigafactories': { - popoverTitle: 'Stor industrisatsning', - popover: [ + gigafactories: { + title: 'Stor industrisatsning', + fields: [ ['Namn', 'Name'], ['Länk', 'Url', { fn: function(v) { if (v == null | v === '') { @@ -1556,15 +1592,136 @@ const layers = { }, }; -/* popup and feature overlays */ -export const popover = function(map, mapLayers, featureOverlayLayer) { - const popupOverlay = new Overlay({ + + +/* 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([desc, key, opts]) { + const tr = document.createElement('tr'); + tbody.appendChild(tr); + + const td1 = document.createElement('td'); + tr.appendChild(td1); + const textDesc = document.createTextNode(desc); + td1.appendChild(textDesc); + + const td2 = document.createElement('td'); + tr.appendChild(td2); + const v = formatValue(properties[key], opts); + if (v != null) { + td2.appendChild(v); + } + opts?.classes?.forEach?.((c) => td2.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; +}; + +let popupOverlay = null; +export const initPopupOverLay = function(map, element) { + popupOverlay = new Overlay({ stopEvent: true, - element: popup, + element: element, }); map.addOverlay(popupOverlay); +}; - let popover, overlayAttributes = [], overlayAttrIdx = 0; +let featureOverlayLayer = null; +let overlayAttributes = [], + overlayAttrIdx = 0, + mapSources = {}; +const disposeFeatureOverlay = function() { + if (featureOverlayLayer?.getVisible?.()) { + featureOverlayLayer.setVisible(false); + featureOverlayLayer.changed(); + } + /* clear the overlay list */ + overlayAttributes = []; + overlayAttrIdx = 0; + mapSources = {}; +} + +let popover = null; +export const disposePopover = function() { + disposeFeatureOverlay(); + if (popover?.tip != null) { + popover.dispose(); + } +}; + +export const initPopover = function(map) { + 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'); @@ -1573,11 +1730,14 @@ export const popover = function(map, mapLayers, featureOverlayLayer) { 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) { if (event.button != 0) { return; } - const popoverTip = Popover.getInstance(popup).tip; + const popoverTip = popover.tip; if (popoverTip.classList.contains('popover-maximized')) { return; } @@ -1615,9 +1775,6 @@ export const popover = function(map, mapLayers, featureOverlayLayer) { }; }; - const pageNode = document.createElement('h6'); - headerGrabbingArea.appendChild(pageNode); - const pageNum = document.createElement('span'); const pageCount = document.createElement('span'); pageNode.appendChild(document.createTextNode('Träff ')); @@ -1632,21 +1789,18 @@ export const popover = function(map, mapLayers, featureOverlayLayer) { }), }); const updateFeatureOverlayLayer = function(layer_group, layer, id) { - const lyr = mapLayers[layer_group]; - if (lyr == null) { + const source = mapSources[layer_group]; + if (source == null) { return; } - const urls = lyr.getSource().getUrls(); - const source = featureOverlayLayer.getSource(); - if (source.getUrls().length < 1 || source.getUrls()[0] !== urls[0]) { + if (featureOverlayLayer.getSource() !== source) { + /* console.log('Updating source for feature overlay layer'); */ featureOverlayLayer.setVisible(false); - source.setUrls(urls); + featureOverlayLayer.setSource(source); } featureOverlayLayer.setStyle(function(feature) { if (feature.getId() === id && feature.getProperties().layer === layer) { return featureOverlayStyle; - } else { - return undefined; } }); featureOverlayLayer.setVisible(true); @@ -1732,115 +1886,24 @@ export const popover = function(map, mapLayers, featureOverlayLayer) { btnClose.setAttribute('type', 'button'); btnClose.title = 'Stäng'; btnClose.setAttribute('aria-label', btnClose.title); - btnClose.onclick = function() { - featureOverlayLayer.setVisible(false); - featureOverlayLayer.changed(); - popover?.dispose(); - }; + btnClose.onclick = disposePopover; header.appendChild(btnPrev); header.appendChild(btnNext); header.appendChild(btnExpand); header.appendChild(btnClose); - const formatFeaturePropertiesToHTML = function(properties) { - /* turn the properties into a fine table */ - 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.popover.forEach(function([desc, key, opts]) { - let v = properties[key]; - if (opts === undefined) { - opts = {}; - } - if (opts.fn !== undefined) { - if (opts.fn === 'length') { - if (v < 1000) { - opts.unit = 'm'; - } else { - v /= 1000; - v = Math.round(v*100) / 100; - opts.unit = 'km'; - } - } else if (opts.fn === 'area') { - if (v < 10000) { - opts.unit = 'm²'; - } else if (v < 10000 * 10000) { - v /= 10000; - opts.unit = 'ha'; - } else { - v /= 1000000; - opts.unit = 'km²'; - } - v = Math.round(v*100) / 100; - } else { - v = opts.fn(v); - } - } - if (v == null) { - v = document.createTextNode(''); - } else if (!(v instanceof HTMLElement)) { - if (typeof(v) === 'number' && opts.unit !== undefined) { - v = v.toLocaleString('sv-SE'); - } else if (typeof(v) === 'boolean') { - v = v ? 'Ja' : 'Nej'; - } - if (opts.unit !== undefined && v !== '') { - v += '\u202F' + opts.unit; - } - v = document.createTextNode(v); - } - - const tr = document.createElement('tr'); - tbody.appendChild(tr); - - const td1 = document.createElement('td'); - tr.appendChild(td1); - const textDesc = document.createTextNode(desc); - td1.appendChild(textDesc); - - const td2 = document.createElement('td'); - tr.appendChild(td2); - td2.appendChild(v); - if (opts.classes !== undefined) { - opts.classes.forEach((c) => td2.classList.add(c)); - } - }); - - const content = document.createElement('div'); - if (def.popoverTitle !== undefined) { - const h = document.createElement('h6'); - content.appendChild(h); - const textNode = document.createTextNode(def.popoverTitle); - h.appendChild(textNode); - } - - content.appendChild(table); - return content; - }; - const container0 = map.getViewport().getElementsByClassName('ol-overlaycontainer-stopevent')[0]; map.on('singleclick', function(event) { - /* clear the overlay list */ - featureOverlayLayer.setVisible(false); - featureOverlayLayer.changed(); - overlayAttributes = []; - overlayAttrIdx = 0; + disposeFeatureOverlay(); /* dispose any pre-existing popover if not in detached mode */ - popover = Popover.getInstance(popup); - if (popover !== null) { - const popoverTip = popover.tip; - if (popoverTip !== null && !popoverTip.classList.contains('popover-detached')) { - popover.dispose(); - } + popover = Popover.getInstance(popupOverlay.element); + if (popover?.tip != null && !popover.tip.classList.contains('popover-detached')) { + popover.dispose(); } - const size = map.getSize(); + const size = event.map.getSize(); if (size[0] < 576 || size[1] < 576) { return; } @@ -1858,37 +1921,41 @@ export const popover = function(map, mapLayers, featureOverlayLayer) { btnExpand.title = btnExpandTitle; btnExpand.setAttribute('aria-label', btnExpand.title); - const fetch_body = [] - map.forEachFeatureAtPixel(event.pixel, function(feature, layer) { + const fetch_body = []; + event.map.forEachFeatureAtPixel(event.pixel, function(feature, layer) { const layerGroup = layer.get('layerGroup'); const layerName = feature.getProperties().layer; - const def = layers[layerGroup + '.' + layerName]; - if (def?.popover != null) { + 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 */ - if (!fetch_body.length) { - document.body.classList.add('inprogress'); - if (popover?.tip != null) { - 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 */ - } + return false; + } + if (fetch_body.length === 0) { + /* first feature in the list */ + 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: (l) => l.get('layerGroup') != null, + layerFilter: (lyr) => lyr.get('layerGroup') != null, }); if (fetch_body.length === 0) { - /* dispose pre-detached popover */ - popover?.dispose(); + /* 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; } @@ -1896,8 +1963,8 @@ export const popover = function(map, mapLayers, featureOverlayLayer) { method: 'POST', body: JSON.stringify(fetch_body), headers: { - 'Content-Type': 'application/json; charset=UTF-8' - } + 'Content-Type': 'application/json; charset=UTF-8', + }, }) .then(function(resp) { if (resp.status === 200) { @@ -1910,10 +1977,13 @@ export const popover = function(map, mapLayers, featureOverlayLayer) { /* 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 + overlayAttributes = data; if (overlayAttributes.length === 0) { - /* dispose pre-detached popover */ - popover?.dispose(); + /* couldn't fetch any attribute for feature(s) at pixel */ + if (popover?.tip != null) { + /* dispose pre-detached popover */ + popover.dispose(); + } return; } @@ -1931,7 +2001,7 @@ export const popover = function(map, mapLayers, featureOverlayLayer) { const attr = overlayAttributes[0]; updateFeatureOverlayLayer(attr.layer_group, attr.layer, attr.ogc_fid); - popover = new Popover(popup, { + 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, |