Public
Edited
Dec 22, 2023
1 star
Insert cell
Insert cell
Insert cell
Insert cell
viewof map = {
let container = html`<div style='height:600px;' />`;

// Give the container dimensions.
yield container;

// set the targomo client
const client = tgmCoreClient("westcentraleurope");

// Create the "map" object with the mapboxgl.Map constructor, referencing
// the container div
const mapRef = (container.value = new maplibre.Map({
container,
center: source,
zoom: 11.5,
style: client.basemaps.getGLStyleURL("Light"),
attributionControl: false
})
.addControl(new maplibre.NavigationControl())
.addControl(
new maplibre.AttributionControl({
compact: true,
customAttribution: attribution
})
));

mapRef.on("mouseenter", "hexes", function () {
mapRef.getCanvas().style.cursor = "pointer";
});

// Change it back to a pointer when it leaves.
mapRef.on("mouseleave", "hexes", function () {
mapRef.getCanvas().style.cursor = "";
});

mapRef.on("click", "hexes", function (e) {
var { lng, lat } = e.lngLat;
const time = e.features[0].properties.value;
const html = `<strong>${e.features[0].properties.pointid}</strong><br>${time} seconds`;
new maplibre.Popup().setLngLat({ lng, lat }).setHTML(html).addTo(mapRef);
});

await new Promise((resolve, reject) => {
mapRef.on("load", async () => {
mapRef.addSource("hexes", {
type: "geojson",
data: turf.featureCollection([])
});

mapRef.addLayer(
{
id: "hexes",
type: "fill",
source: "hexes",
layout: {},
paint: {
"fill-color": ["get", "color"],
"fill-opacity": 0.65,
"fill-outline-color": "rgba(0, 0, 0, 0)"
}
},
"place_other"
);

mapRef.addSource("polygon", {
type: "geojson",
data: turf.featureCollection([])
});
mapRef.addLayer(
{
id: "polygon",
type: "line",
source: "polygon",
layout: {},
paint: {
"line-color": "#FF0",
"line-width": 2.5
}
},
"place_other"
);
resolve();
});
});

invalidation.then(() => mapRef.remove());
yield container;
}
Insert cell
viewof mapMarker = {
const inp = Inputs.input({ id: "src", ...source });
const marker = new maplibre.Marker({
draggable: true
})
.setLngLat(source)
.addTo(map);

marker.on("dragend", () => {
inp.value = { id: "src", ...marker.getLngLat() };
inp.dispatchEvent(new Event("input", { bubbles: true }));
});
return inp;
}
Insert cell
Insert cell
h3IDs = {
// here we're deciding that we want transit coverage, 30 minutes (1800s), nodes (as we are aggregating with deckGL)
const multigraphOptions = {
edgeWeight: "time",
maxEdgeWeight: 1800,
multigraph: {
layer: {
type: "h3hexagon",
geometryDetailLevel: travelMode.h3 // the is the h3 resolution, pre-set based on travel mode for performance reasons
},
serialization: {
format: "json",
h3MaxBuffer: travelMode.buffer // the buffer can be used to fill in holes, pre-set based on travel mode
}
},
sources: [{ id: 1, ...mapMarker, ...{ tm: { [travelMode.value]: {} } } }]
};

// set the targomo client
const client = tgmCoreClient("westcentraleurope");
const mg = await fetch(
`https://api.targomo.com/westcentraleurope/v1/multigraph?key=${targomoKey()}`,
{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(multigraphOptions)
}
).then((response) => response.json());

return mg.data;
}
Insert cell
Insert cell
data = {
const coverage = Object.keys(h3IDs).filter((d) => h3IDs[d].w <= 1800); // the multigraph h3 type can allow a buffer to fill in gaps. This also creates cells beyond the target reachability, which we are filtering out here
return geojson2h3.h3SetToFeatureCollection(coverage, (d) => ({
pointid: d,
value: h3IDs[d].w,
color: tgmColorInterpolator(1800)(h3IDs[d].w)
}));
}
Insert cell
Insert cell
polygon = turf.multiPolygon(
h3.cellsToMultiPolygon(
Object.entries(h3IDs)
.filter((d) => d[1].w <= minutes * 60)
.map((d) => d[0]),
true
)
)
Insert cell
{
map.getSource("hexes").setData(data);
}
Insert cell
{
map.getSource("polygon").setData(polygon);
}
Insert cell
viewof travelMode = {
const modes = [
{ label: "Walk", value: "walk", h3: 11, buffer: 200 },
{ label: "Bike", value: "bike", h3: 10, buffer: 400 },
{ label: "Car", value: "car", h3: 9, buffer: 800 }
];
return Inputs.radio(modes, {
label: html`<strong>Mode of transport</strong>`,
value: modes.find((d) => d.value === "bike"),
format: (d) => d.label
});
}
Insert cell
viewof minutes = Scrubber(
Array.from({ length: 30 }).map((_, i) => i + 1),
{
alternate: true,
autoplay: false,
delay: 150,
initial: 30,
format: (d) => d + " minutes"
}
)
Insert cell
Insert cell
html`
<style>
.panel {
display: grid;
grid-template-columns: repeat(2,auto);
column-gap: 20px;
row-gap: 20px;
}
</style>`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more