Public
Edited
May 4, 2024
2 forks
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Initialize the mapbox
{
map.on("load", () => {
// Init source
map.addSource("subwayStationSource", {
type: "geojson",
data: subwayGeoJson.station,
cluster: true,
clusterMaxZoom: 9, // Max zoom to cluster points on
clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
});

map.addSource("subwayLineSource", {
type: "geojson",
lineMetrics: true,
data: subwayGeoJson.line
});

// the layer must be of type 'line'
map.addLayer({
type: "line",
source: "subwayLineSource",
id: "subwayLine",
paint: {
"line-color": ["get", "cl"],
"line-width": 2
},
layout: {
"line-cap": "round",
"line-join": "round"
}
});

map.addLayer({
id: "clusters",
type: "circle",
source: "subwayStationSource",
filter: ["has", "point_count"],
paint: {
// Use step expressions (https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions-step)
// with three steps to implement three types of circles:
// * Blue, 20px circles when point count is less than 100
// * Yellow, 30px circles when point count is between 100 and 750
// * Pink, 40px circles when point count is greater than or equal to 750
"circle-color": [
"step",
["get", "point_count"],
"#51bbd6",
5,
"#f1f075",
10,
"#f28cb1"
],
"circle-opacity": 0.5,
"circle-radius": ["step", ["get", "point_count"], 20, 100, 30, 750, 40]
}
});

map.addLayer({
id: "cluster-count",
type: "symbol",
source: "subwayStationSource",
filter: ["has", "point_count"],
layout: {
"text-field": "{point_count_abbreviated}",
"text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
"text-size": 12
},
paint: {
"text-opacity": 0.5
}
});

map.addLayer({
id: "station-symbol",
type: "symbol",
source: "subwayStationSource",
filter: ["!", ["has", "point_count"]],
layout: {
"text-field": ["get", "n"],
"text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
"text-size": 12,
"text-offset": [0, 1]
},
paint: {
"text-color": "#aaaaaa"
}
});

map.addLayer({
id: "unclustered-point",
type: "circle",
source: "subwayStationSource",
filter: ["!", ["has", "point_count"]],
paint: {
"circle-color": ["get", "cl"],
"circle-radius": 4,
"circle-stroke-width": 1,
"circle-stroke-color": "#fff"
}
});

// inspect a cluster on click
map.on("click", "clusters", (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ["clusters"]
});
const clusterId = features[0].properties.cluster_id;
map
.getSource("subwayStationSource")
.getClusterExpansionZoom(clusterId, (err, zoom) => {
if (err) return;

map.easeTo({
center: features[0].geometry.coordinates,
zoom: zoom
});
});
});

// When a click event occurs on a feature in
// the unclustered-point layer, open a popup at
// the location of the feature, with
// description HTML from its properties.
map.on("click", "unclustered-point", (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const mag = e.features[0].properties.mag;
const tsunami = e.features[0].properties.tsunami === 1 ? "yes" : "no";

// Ensure that if the map is zoomed out such that
// multiple copies of the feature are visible, the
// popup appears over the copy being pointed to.
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}

new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(`magnitude: ${mag}<br>Was there a tsunami?: ${tsunami}`)
.addTo(map);
});

map.on("mouseenter", "clusters", () => {
map.getCanvas().style.cursor = "pointer";
});
map.on("mouseleave", "clusters", () => {
map.getCanvas().style.cursor = "";
});

// Add handlers
{
map.on("dragend", () => {
console.log("dragend");
updateMapboxParamDiv();
});

map.on("zoomend", () => {
console.log("zoomend");
updateMapboxParamDiv();
});

map.on("pitchend", () => {
console.log("pitchend");
updateMapboxParamDiv();
});
}
});
}
Insert cell
map = {
reloadButton;
const map = new mapboxgl.Map({
container,
center: cityCenter["beijing"],
zoom: 10,
maxPitch: 80,
// style: "mapbox://styles/listenzcc/cky2j3ywf13yi15nuybjj4kem"
style: "mapbox://styles/mapbox/light-v9"
});
return map;
}
Insert cell
aspect = 9 / 16
Insert cell
height = width * aspect
Insert cell
mapboxgl = {
const gl = await require("mapbox-gl");
if (!gl.accessToken) {
gl.accessToken =
"pk.eyJ1IjoibGlzdGVuemNjIiwiYSI6ImNrMzU5MmpxZDAxMXEzbXQ0dnd4YTZ2NDAifQ.GohcgYXFsbDqfsi_7SXdpA";
const href = await require.resolve("mapbox-gl/dist/mapbox-gl.css");
document.head.appendChild(html`<link href=${href} rel=stylesheet>`);
}
return gl;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Inputs.table(subwayGeoJson.line.features.map((e) => e.properties))
Insert cell
subwayGeoJson = {
const type = "FeatureCollection";
var stationFeatures = [],
lineFeatures = [];

[subway, subwayShanghai, subwayTianjin].map((sw) => {
const { stGeoJson, lineGeoJson } = mkSubwayGeoJson(sw);
stationFeatures = stationFeatures.concat(stGeoJson.features);
lineFeatures = lineFeatures.concat(lineGeoJson.features);
});

return {
station: { type, features: stationFeatures },
line: { type, features: lineFeatures }
};
}
Insert cell
mkSubwayGeoJson(subway).links
Insert cell
mkSubwayGeoJson(subway).links["西直门"]
Insert cell
dijkstra.find_path(mkSubwayGeoJson(subway).links, "西直门", "东直门")
Insert cell
dijkstra.find_path(mkSubwayGeoJson(subway).links, "龙泽", "俸伯")
Insert cell
mkSubwayGeoJson = (subway) => {
const stGeoJson = { type: "FeatureCollection", features: [] };
const lineGeoJson = { type: "FeatureCollection", features: [] };
const links = {};

const { s } = subway;

var properties,
geometry,
firstSt,
latestSt,
lineFeatures = -1;
var pair, corrected, exists;

const pushLineFeatures = () => {
// If lineFeatures is not -1, it means we already have the lineFeatures
// So we push it, before init a new one
if (lineFeatures !== -1) {
// If the line is loop-line, connect the head with the tail.
if (lineFeatures.properties.lo === "1") {
lineFeatures.geometry.coordinates.push(
lineFeatures.geometry.coordinates[0]
);
}
lineGeoJson.features.push(lineFeatures);
}
};

// For each line
subway.l.map((line) => {
pushLineFeatures();

// Init new line
const { ln, cl, kn, st, lo } = line;
const numStm1 = st.length - 1;

lineFeatures = {
type: "Feature",
properties: { ln, cl: "#" + cl, kn, st, lo, s },
geometry: { coordinates: [], type: "LineString" }
};

// For each station
st.map((station, i) => {
const { n, sl } = station;

// Link the station with the previous one
// Remember the first and the last one,
// and link them with the next station,
// if lo is "1", the start and end stations are linked either
{
// Remember the first st.
if (i === 0) {
firstSt = n;
}

// Link this one with the latest
if (i > 0) {
links[latestSt] = links[latestSt] ? links[latestSt] : {};
links[latestSt][n] = 1;
links[n] = links[n] ? links[n] : {};
links[n][latestSt] = 1;
}

// Link the loop condition
if (lo === "1" && i === numStm1) {
links[firstSt] = links[firstSt] ? links[firstSt] : {};
links[firstSt][n] = 1;
links[n] = links[n] ? links[n] : {};
links[n][firstSt] = 1;
}

// Always remember this one as the latest st.
latestSt = n;
}

pair = sl.split(",").map((e) => parseFloat(e));
corrected = coordtransform.gcj02towgs84(pair[0], pair[1]);

lineFeatures.geometry.coordinates.push(corrected);

// If the station exists, attach it
exists = stGeoJson.features.filter((e) => e.properties.n == n);
if (exists.length > 0) {
exists[0].properties.ln += ", " + ln;
exists[0].properties.kn += ", " + kn;
return;
}

// Add the new station
properties = {
ln,
cl: "#" + cl,
kn,
n,
s,
sl,
corrected
};

geometry = {
type: "Point",
coordinates: corrected
};

stGeoJson.features.push({ type: "Feature", properties, geometry });
});
});

// Push the latest line
pushLineFeatures();

return { stGeoJson, lineGeoJson, links };
}
Insert cell
subwayTianjin = FileAttachment("subway-tianjin.json").json()
Insert cell
subwayShanghai = FileAttachment("subway-shanghai.json").json()
Insert cell
subway = FileAttachment("subway.json").json()
Insert cell
coordtransform = require("coordtransform")
Insert cell
d3 = require("d3")
Insert cell
dijkstra = require("https://bundle.run/dijkstrajs@1.0.2")
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