TopoArcUtil = {
function isRing(arc) {
return eq(arc[0], arc.at(-1));
}
function eq(xy1, xy2) {
return (xy1 == xy2) || (xy1[0] == xy2[0] && xy1[1] == xy2[1]);
}
function isValueBetween(value, a, b) {
const min = (a < b) ? a : b;
const max = (min == a) ? b : a;
return value >= min && value <= max;
}
function isPointBetween(point, xy1, xy2) {
const [px, py] = point;
const [x1, y1] = xy1;
const [x2, y2] = xy2;
if ((py - y1) * (x2 - x1) != (y2 - y1) * (py - x1)) return false;
if (!isValueBetween(px, x1, x2)) return false;
if (!isValueBetween(py, y1, y2)) return false;
return true;
}
class TopoArcUtil {
constructor(topology) {
this.topology = topology;
this.shiftedRingArcIds = new Set();
this.arcs = this.topology.arcs;
const allGeometries = [];
Object.values(topology.objects).forEach(o => turf.geomEach(o, g => allGeometries.push(g)));
const allArcArrays = [];
for (let geometry of allGeometries) {
switch (geometry.type) {
case "Polygon": allArcArrays.push(...geometry.arcs); break;
case "MultiPolygon": geometry.arcs.forEach(a => allArcArrays.push(...a)); break;
}
}
this._arcUsageIndex = new Map();
for (let arcArray of allArcArrays) {
for (let arcId of arcArray) {
this._addArcUsageToIndex(arcArray, arcId);
}
}
}
_addArcUsageToIndex(arcArray, arcId) {
if (arcId < 0) arcId = ~arcId;
if (!this._arcUsageIndex.has(arcId)) this._arcUsageIndex.set(arcId, []);
this._arcUsageIndex.get(arcId).push(arcArray);
}
findArcId(points) {
const [coord1, coord2] = points;
let foundArcId = undefined;
for (let arcId = 0; arcId < this.arcs.length; arcId++) {
let arc = this.arcs[arcId];
if (!arc) continue;
if (eq(coord1, arc[0]) && eq(coord2, arc[1])) foundArcId = arcId;
if (eq(coord1, arc.at(-1)) && eq(coord2, arc.at(-2))) foundArcId = ~arcId;
if (foundArcId !== undefined) break;
}
const foundArc = (foundArcId >= 0) ? this.arcs[foundArcId] : this.arcs[~foundArcId];
if (foundArc.length != points.length) throw "wyd";
return foundArcId;
}
shiftRing(arcId, index) {
if (this.shiftedRingArcIds.has(arcId)) throw "can't shift twice";
const arc = this.arcs[arcId];
if (!isRing(arc)) throw "not a ring";
arc.pop();
const newLeft = arc.splice(index);
arc.splice(0, 0, ...newLeft);
arc.push(arc[0]);
this.shiftedRingArcIds.add(arcId);
}
splitAtIndex(arcId, index) {
if (index == 0) return;
const arcs = this.arcs;
const arc = arcs[arcId];
if (index == arc.length -1) return;
if (isRing(arc) && !this.shiftedRingArcIds.has(arcId)) {
this.shiftRing(arcId, index);
return;
}
const [arcLeftId, arcRightId] = [arcId, arcs.length];
const [arcLeft, arcRight] = [arc.splice(0, index+1), arc.splice(index)]; // arcRight[0] == arcLeft.at(-1) == arc[index];
arcs[arcLeftId] = arcLeft;
arcs[arcRightId] = arcRight;
const negArcId = ~arcId;
for (let arcArray of this._arcUsageIndex.get(arcId) ?? []) {
this._addArcToIndex(arcArray, arcRightId);
const posIndex = arcArray.indexOf(arcLeftId);
if (posIndex >= 0) {
arcArray.splice(posIndex, 1, arcLeftId, arcRightId);
}
else {
const negIndex = arcArray.indexOf(~arcLeftId);
if (negIndex >= 0) {
arcArray.splice(negIndex, 1, ~arcRightId, ~arcLeftId);
}
}
}
}
splitAtPoint(arcId, point) {
const arc = this.arcs[arcId];
if (eq(arc[0], point)) return;
if (eq(arc.at(-1), point)) return;
for (let i = 1; i < arc.length-1; i++) {
if (eq(arc[i], point)) {
// exact match
this.splitAtIndex(arcId, i);
return;
}
const prev = arc[i-1];
if (isPointBetween(point, prev, arc[i])) {
// between two existing points
arc.splice(i, 0, point);
this.splitAtIndex(arcId, i);
return;
}
}
throw "couldn't splitAtPoint";
}
}
return TopoArcUtil;
}