194 lines
6.1 KiB
JavaScript
194 lines
6.1 KiB
JavaScript
import './style.css';
|
|
import {Map, View} from 'ol';
|
|
import {fromLonLat, get, getTransform, toLonLat, transform} from 'ol/proj.js';
|
|
import {defaults as defaultControls} from 'ol/control.js';
|
|
import Overlay from 'ol/Overlay.js';
|
|
import { applyTransform } from 'ol/extent.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);
|
|
}
|
|
},
|
|
},
|
|
{
|
|
text: 'Edit with iD',
|
|
callback: function(obj) {
|
|
const coords = toLonLat(obj.coordinate);
|
|
window.location.href = `https://www.openstreetmap.org/edit?editor=id#map=${map.getView().getZoom()}/${coords[1]}/${coords[0]}`;
|
|
},
|
|
},
|
|
{
|
|
text: 'Edit with JOSM',
|
|
callback: function(obj) {
|
|
const [minx, miny, maxx, maxy] = applyTransform(map.getView().calculateExtent(), getTransform('EPSG:3857', 'EPSG:4326'));
|
|
|
|
const url = `http://127.0.0.1:8111/load_and_zoom?left=${minx}&top=${maxy}&right=${maxx}&bottom=${miny}`;
|
|
// inspiration from
|
|
// https://github.com/openstreetmap/openstreetmap-website/blob/27f1fbcb580db21ca1276b9f2c40a6e1571cd90b/app/assets/javascripts/index.js#L257
|
|
|
|
const iframe = document.createElement('iframe');
|
|
iframe.setAttribute('src', url);
|
|
iframe.style.display = 'none';
|
|
console.log(iframe);
|
|
iframe.addEventListener('load', iframe.remove);
|
|
document.body.append(iframe);
|
|
},
|
|
}
|
|
],
|
|
});
|
|
|
|
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);
|
|
}
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const urlLayers = urlParams.getAll('layer');
|
|
|
|
for (let category of layerCategories) {
|
|
for (let layer of category.layers) {
|
|
if (urlLayers.includes(layer.name)) {
|
|
layer.enabled = true;
|
|
}
|
|
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 !== null) {
|
|
value = objectToTable(value);
|
|
}
|
|
if (typeof value === "string" && /^https?:\/\//.test(value)) {
|
|
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);
|
|
} else {
|
|
// exclude geometry -- https://stackoverflow.com/a/208106
|
|
const {geometry: _, ...featureData} = feature.getProperties();
|
|
|
|
content.innerHTML = objectToTable(featureData);
|
|
}
|
|
if (layer.hasOwnProperty('customPopupCallback')) {
|
|
layer.customPopupCallback(feature);
|
|
}
|
|
|
|
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}
|
|
-->`;
|