2023-07-03 00:20:36 -05:00
|
|
|
import './style.css';
|
|
|
|
import {Map, View} from 'ol';
|
2023-07-03 22:13:15 -05:00
|
|
|
import {fromLonLat, get} from 'ol/proj.js';
|
|
|
|
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
|
|
|
|
|
|
|
import ToggleMenuControl from './ui/controls.js';
|
2023-07-03 00:20:36 -05:00
|
|
|
|
2023-07-03 19:26:02 -05:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
closer.onclick = function () {
|
|
|
|
popupOverlay.setPosition(undefined);
|
|
|
|
closer.blur();
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2023-07-03 00:20:36 -05:00
|
|
|
const map = new Map({
|
2023-07-03 22:13:15 -05:00
|
|
|
controls: defaultControls().extend([new ToggleMenuControl()]),
|
2023-07-03 00:20:36 -05:00
|
|
|
target: 'map',
|
2023-07-03 19:26:02 -05:00
|
|
|
layers: [],
|
2024-01-29 01:39:46 -06:00
|
|
|
overlays: [popupOverlay],
|
2023-07-03 00:20:36 -05:00
|
|
|
view: new View({
|
2023-07-03 01:00:10 -05:00
|
|
|
center: fromLonLat([-93.24151, 44.80376]),
|
|
|
|
zoom: 10,
|
2023-07-03 00:20:36 -05:00
|
|
|
})
|
|
|
|
});
|
2023-07-03 15:27:18 -05:00
|
|
|
|
2023-07-03 19:26:02 -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 = `
|
2023-07-28 08:21:58 -05:00
|
|
|
<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>" : ""}
|
2023-07-03 19:26:02 -05:00
|
|
|
<ul></ul>
|
2023-07-25 20:35:57 -05:00
|
|
|
</details>
|
2023-07-03 19:26:02 -05:00
|
|
|
`;
|
|
|
|
for (let layer of category.layers) {
|
|
|
|
const li = document.createElement("li");
|
|
|
|
li.innerHTML = `
|
2023-07-04 00:20:13 -05:00
|
|
|
<label><input type="checkbox" ${layer.enabled ? "checked" : ""}> ${layer.name}</label>
|
2023-07-03 19:26:02 -05:00
|
|
|
`;
|
|
|
|
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.values_;
|
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);
|
|
|
|
});
|