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];
this.orderedSegmentIndices = [];
this.segmentMap = this.groupBySegment(segments);
this.weldMap = this.groupByWelds(segments);
}
get getWeldMap() {
return this.weldMap;
}
get getSegmentMap() {
return this.segmentMap;
}
groupBySegment(segments) {
const startEndPerSegment = new d3.InternMap([], JSON.stringify);
segments.map((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)
startSegments() {
const starts = [];
for (const [coordString, startEndOfSegmentMap] of this.weldMap) {
if (startEndOfSegmentMap.size === 1) {
if (startEndOfSegmentMap.has("startOf")) {
starts.push({
index: startEndOfSegmentMap.get("startOf"),
reverse: false
});
console.log("start segment:", startEndOfSegmentMap.get("startOf"));
// return { index: startEndOfSegmentMap.get("startOf"), reverse: false };
}
}
}
// only if there's none with only startOf in the weldMap
for (const [coordString, startEndOfSegmentMap] of this.weldMap) {
if (startEndOfSegmentMap.size === 1) {
if (startEndOfSegmentMap.has("endOf")) {
console.log(
"Reversed, MultiLine has no startOf at ends",
startEndOfSegmentMap.get("endOf")
);
starts.push({
index: startEndOfSegmentMap.get("endOf"),
reverse: true
});
// return { 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
}
// TODO: turn this into a recursive generator
// recursive function
joinSegments(currentWeld, currentSegmentIndex) {
let nextSegment = this.findNextSegment(currentWeld, currentSegmentIndex);
if (nextSegment === false)
return `${currentSegmentIndex} is is the final segmentIndex of this LineString`;
this.orderedSegmentIndices.push(nextSegment);
let nextWeld = this.findNextWeld(nextSegment);
// yield*
this.joinSegments(nextWeld, nextSegment);
}
joinAllSegments() {
this.orderedSegmentIndices = [];
const firstSegment = this.startSegment();
const firstWeld = this.findNextWeld(firstSegment);
this.orderedSegmentIndices.push(firstSegment);
this.joinSegments(firstWeld, firstSegment);
return this.mergeMultiLineString();
}
mergeMultiLineString() {
if (!this.orderedSegmentIndices.length) return "No segment indices found";
const orderedLineString = this.orderedSegmentIndices.map((seg) => {
if (seg.reverse) return this.segments[seg.index].reverse(); // mutates this.segments[seg]
return this.segments[seg.index];
});
return orderedLineString.flat(); // remove double nodes too?
}
}