Public
Edited
Oct 29, 2022
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
updateMapboxParamDiv = () => {
const { mkGeoJson, reDraw } = realTimeSummary;
document.getElementById("mapboxParamDiv").innerHTML = html`
<div>
<span> Map parameters: </span>
Lat: ${map.getCenter().lat.toPrecision(8)},
Lng: ${map.getCenter().lng.toPrecision(8)},
Zoom: ${map.getZoom().toPrecision(8)}
</div>
<div>
<span> Time Cost (milliseconds): </span>
mkGeoJson: ${mkGeoJson},
reDraw: ${reDraw}
</div>
`.outerHTML;
}
Insert cell
Insert cell
Insert cell
Insert cell
charts = {
return {
histChart: Histogram([1, 2, 3], {
value: (d) => d,
label: "Amount (%) →",
width,
height: width / 4,
color: "steelblue"
})
};
}
Insert cell
{
map.padding = padding;
map.xNum = xNum;
reDraw();
}
Insert cell
// Make the constants for higher efficiency
mathConst = {
const sqrt3 = Math.sqrt(3);
return { sqrt3 };
}
Insert cell
// Make function for generating Hex grids
// hexFunc = mkHexFunc() yields hexFunc = ƒ(rx, ry, x, y)
// rx, ry: Radius of the hex;
// x, y: Center location of the hex.

mkHexFunc = () => {
const pnts = [];
var rad,
p = (2 * Math.PI) / 6;
for (let i = 0; i < 7; ++i) {
rad = i * p;
pnts.push([Math.cos(rad), Math.sin(rad)]);
}
return (rx, ry, x, y) => {
return pnts.map((e) => [e[0] * rx + x, e[1] * ry + y]);
};
}
Insert cell
hexFunc = mkHexFunc()
Insert cell
mkGeoJson(map)
Insert cell
realTimeSummary = {
return {
mkGeoJson: -1,
reDraw: -1
};
}
Insert cell
mkGeoJson = (map) => {
const timeStart = new Date();

// Some const for computation efficiency
const { sqrt3 } = mathConst;
const { xNum, padding } = map;
console.log("Use", { xNum });

// Parse the current location
var cx = map.getCenter().lng,
cy = map.getCenter().lat,
e = map.getBounds()._ne.lng,
n = map.getBounds()._ne.lat,
w = map.getBounds()._sw.lng,
s = map.getBounds()._sw.lat;

// Correct south lat on pitching
s = 2 * cy - n;

// xNum is defined, so compute the yNum for it
var yNum = parseInt(xNum * aspect);

// Make x- and y-Scaler
var xScale = d3.scaleLinear().range([cx, e]).domain([0, xNum]),
yScale = d3
.scaleLinear()
.range([cy, n])
.domain([0, xNum * aspect]);

// Parameters for Hex layout
var rx = xScale(1) - xScale(0),
ry = yScale(1) - yScale(0),
rxFull = rx,
ryFull = ry,
c = sqrt3 / 2;

// Padding rx and ry
rx *= 1 - padding;
ry *= 1 - padding;

// Make features for Hex grids
var coordinates,
type,
properties,
x,
y,
insideStations,
stations,
hull,
uniqueID = 0;
const features = [];
const stationSummary = [];

for (let i = -xNum; i < xNum + 3; i += 3) {
for (let j = -yNum / c; j < yNum / c + 1; j += 1) {
// Center
x = xScale(i + 1.5 * (j % 2));
y = yScale(c * j);

// The 6 nodes of the hex, 7 is for closing the polygon
// It means the latest node is as the same as the first one.
coordinates = [hexFunc(rx, ry, x, y)];
hull = hexFunc(rxFull, ryFull, x, y);

insideStations = subwayGeoJson.features.filter((e) =>
d3.polygonContains(hull, e.properties.corrected)
);
stations = insideStations.length;

// Generate properties
properties = {
pos: [x, y],
color: colormap(stations), //randomColorString(),
opacity: stations == 0 ? 0.0 : 0.7,
stations,
insideStations
};

if (stations > 0) {
stationSummary.push(stations);
}

// Push the new feature
features.push({
type: "Feature",
properties,
id: uniqueID,
geometry: { coordinates, type: "Polygon" }
});
uniqueID += 1;
}
}

// Make the histogram chart
charts.histChart = Histogram(stationSummary, {
value: (d) => d,
label: "Amount (%) →",
width,
xDomain: [1, 6],
yDomain: [0, 200],
height: width / 4,
yType: d3.scaleSqrt,
color: "steelblue"
});
document.getElementById("chart-1").innerHTML = charts.histChart.outerHTML;

realTimeSummary.mkGeoJson = new Date() - timeStart;

return { type: "FeatureCollection", features: features };
}
Insert cell
randomColorString = () => {
return d3.hsl(d3.randomUniform(0, 360)(), 0.2, 0.7).hex();
}
Insert cell
// Generate reDraw function
reDraw = () => {
const timeStart = new Date();
map.getSource("gridGeometrySource").setData(mkGeoJson(map));
map.setPaintProperty("hexStroke", "line-color", gridColor);
realTimeSummary.reDraw = new Date() - timeStart;
updateMapboxParamDiv();
}
Insert cell
// Initialize the mapbox
{
map.on("load", () => {
// Init source
map.addSource("gridGeometrySource", {
type: "geojson",
data: mkGeoJson(map)
});

// Init grid stroke
map.addLayer({
id: "hexStroke",
type: "line",
source: "gridGeometrySource",
layout: {
"line-join": "round",
"line-cap": "round"
},
paint: {
"line-color": gridColor,
"line-width": 1
}
});

// Init grid fill
map.addLayer({
id: "hexFill",
type: "fill",
source: "gridGeometrySource",
layout: {},
paint: {
// "fill-color": ["get", "color"], //gridColor,
"fill-color": [
"case",
["boolean", ["feature-state", "clicked"], false],
"#000000", // Case fail, boolean is true
["get", "color"] // Case success, boolean is false
],
"fill-opacity": ["get", "opacity"]
}
});

// Behavior onclick
map.on("click", "hexFill", (e) => {
console.log("Click", e, e.features[0].properties);
map.setFeatureState(
{
source: "gridGeometrySource",
id: e.features[0].id
},
{ clicked: true }
);
});

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

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

map.on("pitchend", () => {
console.log("pitchend");
reDraw();
});
}
});
}
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
geoJson1 = mkGeoJson(map)
Insert cell
d1 = {
const { coordinates } = geoJson1.features[0].geometry;
const { pos } = geoJson1.features[0].properties;
return { coordinates, pos };
}
Insert cell
d3.polygonContains(d1.coordinates[0], d1.pos)
Insert cell
subwayGeoJson.features.map((e) => e.properties.corrected)
Insert cell
subwayGeoJson.features.filter((e) =>
d3.polygonContains(d1.coordinates[0], e.properties.corrected)
)
Insert cell
{
const t = new Date();

const output = geoJson1.features
.map((e) => e.geometry.coordinates[0])
.map((coords) => {
return subwayGeoJson.features.filter((e) =>
d3.polygonContains(coords, e.properties.corrected)
);
})
.filter((e) => e.length > 0);

const d = new Date() - t;
const dUnit = "milli seconds";

return { output, d, dUnit };
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
subwayGeoJson = {
const type = "FeatureCollection";
var features = [];

features = features.concat(mkSubwayGeoJson(subway).features);
features = features.concat(mkSubwayGeoJson(subwayShanghai).features);
features = features.concat(mkSubwayGeoJson(subwayTianjin).features);

return { type, features };
}
Insert cell
subwayBeijingGeoJson = mkSubwayGeoJson(subway)
Insert cell
mkSubwayGeoJson = (subway) => {
const geoJson = { type: "FeatureCollection", features: [] };
const { s } = subway;

var properties, geometry;

subway.l.map((line) => {
var { ln, cl, kn, st } = line;
st.map((station) => {
var { n, sl } = station;
var pair = sl.split(",").map((e) => parseFloat(e));
var corrected = coordtransform.gcj02towgs84(pair[0], pair[1]);
properties = {
ln,
cl: "#" + cl,
kn,
n,
s,
corrected
};
geometry = {
type: "Point",
coordinates: corrected
};
geoJson.features.push({ type: "Feature", properties, geometry });
});
});
return geoJson;
}
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
import { Legend, Swatches } from "@d3/color-legend"
Insert cell
import { Histogram } from "@d3/histogram"
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