{
const container = html`<div style="height:500px;">`;
yield container;
mapboxgl.accessToken =
"pk.eyJ1IjoidG9mZmkiLCJhIjoiY2lvMDBxMzR3MDB1eHZza2x4NGI2YjI5OSJ9.IEaNA05pWbT92nOu-lEOYw";
let initialPolygon = viewof selectedCountry.value;
if (customGeojsonString) {
initialPolygon = JSON.parse(customGeojsonString);
} else {
initialPolygon = countries.features.find(
({ properties: { name } }) => name === initialPolygon
);
}
const map = new mapboxgl.Map({
container: container,
style: "mapbox://styles/toffi/clnx5216j006b01qm3ijohntw",
center: turf.center(initialPolygon).geometry.coordinates,
zoom: 10
});
map.fitBounds(turf.bbox(initialPolygon), {
padding: 20,
duration: 0
});
const options = {
displayControlsDefault: false,
controls: {
polygon: true,
trash: true
},
userProperties: true
};
const Draw = new MapboxDraw(options);
map.addControl(Draw, "top-left");
let fc = turf.featureCollection([]);
function calcLabelPositions(polygon) {
// make sure polygon is a feature
if (polygon.type === "FeatureCollection") polygon = polygon.features[0];
// Turf.js
// Centroid
const startCentroid = performance.now();
const centroid = turf.centroid(polygon);
centroid.properties = {
type: "Centroid",
name: polygon.properties?.name || "Unnamed",
ID: polygon.ID
};
const endCentroid = performance.now();
mutable times.Centroid += ~~(endCentroid - startCentroid);
// Point on surface
const startPos = performance.now();
const pos = turf.pointOnSurface(polygon);
pos.properties = {
type: "Point on surface (POS)",
name: polygon.properties?.name || "Unnamed",
ID: polygon.ID
};
const endPos = performance.now();
mutable times.POS += ~~(endPos - startPos);
// Polylabel
const startPolylabel = performance.now();
const polylabelPos = findPolylabel(
polygon,
viewof polylabelPrecision.value
);
const polylab = turf.point(polylabelPos);
polylab.properties = {
type: "Polylabel",
name: polygon.properties?.name || "Unnamed",
ID: polygon.ID
};
const endPolylabel = performance.now();
mutable times.Polylabel += ~~(endPolylabel - startPolylabel);
// GEOS
// Maximum inscribed circle
const startMic = performance.now();
const geomPtr = geojsonToGeosGeom(polygon);
const micPtr = geos.GEOSMaximumInscribedCircle(
geomPtr,
viewof micTolerance.value
);
// the mic is a GEOSGeom linestring with two points,
// the first point is the center of the mic, the second
// point is the distance (tolerance) of the mic
const centerPtr = geos.GEOSGeomGetPointN(micPtr, 0);
const mic = turf.feature(geosGeomToGeojson(centerPtr));
mic.properties = {
type: "Maximum inscribed circle (MIC)",
name: polygon.properties?.name || "Unnamed",
ID: polygon.ID
};
// free memory
geos.GEOSFree(centerPtr);
geos.GEOSFree(micPtr);
geos.GEOSFree(geomPtr);
const endMic = performance.now();
mutable times.MIC += ~~(endMic - startMic);
fc.features.push(centroid, pos, mic, polylab);
}
viewof selectedCountry.addEventListener("change", () => {
const newSelectedCountryName = viewof selectedCountry.value;
const newSelectedCountryBoundary = countries.features.find(
({ properties: { name } }) => name === newSelectedCountryName
);
map.fitBounds(turf.bbox(newSelectedCountryBoundary), {
padding: 20
});
});
function updateLabels() {
// reset performance times
mutable times = {
Centroid: 0,
POS: 0,
Polylabel: 0,
MIC: 0
};
// reset fc
fc = turf.featureCollection([]);
countries.features.forEach((country) => {
calcLabelPositions(country);
});
console.log(mutable times);
map.getSource("label-points").setData(fc);
}
// create a minimal debounced update labels function, since Observable sliders
// fire change events constantly while sliding
const debouncedUpdateLabels = _.debounce(updateLabels, 10);
viewof micTolerance.addEventListener("change", debouncedUpdateLabels);
viewof polylabelPrecision.addEventListener("change", debouncedUpdateLabels);
map.on("load", function () {
if (customGeojsonString) {
const ID = Draw.add(initialPolygon);
initialPolygon.ID = ID[0];
calcLabelPositions(initialPolygon);
}
countries.features.forEach((country) => {
const ID = Draw.add(country);
country.ID = ID[0];
calcLabelPositions(country);
});
console.log(mutable times);
map.on("draw.create", (e) => {
// create labels for new poly, mutates fc
calcLabelPositions(e.features[0]);
map.getSource("label-points").setData(fc);
});
map.on("draw.delete", (e) => {
console.log(e.features[0], fc);
fc.features = fc.features.filter(
({ properties: { ID, id } }) =>
ID !== e.features[0].ID &&
ID !== e.features[0].id &&
id !== e.features[0].id
);
map.getSource("label-points").setData(fc);
});
map.addSource("label-points", {
type: "geojson",
data: fc
});
map.addLayer({
id: "label-position",
type: "circle",
source: "label-points",
paint: {
// Make circles larger as the user zooms from z12 to z22.
"circle-radius": 5,
// Color circles by ethnicity, using a `match` expression.
"circle-color": [
"match",
["get", "type"],
"Centroid",
"#fbb03b",
"Maximum inscribed circle (MIC)",
"#223b53",
"Polylabel",
"#e55e5e",
"Point on surface (POS)",
"#21a373",
/* other */ "#ccc"
]
}
});
map.addLayer({
id: "label-points-labels",
type: "symbol",
source: "label-points",
layout: {
"text-field": ["get", "name"],
"text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
"text-size": 18
},
paint: {
"text-color": "#fff",
"text-halo-width": 1,
"text-halo-color": "grey"
},
...(viewof radios.value !== "All"
? { filter: ["==", "type", viewof radios.value] }
: {})
});
});
function changeLabelPos() {
const curValue = viewof radios.value;
if (curValue === "All") {
map.setFilter("label-points-labels", null);
} else {
map.setFilter("label-points-labels", ["==", "type", curValue]);
}
}
viewof radios.addEventListener("change", changeLabelPos);
invalidation.then(() => {
map.remove();
viewof micTolerance.removeEventListener("change", debouncedUpdateLabels);
viewof polylabelPrecision.removeEventListener(
"change",
debouncedUpdateLabels
);
viewof radios.removeEventListener("change", changeLabelPos);
});
}