Public
Edited
Mar 23
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Initial_Loading_time = {
console.log("Got buffered routes - starting timer", Step1_01_BufferedRoutes);
console.clear();
return performance.now();
}
Insert cell
Step1_01_BufferedRoutes = {
const buffered_features = [];
const buffer_distance = 0.02;
for (const feature of Input_SydneyTransitLinesGeoJSON.features) {
if (_Imports.Geospatial.turf.length(feature, km) < 1000) {
const buffered = _Imports.Geospatial.turf.buffer(
feature,
buffer_distance,
km
);
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",
SydneyTrainRoutes_PreUnionsied_Geometries.length,
"geometries"
);
} catch (e) {}
}
const union_geometry = JSTS_IO_HANDLERS.gjwriter.write(ConstantGeometry);
const union_feature = _Utils.Generate.GeoJSON("Feature", union_geometry, {});
return union_feature;
}
Insert cell
SydneyTrainRoutes_SimplifiedUnionised_Geometry = _Imports.Geospatial.turf.simplify(
SydneyTrainRoutes_Unionised_Geometry,
{
tolerance: 0.0001,
highQuality: false
}
)
Insert cell
Insert cell
Step2_01_BboxUpperBounds = {
const UpperBounds = _Imports.Geospatial.turf.bboxPolygon(
_Imports.Geospatial.turf.bbox(SydneyTrainRoutes_SimplifiedUnionised_Geometry)
).geometry.coordinates[0][3];
console.log("Finished Step2_01_BboxUpperBounds");
return UpperBounds;
}
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(
SydneyTrainRoutes_SimplifiedUnionised_Geometry
)
)
.render();
console.log("Finished Step2_02_RoutesAsSVG");
return RASVG;
}
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
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
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
{
const container = yield htl.html`<div style="height: 500px;">`;
const map = L.map(container);
const layer = L.geoJSON(Step2_05_SATMultiLineStrings).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
CoordsHelper = {
let idx = 0;
const coordsToIdx = new Map();
const idxToCoords = new Map();

function serialise(a, b) {
return `1:${a},2:${b}`;
}

function add(coord) {
const lon = parseFloat(coord[0]);
const lat = parseFloat(coord[1]);

if (coordsToIdx.has(serialise(lon, lat)))
return coordsToIdx.get(serialise(lon, lat));
else {
const i = `C:${idx++}`;
coordsToIdx.set(serialise(lon, lat), i);
idxToCoords.set(i, [lon, lat]);
return i;
}
}

function get(coord) {
return idxToCoords.get(coord);
}

return { add, get, coordsToIdx };
}
Insert cell
CoordString = {
return function (coord) {
return CoordsHelper.add(coord);
};
}
Insert cell
DeserialiseCoordString = {
return function (coord) {
return CoordsHelper.get(coord);
};
}
Insert cell
SerialiseGraph = {
return function (graph) {
return JSON.parse(_Imports.Graph.ngraph.tojson.default(graph));
};
}
Insert cell
DeerialiseGraph = {
return function (graph) {
return _Imports.Graph.ngraph.fromjson.default(JSON.stringify(graph));
};
}
Insert cell
Step3_GetCoordinates = {
const coords = new Map();

for (const coord of _Imports.Geospatial.turf.coordAll(
Step2_05_SATMultiLineStrings
)) {
if (coords.has(CoordString(coord))) {
coords.set(CoordString(coord), coords.get(CoordString(coord)) + 1);
} else {
coords.set(CoordString(coord), 1);
}
}

const coordsValues = new Map();

for (const val of [...coords.values()]) {
if (coordsValues.has(val)) {
coordsValues.set(val, coordsValues.get(val) + 1);
} else {
coordsValues.set(val, 1);
}
}

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

// node types are "single" "double" "hub"

for (const [coord, num] of [...Step3_GetCoordinates.coords.entries()]) {
graph.addNode(coord);
}
return SerialiseGraph(graph);
}
Insert cell
Insert cell
Insert cell
Insert cell
{
const container = yield htl.html`<div style="height: 700px;">`;
const map = L.map(container);
const layer = L.geoJSON(Links_as_debug_geoJSON, {
style: function (f) {
return {
color: f.properties.length < 0.075 ? "#ff0000" : "#000000"
};
}
}).addTo(map);
map.fitBounds(layer.getBounds(), { maxZoom: 9 });
}
Insert cell
Step3_03_CombinedHubs = {
const graph = DeerialiseGraph(Step3_03_IteratedLinks);
console.log("combining hubs");

const get_last = (arr) => arr[arr.length - 1];

graph.forEachNode(function (node) {
node.data = {
appears: node.links.length,
type:
node.links.length == 1
? "single"
: node.links.length == 2
? "double"
: "hub"
};
});

graph.forEachLink(function (link) {
if (!link) return;
if (link.fromId == link.toId) {
graph.removeLink(link);
}
});
function run() {
let idx = 0;
let were_changes_made = false;

graph.forEachLink(function (link) {
if (!link) return;
const lstr = _Imports.Geospatial.turf.lineString(
link.data.absorbed.map((c) => DeserialiseCoordString(c)),
link.data
);

const length = _Imports.Geospatial.turf.length(lstr, km);
const node_first = graph.getNode(link.data.absorbed[0]);
const node_last = graph.getNode(get_last(link.data.absorbed));

if (
node_first.data.type == "hub" &&
node_last.data.type == "hub" &&
length < 0.075
) {

// do thingy here

const first_point = graph.getNode(link.data.absorbed[0]);
const last_point = graph.getNode(get_last(link.data.absorbed));

const midpoint = _Imports.Geospatial.turf.midpoint(
DeserialiseCoordString(first_point.id),
DeserialiseCoordString(last_point.id)
);

const midpoint_cs = CoordString(midpoint.geometry.coordinates);

graph.addNode(midpoint_cs, {
appears: 0,
type: "hub"
});


//first node

first_point.links
.filter((l) => l.id !== link.id)
.map((l) => {
const new_absorbed = l.data.absorbed.map((p) => {
if (p == first_point.id) return midpoint_cs;
else return p;
});
graph.removeLink(l);
graph.addLink(new_absorbed[0], get_last(new_absorbed), {
absorbed: new_absorbed
});
});

last_point.links
.filter((l) => l.id !== link.id)
.map((l) => {
const new_absorbed = l.data.absorbed.map((p) => {
if (p == last_point.id) return midpoint_cs;
else return p;
});
graph.removeLink(l);
graph.addLink(new_absorbed[0], get_last(new_absorbed), {
absorbed: new_absorbed
});
});

// last node

link.data.absorbed.map((p) => {
graph.removeNode(p);
});

graph.removeLink(link);

were_changes_made = true;

console.groupEnd();
}
});

return were_changes_made;
}

let keepRunning = true;
while (keepRunning == true) {
keepRunning = run();
}

graph.forEachNode(function (node) {
node.data = {
appears: node.links.length,
type:
node.links.length == 1
? "single"
: node.links.length == 2
? "double"
: "hub"
};
});
console.log("Combined all hubs!");
return SerialiseGraph(graph);
}
Insert cell
skeleton_geojson = {
const graph = DeerialiseGraph(Step3_03_CombinedHubs);

const lines = [];
const get_last = (arr) => arr[arr.length - 1];

graph.forEachLink(function (link) {
const coords = link.data.absorbed.map((c) => DeserialiseCoordString(c));

const simplified_coords = _Imports.Geospatial["vis-why"].default(
coords,
Math.max(coords.length * 0.1, 2)
);

const smoothed_coords = _Imports.Geospatial.toSmooth.default(
simplified_coords,
{
iteration: 7,
factor: 0.75
}
);

const simplified2_coords = _Imports.Geospatial["vis-why"].default(
simplified_coords,
Math.max(coords.length * 0.5, 2)
);

const lstr = _Imports.Geospatial.turf.lineString(
[coords[0], ...simplified2_coords, coords[coords.length - 1]]
//{
//id: link.id,
//...link.data
//}
);

lines.push(lstr);
});

console.log(
"time taken:",
Math.round(performance.now() - Initial_Loading_time) / 1000 + "s"
);

return _Imports.Geospatial.turf.truncate(
_Imports.Geospatial.turf.featureCollection(lines),
{ precision: 6, coordinates: 2 }
);
}
Insert cell
{
const container = yield htl.html`<div style="height: 1000px;">`;
const map = L.map(container);
const layer = L.geoJSON(skeleton_geojson, {
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"
).addTo(map);
}
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