SydneyTrainRoutes_ClSimplification_UnifiedRoutes = {
console.groupCollapsed("Unifying routes...");
const Graph = _Imports.Graph.ngraph.graph();
const PointsExploded = _Imports.Geospatial.turf.explode(
SydneyTrainRoutes_ClSimplification_GapReducer
);
for (const point of PointsExploded.features) {
Graph.addNode(String(point.geometry.coordinates.join(",")), point);
}
for (const link of SydneyTrainRoutes_ClSimplification_GapReducer.features) {
Graph.addLink(
String(link.geometry.coordinates[0].join(",")),
String(link.geometry.coordinates[1].join(",")),
{
coordinates: [
link.geometry.coordinates[0],
link.geometry.coordinates[1]
]
}
);
Graph.addLink(
String(link.geometry.coordinates[1].join(",")),
String(link.geometry.coordinates[0].join(",")),
{
coordinates: [
link.geometry.coordinates[1],
link.geometry.coordinates[0]
]
}
);
}
console.log("Preparing to combine", PointsExploded.feature, "nodes");
const CoordCompare = (c1, c2) => {
return c1[0] == c2[0] && c1[1] == c2[1];
};
const weirdnodes = [];
const weirdlinks = [];
const junctions = [];
console.groupCollapsed("Weird Nodes 1");
Graph.forEachNode((node) => {
if (node.links.size < 4) {
console.log("weird node found - probably a terminus", node);
weirdnodes.push({
...node.data,
properties: {
links: node.links.size,
"marker-color": "#ffff00"
}
});
} else if (node.links.size > 4) {
console.log("weird node found - probably a junction", node);
weirdnodes.push({
...node.data,
properties: { links: node.links.size, "marker-color": "#00ffff" }
});
junctions.push(node.id);
}
//ClipReportTimes.push(performance.now() - PrevClipTime);
//PrevClipTime = performance.now();
});
console.groupEnd("Weird Nodes 1");
const MergeLinks = (iter) => {
const PerfMerge = new PerfMeasurement(`Combine_LinePairs_${iter}`);
console.log("Merging - Iteration", iter);
const MergeResults = {
skipped: 0,
success: 0,
failed: 0
};
const IterDbg = [0, PointsExploded.length * 2, 0];
Graph.forEachNode((node) => {
PerfMerge.Start();
let kickout = false;
if (node.links.size !== 4 || IterDbg[2] >= IterDbg[1]) {
MergeResults.skipped++;
kickout = true;
PerfMerge.End();
return;
}
//console.groupCollapsed("🔃 Identified node to merge -", node.id);
//console.log(node.id, node.data, node.links);
const CurLinks = [];
const Partners = new Set();
for (const link of node.links) {
if (link.toId == node.id) {
//console.log("Inbound Link from", link.fromId, link.data);
//CurLinks.push(link);
Partners.add(link.fromId);
} else if (link.fromId == node.id) {
//console.log("Outbound Link to", link.toId, link.data);
//CurLinks.push(link);
Partners.add(link.toId);
}
}
//console.log("Replacing with link pairs");
for (const partner of Partners) {
//console.groupCollapsed("🔃 Merge -", partner);
//console.log("Link partner", partner);
const NewCoordData = [];
let OtherNodeId = "";
for (const link of node.links) {
if (kickout == true) break;
if (link.fromId == partner && link.toId == node.id) {
//console.log("Potential merge A", link);
const LinkDataCoords = link.data.coordinates;
if (junctions.includes(partner)) {
console.log(
"⛔ Merge A: Junctions - Attempted to merge with a Junction",
LinkDataCoords
);
kickout = true;
}
if (
!CoordCompare(
LinkDataCoords[0],
Graph.getNode(partner).data.geometry.coordinates
)
) {
LinkDataCoords.reverse();
if (
!CoordCompare(
LinkDataCoords[0],
Graph.getNode(partner).data.geometry.coordinates
)
) {
console.log(
"⛔ Merge A: Coordinates - Still aren't ordered correctly.",
LinkDataCoords
);
kickout = true;
}
}
if (
!CoordCompare(
LinkDataCoords[LinkDataCoords.length - 1],
node.data.geometry.coordinates
)
) {
console.log(
"⛔ Merge A: Coordinates don't end at node to be removed",
LinkDataCoords,
node.data.geometry.coordinates,
LinkDataCoords[LinkDataCoords.length - 1]
);
kickout = true;
} else {
CurLinks.push(link);
}
NewCoordData[0] = LinkDataCoords;
}
if (link.fromId == node.id && link.toId !== partner) {
//console.log("Potential merge B", link);
OtherNodeId = link.toId;
const LinkDataCoords = link.data.coordinates;
if (junctions.includes(OtherNodeId)) {
console.log(
"⛔ Merge B: Junctions - Attempted to merge with a Junction",
LinkDataCoords
);
kickout = true;
}
if (
!CoordCompare(
LinkDataCoords[LinkDataCoords.length - 1],
Graph.getNode(OtherNodeId).data.geometry.coordinates
)
) {
LinkDataCoords.reverse();
if (
!CoordCompare(
LinkDataCoords[LinkDataCoords.length - 1],
Graph.getNode(OtherNodeId).data.geometry.coordinates
)
) {
console.log(
"⛔ Merge B: Coordinates still aren't ordered correctly",
LinkDataCoords,
node.data.geometry.coordinates
);
kickout = true;
}
}
if (
!CoordCompare(LinkDataCoords[0], node.data.geometry.coordinates)
) {
console.log(
"⛔ Merge B: Coordinates don't start at node to be removed",
LinkDataCoords,
node.data.geometry.coordinates,
LinkDataCoords[LinkDataCoords.length - 1]
);
kickout = true;
} else {
CurLinks.push(link);
}
NewCoordData[1] = LinkDataCoords;
}
}
if (kickout == false) {
Graph.addLink(String(partner), String(OtherNodeId), {
coordinates: NewCoordData.flat()
});
/*console.log(
"🟢 Link merged:",
NewCoordData.flat(),
"\n➡️ From:",
partner,
"\n⬅️ To:",
OtherNodeId
);
} else {*/
}
//console.groupEnd("🔃 Merge -", partner);
}
if (kickout == false) {
//console.log("Successfully merged!");
CurLinks.map((l) => Graph.removeLink(l));
Graph.removeNode(node.id);
MergeResults.success++;
} else {
//console.log("⛔ Kickout engaged. Node error, see logs.");
MergeResults.failed++;
}
IterDbg[2]++;
//console.groupEnd("🔃 Identified node to merge -", node.id);
PerfMerge.End();
});
//console.groupEnd("Merging - Iteration", iter);
return MergeResults;
};
const Results = {
skipped: [0],
success: [0],
failed: [0],
iters: 0,
updates: true
};
while (Results.updates == true) {
const newResults = MergeLinks(Results.iters);
if (
newResults.skipped !== Results.skipped[Results.skipped.length - 1] ||
newResults.success !== Results.success[Results.success.length - 1] ||
newResults.failed !== Results.failed[Results.failed.length - 1]
) {
Results.updates = true;
Results.skipped.push(newResults.skipped);
Results.success.push(newResults.success);
Results.failed.push(newResults.failed);
} else {
Results.updates = false;
}
Results.iters++;
}
/*console.table({
Skipped: Results.skipped,
Successful: Results.success,
Failed: Results.failed,
Iterations: Results.iters
});*/
console.groupCollapsed("Weird Nodes");
Graph.forEachNode((node) => {
if (node.links.size < 4) {
console.log("weird node found - probably a terminus", node);
weirdnodes.push({
...node.data,
properties: {
links: node.links.size,
"marker-color": "#ff0000"
}
});
} else if (node.links.size > 4) {
console.log("weird node found - probably a junction", node);
weirdnodes.push({
...node.data,
properties: { links: node.links.size, "marker-color": "#0000ff" }
});
}
});
console.groupEnd("Weird Nodes");
console.groupCollapsed("Weird Links");
Graph.forEachLink(function (link) {
if (link.data.coordinates.length < 4) {
console.log("weird link found - probably a single link", link);
weirdlinks.push(
_Imports.Geospatial.turf.lineString(link.data.coordinates, {
stroke: "#ff0000"
})
);
}
});
console.groupEnd("Weird Links");
/*return _Utils.Generate.GeoJSON("FeatureCollection", [
...SydneyTrainRoutes_ClSimplification_GapReducer.features,
...weirdnodes
]);*/
const ReturnGeometry = [];
Graph.forEachLink(function (link) {
ReturnGeometry.push(
_Imports.Geospatial.turf.lineString(link.data.coordinates)
);
});
console.groupEnd("Unifying routes...");
return _Utils.Generate.GeoJSON("FeatureCollection", [
...ReturnGeometry
//...weirdnodes,
//...weirdlinks
]);
}