Prototype
This commit is contained in:
+139
@@ -0,0 +1,139 @@
|
||||
import { yardGeoJSON } from "./data/yard.js";
|
||||
|
||||
const TILE_MAX_NATIVE_ZOOM = 19;
|
||||
const MAP_MAX_ZOOM = 24;
|
||||
|
||||
const map = L.map("map", {
|
||||
zoomControl: false,
|
||||
maxZoom: MAP_MAX_ZOOM,
|
||||
});
|
||||
|
||||
L.control
|
||||
.zoom({
|
||||
position: "bottomright",
|
||||
})
|
||||
.addTo(map);
|
||||
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
maxNativeZoom: TILE_MAX_NATIVE_ZOOM,
|
||||
maxZoom: MAP_MAX_ZOOM,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
}).addTo(map);
|
||||
|
||||
const geoJsonLayer = L.geoJSON(yardGeoJSON, {
|
||||
pointToLayer(feature, latlng) {
|
||||
const style = feature.properties.style;
|
||||
return L.circleMarker(latlng, {
|
||||
radius: style.radius ?? 6,
|
||||
color: style.color ?? "#333333",
|
||||
fillColor: style.fillColor ?? style.color ?? "#333333",
|
||||
fillOpacity: style.fillOpacity ?? 0.8,
|
||||
weight: style.weight ?? 2,
|
||||
});
|
||||
},
|
||||
style(feature) {
|
||||
return feature.properties.style;
|
||||
},
|
||||
onEachFeature(feature, layer) {
|
||||
layer.bindPopup(buildPopupContent(feature.properties));
|
||||
},
|
||||
}).addTo(map);
|
||||
|
||||
map.fitBounds(geoJsonLayer.getBounds().pad(0.2), { maxZoom: MAP_MAX_ZOOM });
|
||||
|
||||
const legend = document.getElementById("legend");
|
||||
const legendEntries = summarizeTypes(yardGeoJSON.features);
|
||||
|
||||
legend.replaceChildren(
|
||||
...legendEntries.map((entry) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "legend-item";
|
||||
|
||||
const swatch = document.createElement("span");
|
||||
swatch.className = "legend-swatch";
|
||||
swatch.style.background = entry.color;
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.textContent = `${entry.label} (${entry.count})`;
|
||||
|
||||
row.append(swatch, label);
|
||||
return row;
|
||||
}),
|
||||
);
|
||||
|
||||
function summarizeTypes(features) {
|
||||
const summary = new Map();
|
||||
|
||||
for (const feature of features) {
|
||||
const key = feature.properties.kind;
|
||||
const existing = summary.get(key) ?? {
|
||||
label: feature.properties.label,
|
||||
count: 0,
|
||||
color: feature.properties.style.color ?? "#333333",
|
||||
};
|
||||
|
||||
existing.count += 1;
|
||||
summary.set(key, existing);
|
||||
}
|
||||
|
||||
return [...summary.values()];
|
||||
}
|
||||
|
||||
function buildPopupContent(properties) {
|
||||
const { label, name, details, parentId, groupIds } = properties;
|
||||
const entries = Object.entries(details ?? {}).filter(([, value]) => value !== null && value !== undefined);
|
||||
const relationshipEntries = [];
|
||||
|
||||
if (parentId) {
|
||||
relationshipEntries.push(["parentFeature", parentId]);
|
||||
}
|
||||
|
||||
if (groupIds?.length) {
|
||||
relationshipEntries.push(["groups", groupIds.join(", ")]);
|
||||
}
|
||||
|
||||
const title = `<strong>${escapeHtml(name)}</strong>`;
|
||||
const subtitle = name === label ? "" : `<div>${escapeHtml(label)}</div>`;
|
||||
const allEntries = [...entries, ...relationshipEntries];
|
||||
if (allEntries.length === 0) {
|
||||
return `${title}${subtitle}`;
|
||||
}
|
||||
|
||||
const detailRows = allEntries
|
||||
.map(([key, value]) => `<div><span>${escapeHtml(formatDetailLabel(key))}:</span> ${escapeHtml(formatDetailValue(key, value))}</div>`)
|
||||
.join("");
|
||||
|
||||
return `${title}${subtitle}<div>${detailRows}</div>`;
|
||||
}
|
||||
|
||||
function formatDetailLabel(key) {
|
||||
const labels = {
|
||||
canopyDiameterMeters: "Canopy diameter",
|
||||
heightMeters: "Height",
|
||||
plantedDate: "Planted",
|
||||
species: "Species",
|
||||
fenceType: "Fence type",
|
||||
note: "Note",
|
||||
parentFeature: "Parent",
|
||||
groups: "Groups",
|
||||
};
|
||||
|
||||
return labels[key] ?? key;
|
||||
}
|
||||
|
||||
function formatDetailValue(key, value) {
|
||||
if (key === "canopyDiameterMeters" || key === "heightMeters") {
|
||||
return `${value} m`;
|
||||
}
|
||||
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
Reference in New Issue
Block a user