Public
Edited
Aug 15, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
map = {
return FileAttachment("ShapesWithRoutes (4).json").json();
}
Insert cell
Input_SydneyTransitLinesGeoJSON = {
let maxSMlines = map.features.reduce((acc, e) => {
if (e.properties.agency == "SMNW") {
return Math.max(acc, e.geometry.coordinates.length);
}
return acc;
}, 0);
let hasDoneSMNWLine = false;
console.log(maxSMlines);
return _Imports.Geospatial.turf.truncate(
_Utils.Generate.GeoJSON(
"FeatureCollection",
map.features
.map((e) => {
console.log(e.properties.agency, e.geometry.coordinates.length);
if (e.properties.agency == "SMNW") {
if (
e.geometry.coordinates.length == maxSMlines &&
hasDoneSMNWLine == false
) {
hasDoneSMNWLine = true;
return e;
} else return null;
}
return e;
})
.filter((e) => e !== null)
.map((e) => {
console.log(e.properties);
return _Imports.Geospatial.turf.simplify(e, {
tolerance: 0.0001,
highQuality: false
});
})
),
{ precision: 5, coordinates: 2 }
);
}
Insert cell
{
const container = yield htl.html`<div style="height: 500px;">`;
const map = L.map(container);
const layer = L.geoJSON(Input_SydneyTransitLinesGeoJSON).addTo(map);
map.fitBounds(layer.getBounds(), { maxZoom: 9 });
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© <a href=https://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
}).addTo(map);
}
Insert cell
Insert cell
Insert cell
Insert cell
Step1_01_BufferedRoutes = {
const buffered_features = [];

for (const feature of Input_SydneyTransitLinesGeoJSON.features) {
if (
_Imports.Geospatial.turf.length(feature, { units: "kilometers" }) < 1000
) {
const buffered = _Imports.Geospatial.turf.buffer(
feature,
Step1_01_BufferDistance,
{ units: "kilometers" }
);
buffered_features.push(buffered);
}
}

console.log("Finished Step1_01_BufferedRoutes");
return _Utils.Generate.GeoJSON("FeatureCollection", buffered_features, {});
}
Insert cell
{
const container = yield htl.html`<div style="height: 500px;">`;
const map = L.map(container);
const layer = L.geoJSON(Step1_01_BufferedRoutes).addTo(map);
map.fitBounds(layer.getBounds(), {maxZoom: 9});
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© <a href=https://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
}).addTo(map);
}
Insert cell
JSTS_IO_HANDLERS = {
return {
gjreader: new _Imports.Geospatial.jsts.io.GeoJSONReader(),
gjwriter: new _Imports.Geospatial.jsts.io.GeoJSONWriter()
};
}
Insert cell
SydneyTrainRoutes_PreUnionsied_Geometries = {
const combined_routes = _Imports.Geospatial.turf.truncate(
Step1_01_BufferedRoutes,
{ precision: 5, coordinates: 2 }
);
return combined_routes.features.map((feature) =>
JSTS_IO_HANDLERS.gjreader.read(feature.geometry)
);
}
Insert cell
SydneyTrainRoutes_Unionised_Geometry = {
const geom = SydneyTrainRoutes_PreUnionsied_Geometries;
let ConstantGeometry = geom.shift();
for (const CurrentGeometry of geom) {
try {
ConstantGeometry = ConstantGeometry.union(CurrentGeometry);
console.log(
"Unionised of",
SydneyTrainRoutes_PreUnionsied_Geometries.length,
"geometries"
);
} catch (e) {
console.log(e, JSTS_IO_HANDLERS.gjwriter.write(CurrentGeometry));
}
}
//if (ConstantGeometry._geometries !== 1) throw new Error("GEOMETRIES DON'T INTERSECT");
return ConstantGeometry;
}
Insert cell
SydneyTrainRoutes_Unionised = {
const union_geometry = JSTS_IO_HANDLERS.gjwriter.write(
SydneyTrainRoutes_Unionised_Geometry
);
const union_feature = _Utils.Generate.GeoJSON("Feature", union_geometry, {});
return union_feature;
}
Insert cell
Step1_02_MergedRoutes = {
return SydneyTrainRoutes_Unionised;

const combined_routes = _Imports.Geospatial.turf.truncate(
_Imports.Geospatial.turf.combine(Step1_01_BufferedRoutes),
{ precision: 5, coordinates: 2 }
);

try {
const merged_routes = _Imports.Geospatial.turf.intersect(
combined_routes.features[0],
combined_routes.features[0]
);

console.log("Finished Step1_02_MergedRoutes");
return merged_routes;
} catch (e) {
console.error(e);
return {};
}
}
Insert cell
{
const container = yield htl.html`<div style="height: 500px;">`;
const map = L.map(container);
const layer = L.geoJSON(Step1_02_MergedRoutes).addTo(map);
map.fitBounds(layer.getBounds(), {maxZoom: 9});
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© <a href=https://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
}).addTo(map);
}
Insert cell
unbuffered_lo = _Imports.Geospatial.turf.buffer(
Step1_02_MergedRoutes,
Step1_01_BufferDistance * -0.85,
{ units: "kilometers" }
)
Insert cell
{
const container = yield htl.html`<div style="height: 500px;">`;
const map = L.map(container);
const layer = L.geoJSON(unbuffered_lo).addTo(map);
map.fitBounds(layer.getBounds(), {maxZoom: 9});
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© <a href=https://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
}).addTo(map);
}
Insert cell
simplified_unbuffered = _Imports.Geospatial.turf.simplify(unbuffered_lo, {
tolerance: 0.0001,
highQuality: false
})
Insert cell
whatToUse = simplified_unbuffered
Insert cell
{
const container = yield htl.html`<div style="height: 500px;">`;
const map = L.map(container);
const layer = L.geoJSON(whatToUse).addTo(map);
map.fitBounds(layer.getBounds(), {maxZoom: 9});
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© <a href=https://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
}).addTo(map);
}
Insert cell
Insert cell
Insert cell
Insert cell
Step2_01_BboxUpperBounds = {
const UpperBounds = _Imports.Geospatial.turf.bboxPolygon(
_Imports.Geospatial.turf.bbox(Step1_02_MergedRoutes)
).geometry.coordinates[0][3];
console.log("Finished Step2_01_BboxUpperBounds");
return UpperBounds;
}
Insert cell
Insert cell
Step2_02_RoutesAsSVG = {
const RASVG = _Imports.Geospatial.geojson2svg
.default()
.styles(() => ({ fill: "black" }))
.projection(function (coord) {
return [
coord[0] + Step2_01_BboxUpperBounds[0] * -1,
coord[1] + Step2_01_BboxUpperBounds[1] * -1
];
})
.data(_Imports.Geospatial.turf.rewind(whatToUse))
.render();
console.log("Finished Step2_02_RoutesAsSVG");
return RASVG;
}
Insert cell
Insert cell
Insert cell
Step2_03_RoutesPathCommands = {
const parser = new DOMParser();
const doc = parser.parseFromString(Step2_02_RoutesAsSVG, "application/xml");
console.log("Finished Step2_03_RoutesPathCommands");

return doc.querySelector(".polygon").getAttribute("d");
}
Insert cell
Insert cell
Step2_04_RoutesSAT = {
const paths = _Imports.Vector.mat.getPathsFromStr(
Step2_03_RoutesPathCommands
);
const mats = _Imports.Vector.mat.findMats(paths, 3);
const sats = mats.map((mat) => _Imports.Vector.mat.toScaleAxis(mat, 1.5));
console.log("Finished Step2_04_RoutesSAT");

return sats;
}
Insert cell
Insert cell
Step2_05_SATMultiLineStrings = {
const LsCoordinates = [];

const ReceivePathStr = (pathString) => {
if (pathString.length < 2) return;
const pathCoords = [pathString[0], pathString[pathString.length - 1]];
LsCoordinates.push([
[
pathCoords[0][0] - Step2_01_BboxUpperBounds[0] * -1,
pathCoords[0][1] - Step2_01_BboxUpperBounds[1] * -1
],
[
pathCoords[1][0] - Step2_01_BboxUpperBounds[0] * -1,
pathCoords[1][1] - Step2_01_BboxUpperBounds[1] * -1
]
]);
};

for (const MAT of Step2_04_RoutesSAT) {
if (!MAT.cpNode) return;
_Imports.Vector.mat.traverseEdges(MAT.cpNode, (cpNode) => {
if (_Imports.Vector.mat.isTerminating(cpNode)) return;
const bezier = _Imports.Vector.mat.getCurveToNext(cpNode);
if (bezier) ReceivePathStr(bezier);
});
}
console.log("Finished Step2_05_SATMultiLineStrings ");

return _Imports.Geospatial.turf.truncate(
_Imports.Geospatial.turf.multiLineString(LsCoordinates),
{ precision: 5, coordinates: 2 }
);
}
Insert cell
Insert cell
Insert cell
Step3_01_CoordMapping = {
const coordMapping = new Map();
const coordUnmapping = new Map();

for (const coord of _Imports.Geospatial.turf.coordAll(
Step2_05_SATMultiLineStrings
)) {
if (!coordMapping.has(coord.toString())) {
const cid = coordMapping.size + 1;
coordMapping.set(coord.toString(), cid);
coordUnmapping.set(cid, coord);
}
}
console.log("Finished Step3_01_CoordMapping ");

return { coordMapping, coordUnmapping };
}
Insert cell
Step3_02_GraphNetwork = {
const graph = _Imports.Graph.ngraph.graph();

const coordMapping = Step3_01_CoordMapping.coordMapping;

for (const coord of [...coordMapping.values()]) {
graph.addNode(coord);
}

const dedupSegs = new Set();
const uniqueSegs = [];

for (const lstr of Step2_05_SATMultiLineStrings.geometry.coordinates) {
const segid1 = [
coordMapping.get(lstr[0].toString()),
coordMapping.get(lstr[1].toString())
].toString();
const segid2 = [
coordMapping.get(lstr[1].toString()),
coordMapping.get(lstr[0].toString())
].toString();

if (
!dedupSegs.has(segid1) &&
!dedupSegs.has(segid2) &&
coordMapping.get(lstr[0].toString()) !==
coordMapping.get(lstr[1].toString())
) {
dedupSegs.add(segid1);
dedupSegs.add(segid2);
uniqueSegs.push(lstr);
}
}

for (const lstr of uniqueSegs) {
graph.addLink(
coordMapping.get(lstr[0].toString()),
coordMapping.get(lstr[1].toString())
);
}

console.log("Finished Step3_02_GraphNetwork");

return _Imports.Graph.ngraph.todot.default(graph);
}
Insert cell
Step3_02_Hubs = {
const graph = _Imports.Graph.ngraph.fromdot.default(Step3_02_GraphNetwork);

const hubs = new Map();
const hubsToBeRemoved = [];

graph.forEachNode((node) => {
if (node.links.size > 2) {
hubs.set(node.id, new Set(node.links.entries()));
hubsToBeRemoved.push(node.id);
}
});

hubsToBeRemoved.map((h) => {
graph.removeNode(h);
});
console.log("Finished Step3_02_Hubs");

return { hubs, graph: _Imports.Graph.ngraph.todot.default(graph) };
}
Insert cell
Step3_025_Hub2Hub = {
const graph = _Imports.Graph.ngraph.fromdot.default(Step3_02_Hubs.graph);

const HubsRTG = new Map();

const connections = new Map();

const ConnectionsReplacement = new Map();

for (const hub of [...Step3_02_Hubs.hubs.entries()]) {
for (const link of [...hub[1].values()].map((link) =>
[link[0].fromId, link[0].toId].filter((v) => v !== hub[0])
)) {
if (HubsRTG.has(hub[0])) {
HubsRTG.set(hub[0], [...HubsRTG.get(hub[0]), link[0]]);
} else {
HubsRTG.set(hub[0], [link[0]]);
}
}
}

for (const hub of [...HubsRTG.entries()]) {
const connection = hub[1]
.map((v) => HubsRTG.has(v))
.map((v, i) => (v ? hub[1][i] : null))
.filter((v) => v !== null);
if (connection.length > 0) {
connections.set(hub[0], connection[0]);
graph.removeNode(connection[0]);
}
}

for (const connection of [...connections.entries()]) {
ConnectionsReplacement.set(
connection[0],
_Imports.Geospatial.turf.midpoint(
Step3_01_CoordMapping.coordUnmapping.get(connection[0]),
Step3_01_CoordMapping.coordUnmapping.get(connection[1])
).geometry.coordinates
);
}

console.log("Finished Step3_025_Hub2Hub");

return {
ConnectionsReplacement,
graph: _Imports.Graph.ngraph.todot.default(graph)
};
}
Insert cell
Step3_03_GraphWalked = {
const graph = _Imports.Graph.ngraph.fromdot.default(Step3_025_Hub2Hub.graph);
for (const hubId of [...Step3_02_Hubs.hubs.keys()]) {
graph.removeNode(hubId);
}

const paths = [];

const WalkToFindPath = (nodeIds) => {
const nextLink = [
...graph.getNode(nodeIds[nodeIds.length - 1]).links.values()
]
.map((link) => [link.fromId, link.toId])
.flat()
.filter((v) => !nodeIds.includes(v));
return nextLink[0];
};

graph.forEachNode((node) => {
if (node.links !== null && node.links.size == 1) {
if (
paths.filter((path) => path[path.length - 1] == node.id).length == 0
) {
const nodeIds = [node.id];
while (nodeIds[nodeIds.length - 1] !== undefined) {
nodeIds.push(WalkToFindPath(nodeIds));
}
paths.push(nodeIds.slice(0, nodeIds.length - 1));
}
}
});
console.log("Finished Step3_03_GraphWalked");

return paths;
}
Insert cell
Step3_04_AddHubs = {
const HubsRTG = new Map();
const paths = [];

for (const hub of [...Step3_02_Hubs.hubs.entries()]) {
for (const link of [...hub[1].values()]
.map((l) => l[0])
.map((l) => [l.fromId, l.toId])
.flat()
.filter((v) => v !== hub[0])) {
HubsRTG.set(link, hub[0]);
}
}

for (const incompletePath of Step3_03_GraphWalked) {
paths.push(
[
HubsRTG.get(incompletePath[0]),
...incompletePath,
HubsRTG.get(incompletePath[incompletePath.length - 1])
].filter((v) => v !== undefined)
);
}
console.log("Finished Step3_04_AddHubs");

return paths;
}
Insert cell
Step3_05_GetSegments = {
const LineStrings = [];

for (const path of Step3_04_AddHubs) {
LineStrings.push(
_Imports.Geospatial.turf.lineString(
path.map((id) => {
if (Step3_025_Hub2Hub.ConnectionsReplacement.has(id)) {
return Step3_025_Hub2Hub.ConnectionsReplacement.get(id);
} else {
return Step3_01_CoordMapping.coordUnmapping.get(id);
}
}),
{
id: LineStrings.length + 1
}
)
);
}

console.log("Finished Step3_05_GetSegments");

return _Imports.Geospatial.turf.truncate(
_Utils.Generate.GeoJSON("FeatureCollection", LineStrings, {}),
{ precision: 5, coordinates: 2 }
);
}
Insert cell
{
const container = yield htl.html`<div style="height: 1000px;">`;
const map = L.map(container);
const layer = L.geoJSON(Step3_05_GetSegments, {
style: function (f) {
return {
color: "blue",
weight: 4
};
}
}).addTo(map);
map.fitBounds(layer.getBounds(), { maxZoom: 9 });
L.tileLayer(
"https://tiles.stadiamaps.com/tiles/stamen_toner-lite/{z}/{x}/{y}.png",
{
attribution:
"© <a href=https://www.openstreetmap.org/copyright>OpenStreetMap</a> contributors"
}
).addTo(map);
}
Insert cell
Insert cell
Step4_01_ExplodeLines = {
const ExplodedLines = _Imports.Geospatial.turf.featureCollection(
Step3_05_GetSegments.features
.map((line) => {
return line.geometry.coordinates.map((coord, idx) =>
_Imports.Geospatial.turf.point(coord, {
prt: line.properties.id,
idx
})
);
})
.flat()
);
console.log("Finished Step4_01_ExplodeLines");
return ExplodedLines;
}
Insert cell
Step4_02_ReconstructGraph = {

const graph = _Imports.Graph.ngraph.graph();

const coordMapping = new Map();
const coordUnmapping = new Map();

for (const coord of _Imports.Geospatial.turf.coordAll(Step3_05_GetSegments)) {
if (!coordMapping.has(coord.toString())) {
const cid = coordMapping.size + 1;
coordMapping.set(coord.toString(), cid);
coordUnmapping.set(cid, coord);
graph.addNode(cid);
}
}

const dedupSegs = new Set();
const uniqueSegs = [];

Step3_05_GetSegments.features.map((line) => {
line.geometry.coordinates.reduce((acc, cur, idx, arr) => {
const id = coordMapping.get(cur.toString());
const segid1 = [id, acc].toString();
const segid2 = [acc, id].toString();
if (!dedupSegs.has(segid1) && !dedupSegs.has(segid2) && id !== acc) {
dedupSegs.add(segid1);
dedupSegs.add(segid2);
graph.addLink(id, acc);
}
return id;
}, coordMapping.get(line.geometry.coordinates[0].toString()));
});
console.log("Finished Step4_02_ReconstructGraph");

return {
coordMapping,
coordUnmapping,
graph: _Imports.Graph.ngraph.todot.default(graph)
};
}
Insert cell
Step4_03_Walk = {
console.group("Starting Step4_03_Walk");

const routemappings = new Map();

for (const route of Input_SydneyTransitLinesGeoJSON.features) {
console.groupCollapsed("Processing route:", route.properties.title);

const AlongCoords = [route.geometry.coordinates[0]];
let distance = 0.25,
reachedEnd = false;
while (reachedEnd == false) {
const prevPoint = AlongCoords[AlongCoords.length - 1];
const nextPoint = _Imports.Geospatial.turf.along(route, distance, {
units: "kilometers"
}).geometry.coordinates;
if (prevPoint[0] == nextPoint[0] && prevPoint[1] == nextPoint[1]) {
reachedEnd = true;
} else {
AlongCoords.push(nextPoint);
distance += 0.25;
}
}
console.log("Finish Walk");

const closestPoints = AlongCoords.map((coord) => {
const closestPoint = _Imports.Geospatial.turf.nearestPoint(
coord,
Step4_01_ExplodeLines
);
return closestPoint;
});

console.log("Processed closest points");

routemappings.set(route.properties.title, {
points: closestPoints.map((p) =>
Step4_02_ReconstructGraph.coordMapping.get(
p.geometry.coordinates.toString()
)
),
route: route
});
console.groupEnd();
}
console.groupEnd();
console.log("Finished Step4_03_Walk");
return routemappings;
}
Insert cell
Step4_04_Pathfind = {
const graph = _Imports.Graph.ngraph.fromdot.default(
Step4_02_ReconstructGraph.graph
);

const pathFinder = _Imports.Graph.ngraph.path.aStar(graph);

const routemappings = new Map();

for (const route of [...Step4_03_Walk.values()]) {
const path = [];
route.points.reduce((acc, cur, idx, arr) => {
const found = pathFinder.find(cur, acc).map((n) => n.id);
if (path.length == 0) {
path.push(...found);
} else {
path.push(...found.splice(1, found.length - 1));
}
return cur;
}, route.points[0]);

routemappings.set(route.route.properties.title, {
path,
route: route.route
});
}
console.log("Finished Step4_04_Pathfind");

return routemappings;
}
Insert cell
Step4_05_Reconstruct = {
const routes = [];
for (const route of [...Step4_04_Pathfind.values()]) {
routes.push(
_Imports.Geospatial.turf.lineString(
route.path.map((id) =>
Step4_02_ReconstructGraph.coordUnmapping.get(id)
),
route.route.properties
)
);
}
console.log(
"Processed",
routes.length,
routes.length == 1 ? "route" : "routes"
);
return _Imports.Geospatial.turf.truncate(
_Utils.Generate.GeoJSON("FeatureCollection", routes, {}),
{ precision: 5, coordinates: 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