sweepline-intersections
Version:
A module to check if a polygon self-intersects using a sweepline algorithm
574 lines (497 loc) • 19 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.sweeplineIntersections = factory());
})(this, (function () { 'use strict';
var TinyQueue = function TinyQueue(data, compare) {
if ( data === void 0 ) data = [];
if ( compare === void 0 ) compare = defaultCompare;
this.data = data;
this.length = this.data.length;
this.compare = compare;
if (this.length > 0) {
for (var i = (this.length >> 1) - 1; i >= 0; i--) { this._down(i); }
}
};
TinyQueue.prototype.push = function push (item) {
this.data.push(item);
this.length++;
this._up(this.length - 1);
};
TinyQueue.prototype.pop = function pop () {
if (this.length === 0) { return undefined; }
var top = this.data[0];
var bottom = this.data.pop();
this.length--;
if (this.length > 0) {
this.data[0] = bottom;
this._down(0);
}
return top;
};
TinyQueue.prototype.peek = function peek () {
return this.data[0];
};
TinyQueue.prototype._up = function _up (pos) {
var ref = this;
var data = ref.data;
var compare = ref.compare;
var item = data[pos];
while (pos > 0) {
var parent = (pos - 1) >> 1;
var current = data[parent];
if (compare(item, current) >= 0) { break; }
data[pos] = current;
pos = parent;
}
data[pos] = item;
};
TinyQueue.prototype._down = function _down (pos) {
var ref = this;
var data = ref.data;
var compare = ref.compare;
var halfLength = this.length >> 1;
var item = data[pos];
while (pos < halfLength) {
var left = (pos << 1) + 1;
var best = data[left];
var right = left + 1;
if (right < this.length && compare(data[right], best) < 0) {
left = right;
best = data[right];
}
if (compare(best, item) >= 0) { break; }
data[pos] = best;
pos = left;
}
data[pos] = item;
};
function defaultCompare(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
function checkWhichEventIsLeft (e1, e2) {
if (e1.p.x > e2.p.x) { return 1 }
if (e1.p.x < e2.p.x) { return -1 }
if (e1.p.x === e2.p.x && (e1.featureId !== e2.featureId || e1.ringId !== e2.ringId)) {
if (e1.isLeftEndpoint && !e2.isLeftEndpoint) { return -1 }
}
if (e1.p.y !== e2.p.y) { return e1.p.y > e2.p.y ? 1 : -1 }
return 1
}
function checkWhichSegmentHasRightEndpointFirst (seg1, seg2) {
if (seg1.rightSweepEvent.p.x > seg2.rightSweepEvent.p.x) { return 1 }
if (seg1.rightSweepEvent.p.x < seg2.rightSweepEvent.p.x) { return -1 }
if (seg1.rightSweepEvent.p.y !== seg2.rightSweepEvent.p.y) {
return seg1.rightSweepEvent.p.y < seg2.rightSweepEvent.p.y ? 1 : -1
}
return 1
}
var Event = function Event (p, featureId, ringId, eventId) {
this.p = {
x: p[0],
y: p[1]
};
this.featureId = featureId;
this.ringId = ringId;
this.eventId = eventId;
this.otherEvent = null;
this.isLeftEndpoint = null;
};
Event.prototype.isSamePoint = function isSamePoint (eventToCheck) {
return this.p.x === eventToCheck.p.x && this.p.y === eventToCheck.p.y
};
Event.prototype.asNewXY = function asNewXY () {
return [this.p.x, this.p.y]
};
function fillEventQueue (geojson, eventQueue) {
if (geojson.type === 'FeatureCollection') {
var features = geojson.features;
for (var i = 0; i < features.length; i++) {
processFeature(features[i], eventQueue);
}
} else {
processFeature(geojson, eventQueue);
}
}
var featureId = 0;
var ringId = 0;
var eventId = 0;
function processFeature (featureOrGeometry, eventQueue) {
var geom = featureOrGeometry.type === 'Feature' ? featureOrGeometry.geometry : featureOrGeometry;
var coords = geom.coordinates;
// standardise the input
if (geom.type === 'Polygon' || geom.type === 'MultiLineString') { coords = [coords]; }
if (geom.type === 'LineString') { coords = [[coords]]; }
for (var i = 0; i < coords.length; i++) {
for (var ii = 0; ii < coords[i].length; ii++) {
var currentP = coords[i][ii][0];
var nextP = null;
ringId = ringId + 1;
for (var iii = 0; iii < coords[i][ii].length - 1; iii++) {
nextP = coords[i][ii][iii + 1];
var e1 = new Event(currentP, featureId, ringId, eventId);
var e2 = new Event(nextP, featureId, ringId, eventId + 1);
e1.otherEvent = e2;
e2.otherEvent = e1;
if (checkWhichEventIsLeft(e1, e2) > 0) {
e2.isLeftEndpoint = true;
e1.isLeftEndpoint = false;
} else {
e1.isLeftEndpoint = true;
e2.isLeftEndpoint = false;
}
eventQueue.push(e1);
eventQueue.push(e2);
currentP = nextP;
eventId = eventId + 1;
}
}
}
featureId = featureId + 1;
}
var Segment = function Segment (event) {
this.leftSweepEvent = event;
this.rightSweepEvent = event.otherEvent;
};
var epsilon = 1.1102230246251565e-16;
var splitter = 134217729;
var resulterrbound = (3 + 8 * epsilon) * epsilon;
// fast_expansion_sum_zeroelim routine from oritinal code
function sum(elen, e, flen, f, h) {
var Q, Qnew, hh, bvirt;
var enow = e[0];
var fnow = f[0];
var eindex = 0;
var findex = 0;
if ((fnow > enow) === (fnow > -enow)) {
Q = enow;
enow = e[++eindex];
} else {
Q = fnow;
fnow = f[++findex];
}
var hindex = 0;
if (eindex < elen && findex < flen) {
if ((fnow > enow) === (fnow > -enow)) {
Qnew = enow + Q;
hh = Q - (Qnew - enow);
enow = e[++eindex];
} else {
Qnew = fnow + Q;
hh = Q - (Qnew - fnow);
fnow = f[++findex];
}
Q = Qnew;
if (hh !== 0) {
h[hindex++] = hh;
}
while (eindex < elen && findex < flen) {
if ((fnow > enow) === (fnow > -enow)) {
Qnew = Q + enow;
bvirt = Qnew - Q;
hh = Q - (Qnew - bvirt) + (enow - bvirt);
enow = e[++eindex];
} else {
Qnew = Q + fnow;
bvirt = Qnew - Q;
hh = Q - (Qnew - bvirt) + (fnow - bvirt);
fnow = f[++findex];
}
Q = Qnew;
if (hh !== 0) {
h[hindex++] = hh;
}
}
}
while (eindex < elen) {
Qnew = Q + enow;
bvirt = Qnew - Q;
hh = Q - (Qnew - bvirt) + (enow - bvirt);
enow = e[++eindex];
Q = Qnew;
if (hh !== 0) {
h[hindex++] = hh;
}
}
while (findex < flen) {
Qnew = Q + fnow;
bvirt = Qnew - Q;
hh = Q - (Qnew - bvirt) + (fnow - bvirt);
fnow = f[++findex];
Q = Qnew;
if (hh !== 0) {
h[hindex++] = hh;
}
}
if (Q !== 0 || hindex === 0) {
h[hindex++] = Q;
}
return hindex;
}
function estimate(elen, e) {
var Q = e[0];
for (var i = 1; i < elen; i++) { Q += e[i]; }
return Q;
}
function vec(n) {
return new Float64Array(n);
}
var ccwerrboundA = (3 + 16 * epsilon) * epsilon;
var ccwerrboundB = (2 + 12 * epsilon) * epsilon;
var ccwerrboundC = (9 + 64 * epsilon) * epsilon * epsilon;
var B = vec(4);
var C1 = vec(8);
var C2 = vec(12);
var D = vec(16);
var u = vec(4);
function orient2dadapt(ax, ay, bx, by, cx, cy, detsum) {
var acxtail, acytail, bcxtail, bcytail;
var bvirt, c, ahi, alo, bhi, blo, _i, _j, _0, s1, s0, t1, t0, u3;
var acx = ax - cx;
var bcx = bx - cx;
var acy = ay - cy;
var bcy = by - cy;
s1 = acx * bcy;
c = splitter * acx;
ahi = c - (c - acx);
alo = acx - ahi;
c = splitter * bcy;
bhi = c - (c - bcy);
blo = bcy - bhi;
s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
t1 = acy * bcx;
c = splitter * acy;
ahi = c - (c - acy);
alo = acy - ahi;
c = splitter * bcx;
bhi = c - (c - bcx);
blo = bcx - bhi;
t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
_i = s0 - t0;
bvirt = s0 - _i;
B[0] = s0 - (_i + bvirt) + (bvirt - t0);
_j = s1 + _i;
bvirt = _j - s1;
_0 = s1 - (_j - bvirt) + (_i - bvirt);
_i = _0 - t1;
bvirt = _0 - _i;
B[1] = _0 - (_i + bvirt) + (bvirt - t1);
u3 = _j + _i;
bvirt = u3 - _j;
B[2] = _j - (u3 - bvirt) + (_i - bvirt);
B[3] = u3;
var det = estimate(4, B);
var errbound = ccwerrboundB * detsum;
if (det >= errbound || -det >= errbound) {
return det;
}
bvirt = ax - acx;
acxtail = ax - (acx + bvirt) + (bvirt - cx);
bvirt = bx - bcx;
bcxtail = bx - (bcx + bvirt) + (bvirt - cx);
bvirt = ay - acy;
acytail = ay - (acy + bvirt) + (bvirt - cy);
bvirt = by - bcy;
bcytail = by - (bcy + bvirt) + (bvirt - cy);
if (acxtail === 0 && acytail === 0 && bcxtail === 0 && bcytail === 0) {
return det;
}
errbound = ccwerrboundC * detsum + resulterrbound * Math.abs(det);
det += (acx * bcytail + bcy * acxtail) - (acy * bcxtail + bcx * acytail);
if (det >= errbound || -det >= errbound) { return det; }
s1 = acxtail * bcy;
c = splitter * acxtail;
ahi = c - (c - acxtail);
alo = acxtail - ahi;
c = splitter * bcy;
bhi = c - (c - bcy);
blo = bcy - bhi;
s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
t1 = acytail * bcx;
c = splitter * acytail;
ahi = c - (c - acytail);
alo = acytail - ahi;
c = splitter * bcx;
bhi = c - (c - bcx);
blo = bcx - bhi;
t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
_i = s0 - t0;
bvirt = s0 - _i;
u[0] = s0 - (_i + bvirt) + (bvirt - t0);
_j = s1 + _i;
bvirt = _j - s1;
_0 = s1 - (_j - bvirt) + (_i - bvirt);
_i = _0 - t1;
bvirt = _0 - _i;
u[1] = _0 - (_i + bvirt) + (bvirt - t1);
u3 = _j + _i;
bvirt = u3 - _j;
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
u[3] = u3;
var C1len = sum(4, B, 4, u, C1);
s1 = acx * bcytail;
c = splitter * acx;
ahi = c - (c - acx);
alo = acx - ahi;
c = splitter * bcytail;
bhi = c - (c - bcytail);
blo = bcytail - bhi;
s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
t1 = acy * bcxtail;
c = splitter * acy;
ahi = c - (c - acy);
alo = acy - ahi;
c = splitter * bcxtail;
bhi = c - (c - bcxtail);
blo = bcxtail - bhi;
t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
_i = s0 - t0;
bvirt = s0 - _i;
u[0] = s0 - (_i + bvirt) + (bvirt - t0);
_j = s1 + _i;
bvirt = _j - s1;
_0 = s1 - (_j - bvirt) + (_i - bvirt);
_i = _0 - t1;
bvirt = _0 - _i;
u[1] = _0 - (_i + bvirt) + (bvirt - t1);
u3 = _j + _i;
bvirt = u3 - _j;
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
u[3] = u3;
var C2len = sum(C1len, C1, 4, u, C2);
s1 = acxtail * bcytail;
c = splitter * acxtail;
ahi = c - (c - acxtail);
alo = acxtail - ahi;
c = splitter * bcytail;
bhi = c - (c - bcytail);
blo = bcytail - bhi;
s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
t1 = acytail * bcxtail;
c = splitter * acytail;
ahi = c - (c - acytail);
alo = acytail - ahi;
c = splitter * bcxtail;
bhi = c - (c - bcxtail);
blo = bcxtail - bhi;
t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
_i = s0 - t0;
bvirt = s0 - _i;
u[0] = s0 - (_i + bvirt) + (bvirt - t0);
_j = s1 + _i;
bvirt = _j - s1;
_0 = s1 - (_j - bvirt) + (_i - bvirt);
_i = _0 - t1;
bvirt = _0 - _i;
u[1] = _0 - (_i + bvirt) + (bvirt - t1);
u3 = _j + _i;
bvirt = u3 - _j;
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
u[3] = u3;
var Dlen = sum(C2len, C2, 4, u, D);
return D[Dlen - 1];
}
function orient2d(ax, ay, bx, by, cx, cy) {
var detleft = (ay - cy) * (bx - cx);
var detright = (ax - cx) * (by - cy);
var det = detleft - detright;
if (detleft === 0 || detright === 0 || (detleft > 0) !== (detright > 0)) { return det; }
var detsum = Math.abs(detleft + detright);
if (Math.abs(det) >= ccwerrboundA * detsum) { return det; }
return -orient2dadapt(ax, ay, bx, by, cx, cy, detsum);
}
function testSegmentIntersect (seg1, seg2) {
if (seg1 === null || seg2 === null) { return false }
var x1 = seg1.leftSweepEvent.p.x;
var y1 = seg1.leftSweepEvent.p.y;
var x2 = seg1.rightSweepEvent.p.x;
var y2 = seg1.rightSweepEvent.p.y;
var x3 = seg2.leftSweepEvent.p.x;
var y3 = seg2.leftSweepEvent.p.y;
var x4 = seg2.rightSweepEvent.p.x;
var y4 = seg2.rightSweepEvent.p.y;
var score1 = orient2d(x1, y1, x2, y2, x3, y3);
var score2 = orient2d(x1, y1, x2, y2, x4, y4);
if (score1 > 0 && score2 > 0) { return false }
else if (score1 < 0 && score2 < 0) { return false }
if (seg1.leftSweepEvent.ringId === seg2.leftSweepEvent.ringId) {
if (
seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
seg1.rightSweepEvent.isSamePoint(seg2.rightSweepEvent) ||
seg1.leftSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
seg1.leftSweepEvent.isSamePoint(seg2.rightSweepEvent)
) { return false }
} else {
if (seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent)) { return seg2.leftSweepEvent.asNewXY() }
if (seg1.rightSweepEvent.isSamePoint(seg2.rightSweepEvent)) { return seg2.rightSweepEvent.asNewXY() }
if (seg1.leftSweepEvent.isSamePoint(seg2.leftSweepEvent)) { return seg2.leftSweepEvent.asNewXY() }
if (seg1.leftSweepEvent.isSamePoint(seg2.rightSweepEvent)) { return seg2.rightSweepEvent.asNewXY() }
}
var denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
var numeA = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3));
var numeB = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3));
if (denom === 0) {
if (numeA === 0 && numeB === 0) { return false }
return false
}
var uA = numeA / denom;
var uB = numeB / denom;
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
var x = x1 + (uA * (x2 - x1));
var y = y1 + (uA * (y2 - y1));
return [x, y]
}
return false
}
// import {debugEventAndSegments, debugRemovingSegment} from './debug'
function runCheck (eventQueue, ignoreSelfIntersections) {
ignoreSelfIntersections = ignoreSelfIntersections ? ignoreSelfIntersections : false;
var intersectionPoints = [];
var outQueue = new TinyQueue([], checkWhichSegmentHasRightEndpointFirst);
while (eventQueue.length) {
var event = eventQueue.pop();
if (event.isLeftEndpoint) {
// debugEventAndSegments(event.p, outQueue.data)
var segment = new Segment(event);
for (var i = 0; i < outQueue.data.length; i++) {
var otherSeg = outQueue.data[i];
if (ignoreSelfIntersections) {
if (otherSeg.leftSweepEvent.featureId === event.featureId) { continue }
}
var intersection = testSegmentIntersect(segment, otherSeg);
if (intersection !== false) { intersectionPoints.push(intersection); }
}
outQueue.push(segment);
} else if (event.isLeftEndpoint === false) {
outQueue.pop();
// const seg = outQueue.pop()
// debugRemovingSegment(event.p, seg)
}
}
return intersectionPoints
}
var SweeplineIntersections = function SweeplineIntersections () {
this._eventQueue = new TinyQueue([], checkWhichEventIsLeft);
};
SweeplineIntersections.prototype.addData = function addData (geojson, alternateEventQueue) {
if (alternateEventQueue !== undefined) {
var newQueue = new TinyQueue([], checkWhichEventIsLeft);
for (var i = 0; i < alternateEventQueue.length; i++) {
newQueue.push(alternateEventQueue.data[i]);
}
this._eventQueue = newQueue;
}
fillEventQueue(geojson, this._eventQueue);
};
SweeplineIntersections.prototype.cloneEventQueue = function cloneEventQueue () {
var newQueue = new TinyQueue([], checkWhichEventIsLeft);
for (var i = 0; i < this._eventQueue.length; i++) {
newQueue.push(this._eventQueue.data[i]);
}
return newQueue
};
SweeplineIntersections.prototype.getIntersections = function getIntersections (ignoreSelfIntersections) {
return runCheck(this._eventQueue, ignoreSelfIntersections)
};
return SweeplineIntersections;
}));