UNPKG

sweepline-intersections

Version:

A module to check if a polygon self-intersects using a sweepline algorithm

553 lines (479 loc) 18.2 kB
(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 } function sweeplineIntersections (geojson, ignoreSelfIntersections) { var eventQueue = new TinyQueue([], checkWhichEventIsLeft); fillEventQueue(geojson, eventQueue); return runCheck(eventQueue, ignoreSelfIntersections) } return sweeplineIntersections; }));