Public
Edited
Feb 6, 2024
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Step1_01_BufferedRoutes = {
const buffered_features = [];

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

return _Utils.Generate.GeoJSON("FeatureCollection", buffered_features, {});
}
Insert cell
Insert cell
Step1_02_MergedRoutes = {
const combined_routes = _Imports.Geospatial.turf.truncate(
_Imports.Geospatial.turf.combine(Step1_01_BufferedRoutes),
{ precision: 6, coordinates: 2 }
);

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

return merged_routes;
}
Insert cell
Insert cell
Insert cell
Insert cell
Step2_01_DensifiedRoute = {

console.log(
"DENSIFY: Operation started"
);
const start_time = performance.now();

const new_windings = [];

const ChunkSize = 0.025;

for (const coord_segment of Step1_02_MergedRoutes.geometry.coordinates) {
const LineString = _Imports.Geospatial.turf.lineString(coord_segment);

if (
_Imports.Geospatial.turf.length(LineString, {
units: "kilometers"
}) > ChunkSize
) {
const densified_seg = _Imports.Geospatial.turf.lineChunk(
LineString,
ChunkSize,
{
units: "kilometers"
}
);

const flattened_seg = _Imports.Geospatial.turf.lineString(
densified_seg.features.map((f) => f.geometry.coordinates).flat()
);
new_windings.push(
_Imports.Geospatial.turf.cleanCoords(flattened_seg).geometry.coordinates
);
} else {
new_windings.push(LineString.geometry.coordinates);
}
}

console.log(
"DENSIFY: ended in",
(performance.now() - start_time).toFixed(3) / 1000,
"seconds"
);

return _Imports.Geospatial.turf.truncate(
_Utils.Generate.GeoJSON(
"Feature",
{ type: "Polygon", coordinates: new_windings },
{}
),
{ precision: 6, coordinates: 2 }
);
}
Insert cell
Insert cell
Insert cell
Step3_01_TesselatedRoutes = {
console.log("TESSELATE: Operation started");

const start_time = performance.now();

const points = _Imports.Geospatial.turf.explode(Step2_01_DensifiedRoute);
const raw_triangles = _Imports.Geospatial.geoDelaunay
.geoVoronoi(points)
.triangles();

const cutoff_area = [250, 5000];
const triangles = [];

for (const triangle of raw_triangles.features) {
const triangle_area = _Imports.Geospatial.turf.area(triangle);
if (triangle_area >= cutoff_area[0] && triangle_area <= cutoff_area[1])
triangles.push(triangle);
}

console.log(
"TESSELATE: ended in",
(performance.now() - start_time).toFixed(3) / 1000,
"seconds"
);

return _Utils.Generate.GeoJSON("FeatureCollection", triangles, {});
}
Insert cell
viewof MapView_03_TesselatedRoutes = {
await visibility();
const map = new _MapLibre({ height: 1000 });
yield map.attach;
map.AddDataSource("t_inside", Step3_01_TesselatedRoutes);
map.AddMapPolygon("t_inside", "t_inside", "#088", 0.25);
map.AddMapLine("t_inside_outline", "t_inside", "#088", 1);
invalidation.then(map.kill);
}
Insert cell
Insert cell
Insert cell
Step4_01_CleanedTesselations = {
console.log(
"TESSELATE-CLEANUP: Operation started"
);
const start_time = performance.now();

const triangles_inside = [];

for (const triangle of Step3_01_TesselatedRoutes.features) {
const centerOfMass =
_Imports.Geospatial.turf.centerOfMass(triangle).geometry.coordinates;
const isInside = _Imports.Geospatial.turf.booleanPointInPolygon(
centerOfMass,
Step2_01_DensifiedRoute,
{ ignoreBoundary: false }
);
if (isInside) {
triangle.properties.centerOfMass = centerOfMass;
delete triangle.properties.circumcenter;
triangles_inside.push(triangle);
}
}

console.log(
"TESSELATE-CLEANUP: ended in",
(performance.now() - start_time).toFixed(3) / 1000,
"seconds"
);

return _Utils.Generate.GeoJSON("FeatureCollection", triangles_inside, {});
}
Insert cell
viewof MapView_04_TesselatedRoutes = {
await visibility();
const map = new _MapLibre({ height: 1000 });
yield map.attach;
map.AddDataSource("t_inside", Step4_01_CleanedTesselations);
map.AddMapPolygon("t_inside", "t_inside", "#088", 0.25);
map.AddMapLine("t_inside_outline", "t_inside", "#088", 1);
invalidation.then(map.kill);
}
Insert cell
Insert cell
Insert cell
Step5_01_MapRouteToTesselatedCenter = (route) => {
const route_line_orig = route;

// get all triangles that the route overlaps
const route_triangles_overlap = [];
for (const triangle of Step4_01_CleanedTesselations.features) {
if (
_Imports.Geospatial.turf.lineIntersect(route_line_orig, triangle).features
.length > 0
) {
route_triangles_overlap.push(triangle);
}
}

// densify the original route to every 5m
const route_line_densified = _Imports.Geospatial.turf.lineChunk(
route_line_orig,
0.001,
{ units: "kilometers" }
);

const exploded_line_points = [];

for (const seg of route_line_densified.features) {
exploded_line_points.push(
//...seg.geometry.coordinates.map((p) => _Imports.Geospatial.turf.point(p))
...seg.geometry.coordinates
);
}

const exploded_line_points_cleaned = _Imports.Geospatial.turf.cleanCoords(
_Imports.Geospatial.turf.lineString(exploded_line_points)
).geometry.coordinates;

const new_line_points = [];

for (const point of exploded_line_points_cleaned) {
for (const triangle of route_triangles_overlap) {
if (_Imports.Geospatial.turf.booleanPointInPolygon(point, triangle)) {
new_line_points.push(triangle.properties.centerOfMass);
/*console.log(
"Run iter",
new_line_points.length,
"of",
exploded_line_points_cleaned.length
);*/
break;
}
}
}

const output_linestring = _Imports.Geospatial.turf.cleanCoords(
_Imports.Geospatial.turf.lineString(new_line_points)
);

return output_linestring;
}
Insert cell
Step5_01_ExperimentalForAPS1c = {
/*
const route_line_orig = Input_SydneyTransitLinesGeoJSON.features[0];

const start_time = performance.now();

const output_linestring =
Step5_01_MapRouteToTesselatedCenter(route_line_orig);

console.log(
"ended in",
(performance.now() - start_time).toFixed(3) / 1000,
"seconds"
);

return output_linestring;

*/

const routes = [];

const op_start_time = performance.now();
const opt_times = [];

console.group(
"ROUTE-NORMALISATION:",
Input_SydneyTransitLinesGeoJSON.features.length,
"routes"
);
for (const route of Input_SydneyTransitLinesGeoJSON.features) {
const start_time = performance.now();
const output_linestring = Step5_01_MapRouteToTesselatedCenter(route);

const opt_time = performance.now() - start_time;
opt_times.push(opt_time / _Imports.Geospatial.turf.length(route));

const opt_average = opt_times.reduce((a, b) => a + b) / opt_times.length;

console.log(
"Route",
`${routes.length}:`,
"Ended in",
opt_time.toFixed(3) / 1000,
"seconds",
`(${
route.geometry.coordinates.length
} input points, ${_Imports.Geospatial.turf.length(route)}km length, ${
(opt_average * _Imports.Geospatial.turf.length(route)).toFixed(3) / 1000
} seconds estimated`
);
routes.push(output_linestring)
; }

console.log(
Input_SydneyTransitLinesGeoJSON.features,
"routes: Ended in",
(performance.now() - op_start_time).toFixed(3) / 1000,
"seconds"
);

console.groupEnd();

return _Utils.Generate.GeoJSON("FeatureCollection", routes, {});
}
Insert cell
viewof MapView_05_TesselatedRoutes = {
await visibility();
const map = new _MapLibre({ height: 1000 });
yield map.attach;
map.AddDataSource("t_inside", Step4_01_CleanedTesselations);
map.AddMapPolygon("t_inside", "t_inside", "#088", 0.25);
map.AddMapLine("t_inside_outline", "t_inside", "#088", 1);
map.AddDataSource("line", Step5_01_ExperimentalForAPS1c);
map.AddMapLine("line_outline", "line", "#ff0000", 1);
invalidation.then(map.kill);
}
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