import './style.css'; import {Map, View} from 'ol'; import {fromLonLat, get, transform} from 'ol/proj.js'; import {defaults as defaultControls} from 'ol/control.js'; import Overlay from 'ol/Overlay.js'; import ContextMenu from 'ol-contextmenu'; import ToggleMenuControl from './ui/controls.js'; import layerCategories from './layers/index.js'; // from https://openlayers.org/en/latest/examples/popup.html const container = document.getElementById('popup'); const content = document.getElementById('popup-content'); const closer = document.getElementById('popup-closer'); const popupOverlay = new Overlay({ element: container, autoPan: { animation: { duration: 250, }, }, }); const contextMenu = new ContextMenu({ width: 170, defaultItems: false, items: [ { text: 'Open on OSM', callback: obj => { const coords = transform(obj.coordinate, 'EPSG:3857', 'EPSG:4326') window.open( `https://www.openstreetmap.org/#map=${map.getView().getZoom()}/${coords[1]}/${coords[0]}`, "_blank", ); }, }, { text: 'Copy lat/long', callback: async function(obj){ const coords = transform(obj.coordinate, 'EPSG:3857', 'EPSG:4326') try { await navigator.clipboard.writeText(`${coords[1]}, ${coords[0]}`); } catch (error) { console.error(error.message); } }, }, ], }); const map = new Map({ controls: defaultControls().extend([new ToggleMenuControl(), contextMenu]), target: 'map', layers: [], overlays: [popupOverlay], view: new View({ center: fromLonLat([-93.24151, 44.80376]), zoom: 10, }) }); // Basic reactivity binding: like vue.js, just worse :) // // This implements some basic reactivity so that I can add and remove layers. // Nothing too fancy, at this point. Eventually, I'll likely pull in proper Vue, // as I aim for more complex interactions, like layer ordering, color selection, // custom layer imports, and more. for (let category of layerCategories) { const catDiv = document.createElement("div"); catDiv.innerHTML = ` <details ${category.layers.filter(l => l.enabled).length > 0 ? "open" : ""}> <summary>${category.name}</summary> ${category.details ? "<p>" + category.details + "</p>" : ""} <ul></ul> </details> `; for (let layer of category.layers) { const li = document.createElement("li"); li.innerHTML = ` <label><input type="checkbox" ${layer.enabled ? "checked" : ""}> ${layer.name}</label> `; li.querySelector("input").addEventListener("change", function(e){ if (e.target.checked) { map.getLayers().push(layer.layer); } else { map.getLayers().remove(layer.layer); } }); catDiv.querySelector("ul").appendChild(li); } document.querySelector("aside").appendChild(catDiv); } for (let category of layerCategories) { for (let layer of category.layers) { if (layer.enabled) { map.addLayer(layer.layer); } } } function objectToTable(o) { // TODO: hack hack hack let table = `<table style="margin: 0.5em; border-collapse: collapse;">`; for (let [key, value] of Object.entries(o)) { if (typeof value === "object") { value = objectToTable(value); } if (typeof value === "string" && value.startsWith('https://')) { value = `<a href="${value}">${value}</a>` } table += `<tr><td style="border: 1px solid;">${key}</td><td style="border: 1px solid;">${value}</td></tr>`; } table += `</table>`; return table; } // from https://openlayers.org/en/latest/examples/icon.html map.on('click', function (evt) { let layer; const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature, l) { layer = l; return feature; }); if (!feature) { return; } if (layer.hasOwnProperty('customPopup')) { content.innerHTML = layer.customPopup(feature); if (layer.hasOwnProperty('customPopupCallback')) { layer.customPopupCallback(feature); } } else { // exclude geometry -- https://stackoverflow.com/a/208106 const {geometry: _, ...featureData} = feature.getProperties(); content.innerHTML = objectToTable(featureData); } popupOverlay.setPosition(evt.coordinate); closer.onclick = function (){ popupOverlay.setPosition(undefined); if (layer.hasOwnProperty('destroyPopupCallback')) { layer.destroyPopupCallback(feature); } closer.blur(); return false; }; }); new ResizeObserver(() => map.updateSize()).observe(document.getElementById("map")); window.map = map; document.getElementById('source').innerHTML = `<a href="https://git.chandlerswift.com/chandlerswift/maps.chandlerswift.com/commit/${import.meta.env.VITE_GIT_COMMIT_HASH}">Source code</a> <!-- ${import.meta.env.VITE_FILE_DATES} -->`;