Combine adjacent plats
curl -LO "https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"
This commit is contained in:
parent
08b3a0bac5
commit
9352930ba6
2 changed files with 186 additions and 29 deletions
127
map.html
127
map.html
|
|
@ -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
88
turf.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue