aboutsummaryrefslogtreecommitdiffstats
path: root/main.js
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2024-01-20 21:15:18 +0100
committerGuilhem Moulin <guilhem@fripost.org>2024-01-22 01:18:51 +0100
commit1b6b4952f53c90e7e071fa5a71cea48d74e8d2e4 (patch)
treeb506c7becadf76c20c35a4442f334df2d091ad51 /main.js
parent46fc2671c139cdce834b29e700deba60e626820f (diff)
Show feature properties on popover via singleclick on map.
We use bootstrap's Popover for that: https://getbootstrap.com/docs/5.3/components/popovers/#options
Diffstat (limited to 'main.js')
-rw-r--r--main.js220
1 files changed, 212 insertions, 8 deletions
diff --git a/main.js b/main.js
index 77d2bdf..bd8dbff 100644
--- a/main.js
+++ b/main.js
@@ -27,6 +27,8 @@ import ScaleLine from 'ol/control/ScaleLine.js';
import Zoom from 'ol/control/Zoom.js';
import ZoomSlider from 'ol/control/ZoomSlider.js';
+import Overlay from 'ol/Overlay.js';
+
import MVT from 'ol/format/MVT.js';
import VectorTileLayer from 'ol/layer/VectorTile.js';
import VectorTile from 'ol/source/VectorTile.js';
@@ -42,7 +44,7 @@ import proj4 from 'proj4';
import { get as getProjection } from 'ol/proj.js';
import { register as registerProjection } from 'ol/proj/proj4.js';
-import { Modal } from 'bootstrap';
+import { Modal, Popover } from 'bootstrap';
import './style.css';
@@ -140,6 +142,7 @@ const map = new Map({
],
target: document.getElementById('map'),
});
+const popup = document.getElementById('popup');
/* move the control container to the viewport */
const container = document.getElementById('map-control-container');
@@ -344,6 +347,12 @@ if (window.location !== window.parent.location) {
}
})
control.addEventListener('leavefullscreen', function() {
+ const popover = Popover.getInstance(popup);
+ if (popover !== null) {
+ /* dispose popover as is might overflow the viewport */
+ popover.dispose();
+ }
+
const btn = control.element.getElementsByTagName('button')[0];
btn.classList.replace(classActive, classInactive);
btn.title = titleInactive;
@@ -452,14 +461,12 @@ if (window.location !== window.parent.location) {
/* we're all set, show the control container now */
container.setAttribute('aria-hidden', 'false');
-map.on('singleclick', function(event) {
- const size = map.getSize();
- if (size[0] < 576 || size[1] < 576) {
- return;
- }
-});
-
view.on('change', function(event) {
+ const popover = Popover.getInstance(popup);
+ if (popover !== null) {
+ popover.dispose();
+ }
+
const coordinates = view.getCenter();
const searchParams = new URLSearchParams(location.hash.substring(1));
searchParams.set('x', coordinates[0].toFixed(2).replace(TRAILING_ZEROES, ''));
@@ -471,6 +478,11 @@ view.on('change', function(event) {
const layers = {
svk_lines: {
+ popoverTitle: 'Kraftledning (befintlig)',
+ popover: [
+ ['Förläggn', 'FÖRLÄGGN'],
+ ['Spänning', 'SPÄNNING', { fn: (v) => v + '\u202FkV' }],
+ ],
style: [1, 1.5, 2, 2, 2, 2, 3, 4, 5, 6, 8, 10].map(function(width) {
return new Style({
zIndex: 2,
@@ -782,3 +794,195 @@ map.addLayer(new VectorTileLayer({
body.classList.add('modal-body');
body.innerHTML = 'legend TODO';
})();
+
+/* popup overlay */
+(function() {
+ const popupOverlay = new Overlay({
+ stopEvent: true,
+ element: popup,
+ });
+
+ map.addOverlay(popupOverlay);
+ const features = [];
+ let popover, featureNum = 0;
+
+ const header = document.createElement('div');
+ header.classList.add('d-flex');
+
+ const pageNode = document.createElement('div');
+ pageNode.classList.add('flex-grow-1', 'pe-4');
+ header.appendChild(pageNode);
+
+ 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);
+
+ const onClickPageChange = function(event, offset) {
+ const btn = event.target;
+ if (btn.classList.contains('disabled') || popover === null || popover.tip === null) {
+ return;
+ }
+ if (featureNum + offset < 0 || featureNum + offset > features.length - 1) {
+ return;
+ }
+
+ featureNum += offset;
+ if (featureNum < 1) {
+ btnPrev.classList.add('disabled');
+ } else {
+ btnPrev.classList.remove('disabled');
+ }
+ if (featureNum < features.length - 1) {
+ btnNext.classList.remove('disabled');
+ } else {
+ btnNext.classList.add('disabled');
+ }
+
+ pageNum.innerHTML = (featureNum + 1).toString();
+ popover.tip.getElementsByClassName('popover-body')[0].replaceChildren(features[featureNum]);
+ setTimeout(function() { btn.blur() }, 100);
+ };
+
+ 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.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.onclick = function(event) {
+ return onClickPageChange(event, +1);
+ };
+
+ const btnClose = document.createElement('button');
+ btnClose.classList.add('popover-button', 'popover-button-close');
+ btnClose.setAttribute('type', 'button');
+ btnClose.title = 'Stäng';
+ btnClose.onclick = function(event) {
+ if (popover !== null) {
+ popover.dispose();
+ }
+ };
+
+ header.appendChild(btnPrev);
+ header.appendChild(btnNext);
+ header.appendChild(btnClose);
+
+ const container0 = map.getViewport().querySelector('.ol-overlay-container.ol-selectable');
+ map.on('singleclick', function(event) {
+ /* clear the features list */
+ features.length = 0;
+ featureNum = 0;
+
+ /* dispose any pre-existing popover */
+ popover = Popover.getInstance(popup);
+ if (popover !== null) {
+ popover.dispose();
+ }
+
+ const size = map.getSize();
+ if (size[0] < 576 || size[1] < 576) {
+ return;
+ }
+
+ /* unclear how many feature we'll find, render prev/next buttons invisible for now */
+ pageNode.classList.add('invisible');
+ btnPrev.classList.add('invisible', 'disabled');
+ btnNext.classList.add('invisible', 'disabled');
+
+ map.forEachFeatureAtPixel(event.pixel, function(feature, layer) {
+ const properties = feature.getProperties();
+ const def = layers[properties.layer];
+ if (def === undefined || def.popover === undefined) {
+ /* skip layers which didn't opt-in for popover */
+ return;
+ }
+
+ /* 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);
+
+ def.popover.forEach(function([desc, key, opts]) {
+ let v = properties[key];
+ if (opts === undefined) {
+ opts = {};
+ }
+ if (opts.fn !== undefined) {
+ v = opts.fn(v);
+ }
+ if (v === undefined) {
+ v = document.createTextNode('');
+ } else if (!(v instanceof HTMLElement)) {
+ 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);
+ }
+
+ // console.log(properties);
+ content.appendChild(table);
+ features.push(content);
+
+ pageCount.innerHTML = features.length.toString();
+ if (features.length == 2) {
+ /* there are ≥2 features, make prev/pre buttons visible */
+ btnNext.classList.remove('invisible', 'disabled');
+ btnPrev.classList.remove('invisible');
+ pageNode.classList.remove('invisible');
+ }
+
+ if (popover === null || popover.tip === null) {
+ /* create a new popover if we're not already showing one */
+ pageNum.innerHTML = (featureNum + 1).toString();
+ popupOverlay.setPosition(event.coordinate);
+
+ popover = new Popover(popup, {
+ 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: content,
+ html: true,
+ placement: 'right',
+ fallbackPlacements: ['right', 'left', 'bottom', 'top'],
+ container: container0,
+ });
+ popover.show();
+ }
+ }, {
+ hitTolerance: 5,
+ checkWrapped: false,
+ });
+ });
+}());