Combine adjacent plats

curl -LO "https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"
This commit is contained in:
Chandler Swift 2025-11-08 14:28:29 -06:00
parent 08b3a0bac5
commit 9352930ba6
Signed by: chandlerswift
GPG key ID: A851D929D52FB93F
2 changed files with 186 additions and 29 deletions

127
map.html
View file

@ -6,6 +6,7 @@
<title>Lawrence Deer Club Maps</title>
<link rel="stylesheet" href="/leaflet/leaflet.css" />
<script src="/leaflet/leaflet.js"></script>
<script src="/turf.min.js"></script>
<style>
html, body {
margin: 0;
@ -136,27 +137,56 @@
</div>`;
document.getElementById('lightbox').style.display = 'block';
}
function calculateCentroid(points) {
// https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
let area = 0;
let centroidLat = 0;
let centroidLng = 0;
function collectPlatEdges(featureCollection) {
const segments = new Map();
for (let i = 0; i < points.length; i++) {
const { lat: lat1, lng: lng1 } = points[i];
const { lat: lat2, lng: lng2 } = points[(i + 1) % points.length]; // Next vertex, wrapping around
const addSegment = (start, end) => {
if (!start || !end || start.length !== 2 || end.length !== 2) {
return;
}
if (start[0] === end[0] && start[1] === end[1]) {
return;
}
const forwardKey = `${start[0]},${start[1]}|${end[0]},${end[1]}`;
const backwardKey = `${end[0]},${end[1]}|${start[0]},${start[1]}`;
const key = forwardKey < backwardKey ? forwardKey : backwardKey;
if (!segments.has(key)) {
segments.set(key, { from: start, to: end });
}
};
const determinant = lat1 * lng2 - lng1 * lat2;
area += determinant;
centroidLat += (lat1 + lat2) * determinant;
centroidLng += (lng1 + lng2) * determinant;
const visitRing = (ring) => {
if (!Array.isArray(ring) || ring.length < 2) {
return;
}
for (let i = 0; i < ring.length - 1; i++) {
addSegment(ring[i], ring[i + 1]);
}
};
for (const feature of featureCollection.features) {
if (!feature || !feature.geometry) {
continue;
}
const { type, coordinates } = feature.geometry;
if (type === "Polygon") {
for (const ring of coordinates) {
visitRing(ring);
}
} else if (type === "MultiPolygon") {
for (const polygon of coordinates) {
for (const ring of polygon) {
visitRing(ring);
}
}
}
}
area *= 0.5;
centroidLat /= (6 * area);
centroidLng /= (6 * area);
return [centroidLat, centroidLng];
const features = [];
for (const segment of segments.values()) {
features.push(turf.lineString([segment.from, segment.to]));
}
return turf.featureCollection(features);
}
(async function() {
const map = L.map('map', {
@ -193,19 +223,58 @@
const plats = await plats_req.json();
const data = await data_req.json();
L.geoJSON(plats, {
filter: feature => !feature.properties.TAO_NAME.toLowerCase().includes("lawrence deer club"),
onEachFeature: function(feature, layer) {
const centroid = calculateCentroid(layer.getLatLngs()[0]);
const label = L.marker(centroid, {
icon: L.divIcon({
iconSize: null,
className: "label",
html: "<div>" + feature.properties.TAO_NAME + "</div>"
})
const platFeatures = Array.isArray(plats.features) ? plats.features : [];
const filteredPlats = {
type: "FeatureCollection",
features: platFeatures.filter(feature => {
const name = feature?.properties?.TAO_NAME;
if (typeof name !== "string") {
return false;
}
return !name.toLowerCase().includes("lawrence deer club");
}),
};
if (filteredPlats.features.length > 0) {
// draw deduplicated plat boundaries with a subtle stroke so the merged shapes stay legible
const platEdges = collectPlatEdges(filteredPlats);
if (platEdges.features.length > 0) {
L.geoJSON(platEdges, {
style: {
weight: 2,
dashArray: "15 25", // px on, px off
},
interactive: false,
}).addTo(map);
},
}).addTo(map);
}
const dissolvedPlats = turf.dissolve(filteredPlats, { propertyName: "TAO_NAME" });
if (dissolvedPlats && dissolvedPlats.features) {
L.geoJSON(dissolvedPlats, {
// style: {
// color: "#004f9f",
// weight: 2,
// fillOpacity: 0.1,
// },
onEachFeature(feature, layer) {
if (!feature || !feature.geometry) {
return;
}
const labelPoint = turf.pointOnFeature(feature);
const [lng, lat] = labelPoint.geometry.coordinates;
L.marker([lat, lng], {
icon: L.divIcon({
iconSize: null,
className: "label",
html: "<div>" + feature.properties.TAO_NAME + "</div>",
}),
interactive: false,
}).addTo(map);
},
}).addTo(map);
}
}
L.geoJSON(track, {
style: {

88
turf.min.js vendored Normal file

File diff suppressed because one or more lines are too long