Published
Edited
Aug 22, 2021
Insert cell
Insert cell
Insert cell
Insert cell
joinedLineString = ({
type: "LineString",
properties: selectedLineProperties,
coordinates: joinedLine
})
Insert cell
currentSegmentJoiner = new SegmentJoiner(selectedLineCoords)
Insert cell
[...currentSegmentJoiner.orderedIndicesPerLine.values()].filter(
(d) => d.length > 1
)
Insert cell
joinedLine = orderedLinesTest[0]
Insert cell
orderedLinesTest = {
currentSegmentJoiner.joinAllSegments();

return currentSegmentJoiner.orderAndMergeSegments();
}
Insert cell
// can't really handle quadruple welds
// TODO: static function to get array of segments from any geoJSON object?
class SegmentJoiner {
constructor(segments) {
if (!segments)
throw Error("Missing required argument to parameter 'segments'");
if (!(segments instanceof Array))
throw "Argument to parameter 'segments' must be an array";
this.segments = segments;
this.segmentsCopy = segments.map((d) => d.map((e) => [...e]));
this.segmentMap = this.groupBySegment(segments);
this.weldMap = this.groupByWelds(segments);
this.weldMapCopy = this.groupByWelds(segments);
this.orderedIndicesPerLine = new Map();
this.startSegments = this.findStartSegments();
}

get() {
return this.weldMap;
}

get getWeldMap() {
return this.weldMap;
}

get getWeldMapCopy() {
return this.weldMapCopy;
}

get getSegmentMap() {
return this.segmentMap;
}

// to find start/end coords of a segment
groupBySegment(segments) {
const startEndPerSegment = new d3.InternMap([], JSON.stringify);

// save start and end coord of each segments to InternMap
segments.forEach((coords, i) => {
const start = coords[0];

const { length } = coords;
const end = coords[length - 1];

startEndPerSegment.set(i, { start, end });
});

return startEndPerSegment;
}

groupByWelds(segments) {
const coordsMap = new d3.InternMap([], JSON.stringify);

// Direction of segments is not always the same
segments.map((coords, i) => {
const { length } = coords;
const startNode = coords[0];
const endNode = coords[length - 1];

if (coordsMap.has(startNode)) {
if (coordsMap.get(startNode).has("startOf")) {
coordsMap.get(startNode).set("alsoStartOf", i);
} else {
coordsMap.get(startNode).set("startOf", i);
}
} else {
coordsMap.set(startNode, new Map([["startOf", i]]));
}

if (coordsMap.has(endNode)) {
if (coordsMap.get(endNode).has("endOf")) {
coordsMap.get(endNode).set("alsoEndOf", i);
} else {
coordsMap.get(endNode).set("endOf", i);
}
} else {
coordsMap.set(endNode, new Map([["endOf", i]]));
}
});

return coordsMap;
}

// 1. find welds with only start (or only end)
findStartSegments() {
const starts = [];

for (const [coord, startEndOfSegmentMap] of this.weldMapCopy) {
if (startEndOfSegmentMap.size === 1) {
if (startEndOfSegmentMap.has("startOf")) {
starts.push({
index: startEndOfSegmentMap.get("startOf"),
reverse: false
});
console.log("start segment:", startEndOfSegmentMap.get("startOf"));
}
}
}

// only if there's none with only startOf in the weldMap
for (const [coord, startEndOfSegmentMap] of this.weldMapCopy) {
if (startEndOfSegmentMap.size === 1) {
if (startEndOfSegmentMap.has("endOf")) {
console.log(
"Reversed, segment has no startOf at ends",
startEndOfSegmentMap.get("endOf")
);
starts.push({
index: startEndOfSegmentMap.get("endOf"),
reverse: true
});
}
}
}

return starts;
}

findNextSegment(weld, prevSegment) {
if (!weld || !prevSegment) {
return "Not enough args to findNextSegment :(";
}

console.log(
"finding next segment of weld",
{ weld },
"with prev segment",
prevSegment
);
const segmentsOfWeld = this.weldMap.get(weld);

// TODO: could pass in the previous end type and delete that from the map with needing to iterate over it
for (const [termination, segmentIndex] of segmentsOfWeld) {
console.log(termination, prevSegment.index);
if (segmentIndex === prevSegment.index) {
console.log("Remove previous segment index", segmentIndex);
segmentsOfWeld.delete(termination);
}
}

if (segmentsOfWeld.has("startOf")) {
console.log("Normal direction");
const index = segmentsOfWeld.get("startOf");
segmentsOfWeld.delete("startOf");
return { index, reverse: false };
}

if (segmentsOfWeld.has("endOf")) {
console.log("Reverse direction");
const index = segmentsOfWeld.get("endOf");
segmentsOfWeld.delete("endOf");
return { index, reverse: true };
}

if (segmentsOfWeld.has("alsoStartOf")) {
console.log("Again normal direction");
const index = segmentsOfWeld.get("alsoStartOf");
segmentsOfWeld.delete("alsoStartOf");
return { index, reverse: false };
}

if (segmentsOfWeld.has("alsoEndOf")) {
console.log("Again reverse direction");
const index = segmentsOfWeld.get("alsoEndOf");
segmentsOfWeld.delete("alsoEndOf");
return { index, reverse: true };
}

// this must be the end (or some condition I did not think of ¯\_(ツ)_/¯)
return false;
}

findNextWeld(segment) {
console.log("finding next weld of segment", segment);
const nextWeld = this.segmentMap.get(segment.index);

// special case for the first node?

if (!segment.reverse) return nextWeld.end;

return nextWeld.start; // TODO: or alsoStartOf
}

// recursive function
joinSegments(currentWeld, currentSegmentIndex, i) {
let nextSegment = this.findNextSegment(currentWeld, currentSegmentIndex);

if (nextSegment === false) {
console.log(
`Segment with index ${currentSegmentIndex.index} is is the final segment of this Line`
);
return;
}

this.orderedIndicesPerLine.get(i).push(nextSegment);
let nextWeld = this.findNextWeld(nextSegment);

this.joinSegments(nextWeld, nextSegment, i);
}

joinAllSegments() {
this.startSegments.forEach((firstSegmentIndex, i) => {
const firstWeld = this.findNextWeld(firstSegmentIndex);
this.orderedIndicesPerLine.set(i, [firstSegmentIndex]);

this.joinSegments(firstWeld, firstSegmentIndex, i);
});

return this.orderedIndicesPerLine;
}

orderAndMergeSegments() {
// if (this.orderedIndicesPerLine.size === 0)
// return "No ordered segment indices found";

const orderedLines = [];
console.log("orderAndMergeSegments");
for (const [i, sortedIndices] of this.orderedIndicesPerLine) {
console.log({ i, sortedIndices });
const orderedLine = sortedIndices
.map((seg) => {
if (seg.reverse) {
const reverseSeg = this.segments[seg.index].map((d) => [...d]);
console.log({ reverseSeg });
return reverseSeg.reverse();
} // mutates this.segments[seg]
return this.segments[seg.index];
})
.flat();

orderedLines.push(orderedLine);
}

return orderedLines; //.flat(); // remove double nodes too?
}
}
Insert cell
currentSegmentJoiner.orderedIndicesPerLine.size
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

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