maps.chandlerswift.com/main.js

163 lines
4.8 KiB
JavaScript
Raw Normal View History

import './style.css';
import {Map, View} from 'ol';
2024-02-02 03:14:58 -06:00
import {fromLonLat, get, transform} from 'ol/proj.js';
2023-07-03 22:13:15 -05:00
import {defaults as defaultControls} from 'ol/control.js';
2024-01-29 01:39:46 -06:00
import Overlay from 'ol/Overlay.js';
2023-07-03 22:13:15 -05:00
2024-02-02 03:14:58 -06:00
import ContextMenu from 'ol-contextmenu';
2023-07-03 22:13:15 -05:00
import ToggleMenuControl from './ui/controls.js';
import layerCategories from './layers/index.js';
2023-07-03 15:27:18 -05:00
2024-01-29 01:39:46 -06:00
// 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,
},
},
});
2024-02-02 03:14:58 -06:00
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({
2024-02-02 03:14:58 -06:00
controls: defaultControls().extend([new ToggleMenuControl(), contextMenu]),
target: 'map',
layers: [],
2024-01-29 01:39:46 -06:00
overlays: [popupOverlay],
view: new View({
2023-07-03 01:00:10 -05:00
center: fromLonLat([-93.24151, 44.80376]),
zoom: 10,
})
});
2023-07-03 15:27:18 -05:00
// 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" : ""}>
2023-07-25 20:35:57 -05:00
<summary>${category.name}</summary>
2023-09-01 22:23:40 -05:00
${category.details ? "<p>" + category.details + "</p>" : ""}
<ul></ul>
2023-07-25 20:35:57 -05:00
</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);
}
}
}
2024-01-29 01:39:46 -06:00
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) {
2024-01-29 03:18:36 -06:00
let layer;
const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature, l) {
layer = l;
2024-01-29 01:39:46 -06:00
return feature;
});
if (!feature) {
return;
}
2024-01-29 03:18:36 -06:00
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();
2024-01-29 01:39:46 -06:00
2024-01-29 03:18:36 -06:00
content.innerHTML = objectToTable(featureData);
}
2024-01-29 01:39:46 -06:00
popupOverlay.setPosition(evt.coordinate);
closer.onclick = function (){
popupOverlay.setPosition(undefined);
if (layer.hasOwnProperty('destroyPopupCallback')) {
layer.destroyPopupCallback(feature);
}
closer.blur();
return false;
};
2024-01-29 01:39:46 -06:00
});
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}
-->`;