From 85595359f256461589ec1af83507e6e63044babf Mon Sep 17 00:00:00 2001 From: Mike Fogel Date: Sun, 17 Dec 2023 14:57:57 -0800 Subject: [PATCH] Revert the revert back to v0.15.3 (#160) * Revert "Revert published files to v0.15.3" This reverts commit 212c727a3560388f7fe4798ceb0ce3c9a1fcce81. * Update changelog --- CHANGELOG.md | 4 + dist/polygon-clipping.cjs.js | 2566 ++++++++---------- dist/polygon-clipping.d.ts | 23 +- dist/polygon-clipping.esm.js | 2566 ++++++++---------- dist/polygon-clipping.umd.js | 3601 +++++++++++++------------- dist/polygon-clipping.umd.min.js | 30 +- dist/polygon-clipping.umd.min.js.map | 2 +- package.json | 52 +- 8 files changed, 4117 insertions(+), 4727 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 577a119..92e86d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). +## main (unreleased) + +- IN PROGRESS: fix the packaging issues with v0.15.4 + ## v0.15.5 (2023-12-16) - Revert to v0.15.3 to broken deployed package diff --git a/dist/polygon-clipping.cjs.js b/dist/polygon-clipping.cjs.js index 5616e31..cd842b8 100644 --- a/dist/polygon-clipping.cjs.js +++ b/dist/polygon-clipping.cjs.js @@ -1,56 +1,39 @@ 'use strict'; var SplayTree = require('splaytree'); +var robustPredicates = require('robust-predicates'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var SplayTree__default = /*#__PURE__*/_interopDefaultLegacy(SplayTree); -function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } -} - -function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } -} - -function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; -} - /** * A bounding box has the format: * * { ll: { x: xmin, y: ymin }, ur: { x: xmax, y: ymax } } * */ -var isInBbox = function isInBbox(bbox, point) { + +const isInBbox = (bbox, point) => { return bbox.ll.x <= point.x && point.x <= bbox.ur.x && bbox.ll.y <= point.y && point.y <= bbox.ur.y; }; + /* Returns either null, or a bbox (aka an ordered pair of points) * If there is only one point of overlap, a bbox with identical points * will be returned */ - -var getBboxOverlap = function getBboxOverlap(b1, b2) { +const getBboxOverlap = (b1, b2) => { // check if the bboxes overlap at all - if (b2.ur.x < b1.ll.x || b1.ur.x < b2.ll.x || b2.ur.y < b1.ll.y || b1.ur.y < b2.ll.y) return null; // find the middle two X values + if (b2.ur.x < b1.ll.x || b1.ur.x < b2.ll.x || b2.ur.y < b1.ll.y || b1.ur.y < b2.ll.y) return null; - var lowerX = b1.ll.x < b2.ll.x ? b2.ll.x : b1.ll.x; - var upperX = b1.ur.x < b2.ur.x ? b1.ur.x : b2.ur.x; // find the middle two Y values + // find the middle two X values + const lowerX = b1.ll.x < b2.ll.x ? b2.ll.x : b1.ll.x; + const upperX = b1.ur.x < b2.ur.x ? b1.ur.x : b2.ur.x; - var lowerY = b1.ll.y < b2.ll.y ? b2.ll.y : b1.ll.y; - var upperY = b1.ur.y < b2.ur.y ? b1.ur.y : b2.ur.y; // put those middle values together to get the overlap + // find the middle two Y values + const lowerY = b1.ll.y < b2.ll.y ? b2.ll.y : b1.ll.y; + const upperY = b1.ur.y < b2.ur.y ? b1.ur.y : b2.ur.y; + // put those middle values together to get the overlap return { ll: { x: lowerX, @@ -68,28 +51,29 @@ var getBboxOverlap = function getBboxOverlap(b1, b2) { * * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON */ -var epsilon = Number.EPSILON; // IE Polyfill +let epsilon = Number.EPSILON; + +// IE Polyfill if (epsilon === undefined) epsilon = Math.pow(2, -52); -var EPSILON_SQ = epsilon * epsilon; -/* FLP comparator */ +const EPSILON_SQ = epsilon * epsilon; -var cmp = function cmp(a, b) { +/* FLP comparator */ +const cmp = (a, b) => { // check if they're both 0 if (-epsilon < a && a < epsilon) { if (-epsilon < b && b < epsilon) { return 0; } - } // check if they're flp equal - - - var ab = a - b; + } + // check if they're flp equal + const ab = a - b; if (ab * ab < EPSILON_SQ * a * b) { return 0; - } // normal comparison - + } + // normal comparison return a < b ? -1 : 1; }; @@ -106,467 +90,414 @@ var cmp = function cmp(a, b) { * stored in any data structures in the rest of this algorithm. */ -var PtRounder = /*#__PURE__*/function () { - function PtRounder() { - _classCallCheck(this, PtRounder); - +class PtRounder { + constructor() { this.reset(); } - - _createClass(PtRounder, [{ - key: "reset", - value: function reset() { - this.xRounder = new CoordRounder(); - this.yRounder = new CoordRounder(); - } - }, { - key: "round", - value: function round(x, y) { - return { - x: this.xRounder.round(x), - y: this.yRounder.round(y) - }; - } - }]); - - return PtRounder; -}(); - -var CoordRounder = /*#__PURE__*/function () { - function CoordRounder() { - _classCallCheck(this, CoordRounder); - - this.tree = new SplayTree__default['default'](); // preseed with 0 so we don't end up with values < Number.EPSILON - + reset() { + this.xRounder = new CoordRounder(); + this.yRounder = new CoordRounder(); + } + round(x, y) { + return { + x: this.xRounder.round(x), + y: this.yRounder.round(y) + }; + } +} +class CoordRounder { + constructor() { + this.tree = new SplayTree__default["default"](); + // preseed with 0 so we don't end up with values < Number.EPSILON this.round(0); - } // Note: this can rounds input values backwards or forwards. + } + + // Note: this can rounds input values backwards or forwards. // You might ask, why not restrict this to just rounding // forwards? Wouldn't that allow left endpoints to always // remain left endpoints during splitting (never change to // right). No - it wouldn't, because we snap intersections // to endpoints (to establish independence from the segment // angle for t-intersections). - - - _createClass(CoordRounder, [{ - key: "round", - value: function round(coord) { - var node = this.tree.add(coord); - var prevNode = this.tree.prev(node); - - if (prevNode !== null && cmp(node.key, prevNode.key) === 0) { - this.tree.remove(coord); - return prevNode.key; - } - - var nextNode = this.tree.next(node); - - if (nextNode !== null && cmp(node.key, nextNode.key) === 0) { - this.tree.remove(coord); - return nextNode.key; - } - - return coord; + round(coord) { + const node = this.tree.add(coord); + const prevNode = this.tree.prev(node); + if (prevNode !== null && cmp(node.key, prevNode.key) === 0) { + this.tree.remove(coord); + return prevNode.key; } - }]); - - return CoordRounder; -}(); // singleton available by import - + const nextNode = this.tree.next(node); + if (nextNode !== null && cmp(node.key, nextNode.key) === 0) { + this.tree.remove(coord); + return nextNode.key; + } + return coord; + } +} -var rounder = new PtRounder(); +// singleton available by import +const rounder = new PtRounder(); /* Cross Product of two vectors with first point at origin */ +const crossProduct = (a, b) => a.x * b.y - a.y * b.x; -var crossProduct = function crossProduct(a, b) { - return a.x * b.y - a.y * b.x; -}; /* Dot Product of two vectors with first point at origin */ +const dotProduct = (a, b) => a.x * b.x + a.y * b.y; -var dotProduct = function dotProduct(a, b) { - return a.x * b.x + a.y * b.y; -}; /* Comparator for two vectors with same starting point */ - -var compareVectorAngles = function compareVectorAngles(basePt, endPt1, endPt2) { - var v1 = { - x: endPt1.x - basePt.x, - y: endPt1.y - basePt.y - }; - var v2 = { - x: endPt2.x - basePt.x, - y: endPt2.y - basePt.y - }; - var kross = crossProduct(v1, v2); - return cmp(kross, 0); +const compareVectorAngles = (basePt, endPt1, endPt2) => { + const res = robustPredicates.orient2d(basePt.x, basePt.y, endPt1.x, endPt1.y, endPt2.x, endPt2.y); + if (res > 0) return -1; + if (res < 0) return 1; + return 0; }; -var length = function length(v) { - return Math.sqrt(dotProduct(v, v)); -}; -/* Get the sine of the angle from pShared -> pAngle to pShaed -> pBase */ +const length = v => Math.sqrt(dotProduct(v, v)); -var sineOfAngle = function sineOfAngle(pShared, pBase, pAngle) { - var vBase = { +/* Get the sine of the angle from pShared -> pAngle to pShaed -> pBase */ +const sineOfAngle = (pShared, pBase, pAngle) => { + const vBase = { x: pBase.x - pShared.x, y: pBase.y - pShared.y }; - var vAngle = { + const vAngle = { x: pAngle.x - pShared.x, y: pAngle.y - pShared.y }; return crossProduct(vAngle, vBase) / length(vAngle) / length(vBase); }; -/* Get the cosine of the angle from pShared -> pAngle to pShaed -> pBase */ -var cosineOfAngle = function cosineOfAngle(pShared, pBase, pAngle) { - var vBase = { +/* Get the cosine of the angle from pShared -> pAngle to pShaed -> pBase */ +const cosineOfAngle = (pShared, pBase, pAngle) => { + const vBase = { x: pBase.x - pShared.x, y: pBase.y - pShared.y }; - var vAngle = { + const vAngle = { x: pAngle.x - pShared.x, y: pAngle.y - pShared.y }; return dotProduct(vAngle, vBase) / length(vAngle) / length(vBase); }; + /* Get the x coordinate where the given line (defined by a point and vector) * crosses the horizontal line with the given y coordiante. * In the case of parrallel lines (including overlapping ones) returns null. */ - -var horizontalIntersection = function horizontalIntersection(pt, v, y) { +const horizontalIntersection = (pt, v, y) => { if (v.y === 0) return null; return { x: pt.x + v.x / v.y * (y - pt.y), y: y }; }; + /* Get the y coordinate where the given line (defined by a point and vector) * crosses the vertical line with the given x coordiante. * In the case of parrallel lines (including overlapping ones) returns null. */ - -var verticalIntersection = function verticalIntersection(pt, v, x) { +const verticalIntersection = (pt, v, x) => { if (v.x === 0) return null; return { x: x, y: pt.y + v.y / v.x * (x - pt.x) }; }; + /* Get the intersection of two lines, each defined by a base point and a vector. * In the case of parrallel lines (including overlapping ones) returns null. */ - -var intersection = function intersection(pt1, v1, pt2, v2) { +const intersection$1 = (pt1, v1, pt2, v2) => { // take some shortcuts for vertical and horizontal lines // this also ensures we don't calculate an intersection and then discover // it's actually outside the bounding box of the line if (v1.x === 0) return verticalIntersection(pt2, v2, pt1.x); if (v2.x === 0) return verticalIntersection(pt1, v1, pt2.x); if (v1.y === 0) return horizontalIntersection(pt2, v2, pt1.y); - if (v2.y === 0) return horizontalIntersection(pt1, v1, pt2.y); // General case for non-overlapping segments. + if (v2.y === 0) return horizontalIntersection(pt1, v1, pt2.y); + + // General case for non-overlapping segments. // This algorithm is based on Schneider and Eberly. // http://www.cimec.org.ar/~ncalvo/Schneider_Eberly.pdf - pg 244 - var kross = crossProduct(v1, v2); + const kross = crossProduct(v1, v2); if (kross == 0) return null; - var ve = { + const ve = { x: pt2.x - pt1.x, y: pt2.y - pt1.y }; - var d1 = crossProduct(ve, v1) / kross; - var d2 = crossProduct(ve, v2) / kross; // take the average of the two calculations to minimize rounding error - - var x1 = pt1.x + d2 * v1.x, - x2 = pt2.x + d1 * v2.x; - var y1 = pt1.y + d2 * v1.y, - y2 = pt2.y + d1 * v2.y; - var x = (x1 + x2) / 2; - var y = (y1 + y2) / 2; + const d1 = crossProduct(ve, v1) / kross; + const d2 = crossProduct(ve, v2) / kross; + + // take the average of the two calculations to minimize rounding error + const x1 = pt1.x + d2 * v1.x, + x2 = pt2.x + d1 * v2.x; + const y1 = pt1.y + d2 * v1.y, + y2 = pt2.y + d1 * v2.y; + const x = (x1 + x2) / 2; + const y = (y1 + y2) / 2; return { x: x, y: y }; }; -var SweepEvent = /*#__PURE__*/function () { - _createClass(SweepEvent, null, [{ - key: "compare", - // for ordering sweep events in the sweep event queue - value: function compare(a, b) { - // favor event with a point that the sweep line hits first - var ptCmp = SweepEvent.comparePoints(a.point, b.point); - if (ptCmp !== 0) return ptCmp; // the points are the same, so link them if needed - - if (a.point !== b.point) a.link(b); // favor right events over left - - if (a.isLeft !== b.isLeft) return a.isLeft ? 1 : -1; // we have two matching left or right endpoints - // ordering of this case is the same as for their segments - - return Segment.compare(a.segment, b.segment); - } // for ordering points in sweep line order - - }, { - key: "comparePoints", - value: function comparePoints(aPt, bPt) { - if (aPt.x < bPt.x) return -1; - if (aPt.x > bPt.x) return 1; - if (aPt.y < bPt.y) return -1; - if (aPt.y > bPt.y) return 1; - return 0; - } // Warning: 'point' input will be modified and re-used (for performance) +class SweepEvent { + // for ordering sweep events in the sweep event queue + static compare(a, b) { + // favor event with a point that the sweep line hits first + const ptCmp = SweepEvent.comparePoints(a.point, b.point); + if (ptCmp !== 0) return ptCmp; - }]); + // the points are the same, so link them if needed + if (a.point !== b.point) a.link(b); - function SweepEvent(point, isLeft) { - _classCallCheck(this, SweepEvent); + // favor right events over left + if (a.isLeft !== b.isLeft) return a.isLeft ? 1 : -1; - if (point.events === undefined) point.events = [this];else point.events.push(this); - this.point = point; - this.isLeft = isLeft; // this.segment, this.otherSE set by factory + // we have two matching left or right endpoints + // ordering of this case is the same as for their segments + return Segment.compare(a.segment, b.segment); } - _createClass(SweepEvent, [{ - key: "link", - value: function link(other) { - if (other.point === this.point) { - throw new Error('Tried to link already linked events'); - } + // for ordering points in sweep line order + static comparePoints(aPt, bPt) { + if (aPt.x < bPt.x) return -1; + if (aPt.x > bPt.x) return 1; + if (aPt.y < bPt.y) return -1; + if (aPt.y > bPt.y) return 1; + return 0; + } - var otherEvents = other.point.events; + // Warning: 'point' input will be modified and re-used (for performance) + constructor(point, isLeft) { + if (point.events === undefined) point.events = [this];else point.events.push(this); + this.point = point; + this.isLeft = isLeft; + // this.segment, this.otherSE set by factory + } + link(other) { + if (other.point === this.point) { + throw new Error("Tried to link already linked events"); + } + const otherEvents = other.point.events; + for (let i = 0, iMax = otherEvents.length; i < iMax; i++) { + const evt = otherEvents[i]; + this.point.events.push(evt); + evt.point = this.point; + } + this.checkForConsuming(); + } - for (var i = 0, iMax = otherEvents.length; i < iMax; i++) { - var evt = otherEvents[i]; - this.point.events.push(evt); - evt.point = this.point; + /* Do a pass over our linked events and check to see if any pair + * of segments match, and should be consumed. */ + checkForConsuming() { + // FIXME: The loops in this method run O(n^2) => no good. + // Maintain little ordered sweep event trees? + // Can we maintaining an ordering that avoids the need + // for the re-sorting with getLeftmostComparator in geom-out? + + // Compare each pair of events to see if other events also match + const numEvents = this.point.events.length; + for (let i = 0; i < numEvents; i++) { + const evt1 = this.point.events[i]; + if (evt1.segment.consumedBy !== undefined) continue; + for (let j = i + 1; j < numEvents; j++) { + const evt2 = this.point.events[j]; + if (evt2.consumedBy !== undefined) continue; + if (evt1.otherSE.point.events !== evt2.otherSE.point.events) continue; + evt1.segment.consume(evt2.segment); } - - this.checkForConsuming(); } - /* Do a pass over our linked events and check to see if any pair - * of segments match, and should be consumed. */ - - }, { - key: "checkForConsuming", - value: function checkForConsuming() { - // FIXME: The loops in this method run O(n^2) => no good. - // Maintain little ordered sweep event trees? - // Can we maintaining an ordering that avoids the need - // for the re-sorting with getLeftmostComparator in geom-out? - // Compare each pair of events to see if other events also match - var numEvents = this.point.events.length; - - for (var i = 0; i < numEvents; i++) { - var evt1 = this.point.events[i]; - if (evt1.segment.consumedBy !== undefined) continue; - - for (var j = i + 1; j < numEvents; j++) { - var evt2 = this.point.events[j]; - if (evt2.consumedBy !== undefined) continue; - if (evt1.otherSE.point.events !== evt2.otherSE.point.events) continue; - evt1.segment.consume(evt2.segment); - } + } + getAvailableLinkedEvents() { + // point.events is always of length 2 or greater + const events = []; + for (let i = 0, iMax = this.point.events.length; i < iMax; i++) { + const evt = this.point.events[i]; + if (evt !== this && !evt.segment.ringOut && evt.segment.isInResult()) { + events.push(evt); } } - }, { - key: "getAvailableLinkedEvents", - value: function getAvailableLinkedEvents() { - // point.events is always of length 2 or greater - var events = []; - - for (var i = 0, iMax = this.point.events.length; i < iMax; i++) { - var evt = this.point.events[i]; + return events; + } - if (evt !== this && !evt.segment.ringOut && evt.segment.isInResult()) { - events.push(evt); - } + /** + * Returns a comparator function for sorting linked events that will + * favor the event that will give us the smallest left-side angle. + * All ring construction starts as low as possible heading to the right, + * so by always turning left as sharp as possible we'll get polygons + * without uncessary loops & holes. + * + * The comparator function has a compute cache such that it avoids + * re-computing already-computed values. + */ + getLeftmostComparator(baseEvent) { + const cache = new Map(); + const fillCache = linkedEvent => { + const nextEvent = linkedEvent.otherSE; + cache.set(linkedEvent, { + sine: sineOfAngle(this.point, baseEvent.point, nextEvent.point), + cosine: cosineOfAngle(this.point, baseEvent.point, nextEvent.point) + }); + }; + return (a, b) => { + if (!cache.has(a)) fillCache(a); + if (!cache.has(b)) fillCache(b); + const { + sine: asine, + cosine: acosine + } = cache.get(a); + const { + sine: bsine, + cosine: bcosine + } = cache.get(b); + + // both on or above x-axis + if (asine >= 0 && bsine >= 0) { + if (acosine < bcosine) return 1; + if (acosine > bcosine) return -1; + return 0; } - return events; - } - /** - * Returns a comparator function for sorting linked events that will - * favor the event that will give us the smallest left-side angle. - * All ring construction starts as low as possible heading to the right, - * so by always turning left as sharp as possible we'll get polygons - * without uncessary loops & holes. - * - * The comparator function has a compute cache such that it avoids - * re-computing already-computed values. - */ - - }, { - key: "getLeftmostComparator", - value: function getLeftmostComparator(baseEvent) { - var _this = this; - - var cache = new Map(); - - var fillCache = function fillCache(linkedEvent) { - var nextEvent = linkedEvent.otherSE; - cache.set(linkedEvent, { - sine: sineOfAngle(_this.point, baseEvent.point, nextEvent.point), - cosine: cosineOfAngle(_this.point, baseEvent.point, nextEvent.point) - }); - }; - - return function (a, b) { - if (!cache.has(a)) fillCache(a); - if (!cache.has(b)) fillCache(b); - - var _cache$get = cache.get(a), - asine = _cache$get.sine, - acosine = _cache$get.cosine; - - var _cache$get2 = cache.get(b), - bsine = _cache$get2.sine, - bcosine = _cache$get2.cosine; // both on or above x-axis - - - if (asine >= 0 && bsine >= 0) { - if (acosine < bcosine) return 1; - if (acosine > bcosine) return -1; - return 0; - } // both below x-axis - - - if (asine < 0 && bsine < 0) { - if (acosine < bcosine) return -1; - if (acosine > bcosine) return 1; - return 0; - } // one above x-axis, one below - - - if (bsine < asine) return -1; - if (bsine > asine) return 1; + // both below x-axis + if (asine < 0 && bsine < 0) { + if (acosine < bcosine) return -1; + if (acosine > bcosine) return 1; return 0; - }; - } - }]); + } - return SweepEvent; -}(); + // one above x-axis, one below + if (bsine < asine) return -1; + if (bsine > asine) return 1; + return 0; + }; + } +} +// Give segments unique ID's to get consistent sorting of // segments and sweep events when all else is identical +let segmentId = 0; +class Segment { + /* This compare() function is for ordering segments in the sweep + * line tree, and does so according to the following criteria: + * + * Consider the vertical line that lies an infinestimal step to the + * right of the right-more of the two left endpoints of the input + * segments. Imagine slowly moving a point up from negative infinity + * in the increasing y direction. Which of the two segments will that + * point intersect first? That segment comes 'before' the other one. + * + * If neither segment would be intersected by such a line, (if one + * or more of the segments are vertical) then the line to be considered + * is directly on the right-more of the two left inputs. + */ + static compare(a, b) { + const alx = a.leftSE.point.x; + const blx = b.leftSE.point.x; + const arx = a.rightSE.point.x; + const brx = b.rightSE.point.x; + + // check if they're even in the same vertical plane + if (brx < alx) return 1; + if (arx < blx) return -1; + const aly = a.leftSE.point.y; + const bly = b.leftSE.point.y; + const ary = a.rightSE.point.y; + const bry = b.rightSE.point.y; + + // is left endpoint of segment B the right-more? + if (alx < blx) { + // are the two segments in the same horizontal plane? + if (bly < aly && bly < ary) return 1; + if (bly > aly && bly > ary) return -1; + + // is the B left endpoint colinear to segment A? + const aCmpBLeft = a.comparePoint(b.leftSE.point); + if (aCmpBLeft < 0) return 1; + if (aCmpBLeft > 0) return -1; + + // is the A right endpoint colinear to segment B ? + const bCmpARight = b.comparePoint(a.rightSE.point); + if (bCmpARight !== 0) return bCmpARight; + + // colinear segments, consider the one with left-more + // left endpoint to be first (arbitrary?) + return -1; + } -var segmentId = 0; - -var Segment = /*#__PURE__*/function () { - _createClass(Segment, null, [{ - key: "compare", - - /* This compare() function is for ordering segments in the sweep - * line tree, and does so according to the following criteria: - * - * Consider the vertical line that lies an infinestimal step to the - * right of the right-more of the two left endpoints of the input - * segments. Imagine slowly moving a point up from negative infinity - * in the increasing y direction. Which of the two segments will that - * point intersect first? That segment comes 'before' the other one. - * - * If neither segment would be intersected by such a line, (if one - * or more of the segments are vertical) then the line to be considered - * is directly on the right-more of the two left inputs. - */ - value: function compare(a, b) { - var alx = a.leftSE.point.x; - var blx = b.leftSE.point.x; - var arx = a.rightSE.point.x; - var brx = b.rightSE.point.x; // check if they're even in the same vertical plane - - if (brx < alx) return 1; - if (arx < blx) return -1; - var aly = a.leftSE.point.y; - var bly = b.leftSE.point.y; - var ary = a.rightSE.point.y; - var bry = b.rightSE.point.y; // is left endpoint of segment B the right-more? - - if (alx < blx) { - // are the two segments in the same horizontal plane? - if (bly < aly && bly < ary) return 1; - if (bly > aly && bly > ary) return -1; // is the B left endpoint colinear to segment A? - - var aCmpBLeft = a.comparePoint(b.leftSE.point); - if (aCmpBLeft < 0) return 1; - if (aCmpBLeft > 0) return -1; // is the A right endpoint colinear to segment B ? - - var bCmpARight = b.comparePoint(a.rightSE.point); - if (bCmpARight !== 0) return bCmpARight; // colinear segments, consider the one with left-more - // left endpoint to be first (arbitrary?) - - return -1; - } // is left endpoint of segment A the right-more? - - - if (alx > blx) { - if (aly < bly && aly < bry) return -1; - if (aly > bly && aly > bry) return 1; // is the A left endpoint colinear to segment B? - - var bCmpALeft = b.comparePoint(a.leftSE.point); - if (bCmpALeft !== 0) return bCmpALeft; // is the B right endpoint colinear to segment A? - - var aCmpBRight = a.comparePoint(b.rightSE.point); - if (aCmpBRight < 0) return 1; - if (aCmpBRight > 0) return -1; // colinear segments, consider the one with left-more - // left endpoint to be first (arbitrary?) - - return 1; - } // if we get here, the two left endpoints are in the same - // vertical plane, ie alx === blx - // consider the lower left-endpoint to come first - - - if (aly < bly) return -1; - if (aly > bly) return 1; // left endpoints are identical - // check for colinearity by using the left-more right endpoint - // is the A right endpoint more left-more? - - if (arx < brx) { - var _bCmpARight = b.comparePoint(a.rightSE.point); - - if (_bCmpARight !== 0) return _bCmpARight; - } // is the B right endpoint more left-more? - - - if (arx > brx) { - var _aCmpBRight = a.comparePoint(b.rightSE.point); - - if (_aCmpBRight < 0) return 1; - if (_aCmpBRight > 0) return -1; - } + // is left endpoint of segment A the right-more? + if (alx > blx) { + if (aly < bly && aly < bry) return -1; + if (aly > bly && aly > bry) return 1; + + // is the A left endpoint colinear to segment B? + const bCmpALeft = b.comparePoint(a.leftSE.point); + if (bCmpALeft !== 0) return bCmpALeft; - if (arx !== brx) { - // are these two [almost] vertical segments with opposite orientation? - // if so, the one with the lower right endpoint comes first - var ay = ary - aly; - var ax = arx - alx; - var by = bry - bly; - var bx = brx - blx; - if (ay > ax && by < bx) return 1; - if (ay < ax && by > bx) return -1; - } // we have colinear segments with matching orientation - // consider the one with more left-more right endpoint to be first + // is the B right endpoint colinear to segment A? + const aCmpBRight = a.comparePoint(b.rightSE.point); + if (aCmpBRight < 0) return 1; + if (aCmpBRight > 0) return -1; + // colinear segments, consider the one with left-more + // left endpoint to be first (arbitrary?) + return 1; + } - if (arx > brx) return 1; - if (arx < brx) return -1; // if we get here, two two right endpoints are in the same - // vertical plane, ie arx === brx - // consider the lower right-endpoint to come first + // if we get here, the two left endpoints are in the same + // vertical plane, ie alx === blx - if (ary < bry) return -1; - if (ary > bry) return 1; // right endpoints identical as well, so the segments are idential - // fall back on creation order as consistent tie-breaker + // consider the lower left-endpoint to come first + if (aly < bly) return -1; + if (aly > bly) return 1; - if (a.id < b.id) return -1; - if (a.id > b.id) return 1; // identical segment, ie a === b + // left endpoints are identical + // check for colinearity by using the left-more right endpoint - return 0; + // is the A right endpoint more left-more? + if (arx < brx) { + const bCmpARight = b.comparePoint(a.rightSE.point); + if (bCmpARight !== 0) return bCmpARight; + } + + // is the B right endpoint more left-more? + if (arx > brx) { + const aCmpBRight = a.comparePoint(b.rightSE.point); + if (aCmpBRight < 0) return 1; + if (aCmpBRight > 0) return -1; + } + if (arx !== brx) { + // are these two [almost] vertical segments with opposite orientation? + // if so, the one with the lower right endpoint comes first + const ay = ary - aly; + const ax = arx - alx; + const by = bry - bly; + const bx = brx - blx; + if (ay > ax && by < bx) return 1; + if (ay < ax && by > bx) return -1; } - /* Warning: a reference to ringWindings input will be stored, - * and possibly will be later modified */ - }]); + // we have colinear segments with matching orientation + // consider the one with more left-more right endpoint to be first + if (arx > brx) return 1; + if (arx < brx) return -1; + + // if we get here, two two right endpoints are in the same + // vertical plane, ie arx === brx - function Segment(leftSE, rightSE, rings, windings) { - _classCallCheck(this, Segment); + // consider the lower right-endpoint to come first + if (ary < bry) return -1; + if (ary > bry) return 1; + + // right endpoints identical as well, so the segments are idential + // fall back on creation order as consistent tie-breaker + if (a.id < b.id) return -1; + if (a.id > b.id) return 1; + + // identical segment, ie a === b + return 0; + } + /* Warning: a reference to ringWindings input will be stored, + * and possibly will be later modified */ + constructor(leftSE, rightSE, rings, windings) { this.id = ++segmentId; this.leftSE = leftSE; leftSE.segment = this; @@ -575,481 +506,425 @@ var Segment = /*#__PURE__*/function () { rightSE.segment = this; rightSE.otherSE = leftSE; this.rings = rings; - this.windings = windings; // left unset for performance, set later in algorithm + this.windings = windings; + // left unset for performance, set later in algorithm // this.ringOut, this.consumedBy, this.prev } + static fromRing(pt1, pt2, ring) { + let leftPt, rightPt, winding; + + // ordering the two points according to sweep line ordering + const cmpPts = SweepEvent.comparePoints(pt1, pt2); + if (cmpPts < 0) { + leftPt = pt1; + rightPt = pt2; + winding = 1; + } else if (cmpPts > 0) { + leftPt = pt2; + rightPt = pt1; + winding = -1; + } else throw new Error(`Tried to create degenerate segment at [${pt1.x}, ${pt1.y}]`); + const leftSE = new SweepEvent(leftPt, true); + const rightSE = new SweepEvent(rightPt, false); + return new Segment(leftSE, rightSE, [ring], [winding]); + } - _createClass(Segment, [{ - key: "replaceRightSE", - - /* When a segment is split, the rightSE is replaced with a new sweep event */ - value: function replaceRightSE(newRightSE) { - this.rightSE = newRightSE; - this.rightSE.segment = this; - this.rightSE.otherSE = this.leftSE; - this.leftSE.otherSE = this.rightSE; - } - }, { - key: "bbox", - value: function bbox() { - var y1 = this.leftSE.point.y; - var y2 = this.rightSE.point.y; - return { - ll: { - x: this.leftSE.point.x, - y: y1 < y2 ? y1 : y2 - }, - ur: { - x: this.rightSE.point.x, - y: y1 > y2 ? y1 : y2 - } - }; - } - /* A vector from the left point to the right */ - - }, { - key: "vector", - value: function vector() { - return { - x: this.rightSE.point.x - this.leftSE.point.x, - y: this.rightSE.point.y - this.leftSE.point.y - }; - } - }, { - key: "isAnEndpoint", - value: function isAnEndpoint(pt) { - return pt.x === this.leftSE.point.x && pt.y === this.leftSE.point.y || pt.x === this.rightSE.point.x && pt.y === this.rightSE.point.y; - } - /* Compare this segment with a point. - * - * A point P is considered to be colinear to a segment if there - * exists a distance D such that if we travel along the segment - * from one * endpoint towards the other a distance D, we find - * ourselves at point P. - * - * Return value indicates: - * - * 1: point lies above the segment (to the left of vertical) - * 0: point is colinear to segment - * -1: point lies below the segment (to the right of vertical) - */ - - }, { - key: "comparePoint", - value: function comparePoint(point) { - if (this.isAnEndpoint(point)) return 0; - var lPt = this.leftSE.point; - var rPt = this.rightSE.point; - var v = this.vector(); // Exactly vertical segments. - - if (lPt.x === rPt.x) { - if (point.x === lPt.x) return 0; - return point.x < lPt.x ? 1 : -1; - } // Nearly vertical segments with an intersection. - // Check to see where a point on the line with matching Y coordinate is. - - - var yDist = (point.y - lPt.y) / v.y; - var xFromYDist = lPt.x + yDist * v.x; - if (point.x === xFromYDist) return 0; // General case. - // Check to see where a point on the line with matching X coordinate is. - - var xDist = (point.x - lPt.x) / v.x; - var yFromXDist = lPt.y + xDist * v.y; - if (point.y === yFromXDist) return 0; - return point.y < yFromXDist ? -1 : 1; - } - /** - * Given another segment, returns the first non-trivial intersection - * between the two segments (in terms of sweep line ordering), if it exists. - * - * A 'non-trivial' intersection is one that will cause one or both of the - * segments to be split(). As such, 'trivial' vs. 'non-trivial' intersection: - * - * * endpoint of segA with endpoint of segB --> trivial - * * endpoint of segA with point along segB --> non-trivial - * * endpoint of segB with point along segA --> non-trivial - * * point along segA with point along segB --> non-trivial - * - * If no non-trivial intersection exists, return null - * Else, return null. - */ - - }, { - key: "getIntersection", - value: function getIntersection(other) { - // If bboxes don't overlap, there can't be any intersections - var tBbox = this.bbox(); - var oBbox = other.bbox(); - var bboxOverlap = getBboxOverlap(tBbox, oBbox); - if (bboxOverlap === null) return null; // We first check to see if the endpoints can be considered intersections. - // This will 'snap' intersections to endpoints if possible, and will - // handle cases of colinearity. - - var tlp = this.leftSE.point; - var trp = this.rightSE.point; - var olp = other.leftSE.point; - var orp = other.rightSE.point; // does each endpoint touch the other segment? - // note that we restrict the 'touching' definition to only allow segments - // to touch endpoints that lie forward from where we are in the sweep line pass - - var touchesOtherLSE = isInBbox(tBbox, olp) && this.comparePoint(olp) === 0; - var touchesThisLSE = isInBbox(oBbox, tlp) && other.comparePoint(tlp) === 0; - var touchesOtherRSE = isInBbox(tBbox, orp) && this.comparePoint(orp) === 0; - var touchesThisRSE = isInBbox(oBbox, trp) && other.comparePoint(trp) === 0; // do left endpoints match? - - if (touchesThisLSE && touchesOtherLSE) { - // these two cases are for colinear segments with matching left - // endpoints, and one segment being longer than the other - if (touchesThisRSE && !touchesOtherRSE) return trp; - if (!touchesThisRSE && touchesOtherRSE) return orp; // either the two segments match exactly (two trival intersections) - // or just on their left endpoint (one trivial intersection - - return null; - } // does this left endpoint matches (other doesn't) - - - if (touchesThisLSE) { - // check for segments that just intersect on opposing endpoints - if (touchesOtherRSE) { - if (tlp.x === orp.x && tlp.y === orp.y) return null; - } // t-intersection on left endpoint - - - return tlp; - } // does other left endpoint matches (this doesn't) - - - if (touchesOtherLSE) { - // check for segments that just intersect on opposing endpoints - if (touchesThisRSE) { - if (trp.x === olp.x && trp.y === olp.y) return null; - } // t-intersection on left endpoint - - - return olp; - } // trivial intersection on right endpoints - - - if (touchesThisRSE && touchesOtherRSE) return null; // t-intersections on just one right endpoint - - if (touchesThisRSE) return trp; - if (touchesOtherRSE) return orp; // None of our endpoints intersect. Look for a general intersection between - // infinite lines laid over the segments - - var pt = intersection(tlp, this.vector(), olp, other.vector()); // are the segments parrallel? Note that if they were colinear with overlap, - // they would have an endpoint intersection and that case was already handled above - - if (pt === null) return null; // is the intersection found between the lines not on the segments? - - if (!isInBbox(bboxOverlap, pt)) return null; // round the the computed point if needed - - return rounder.round(pt.x, pt.y); - } - /** - * Split the given segment into multiple segments on the given points. - * * Each existing segment will retain its leftSE and a new rightSE will be - * generated for it. - * * A new segment will be generated which will adopt the original segment's - * rightSE, and a new leftSE will be generated for it. - * * If there are more than two points given to split on, new segments - * in the middle will be generated with new leftSE and rightSE's. - * * An array of the newly generated SweepEvents will be returned. - * - * Warning: input array of points is modified - */ - - }, { - key: "split", - value: function split(point) { - var newEvents = []; - var alreadyLinked = point.events !== undefined; - var newLeftSE = new SweepEvent(point, true); - var newRightSE = new SweepEvent(point, false); - var oldRightSE = this.rightSE; - this.replaceRightSE(newRightSE); - newEvents.push(newRightSE); - newEvents.push(newLeftSE); - var newSeg = new Segment(newLeftSE, oldRightSE, this.rings.slice(), this.windings.slice()); // when splitting a nearly vertical downward-facing segment, - // sometimes one of the resulting new segments is vertical, in which - // case its left and right events may need to be swapped - - if (SweepEvent.comparePoints(newSeg.leftSE.point, newSeg.rightSE.point) > 0) { - newSeg.swapEvents(); + /* When a segment is split, the rightSE is replaced with a new sweep event */ + replaceRightSE(newRightSE) { + this.rightSE = newRightSE; + this.rightSE.segment = this; + this.rightSE.otherSE = this.leftSE; + this.leftSE.otherSE = this.rightSE; + } + bbox() { + const y1 = this.leftSE.point.y; + const y2 = this.rightSE.point.y; + return { + ll: { + x: this.leftSE.point.x, + y: y1 < y2 ? y1 : y2 + }, + ur: { + x: this.rightSE.point.x, + y: y1 > y2 ? y1 : y2 } + }; + } - if (SweepEvent.comparePoints(this.leftSE.point, this.rightSE.point) > 0) { - this.swapEvents(); - } // in the point we just used to create new sweep events with was already - // linked to other events, we need to check if either of the affected - // segments should be consumed + /* A vector from the left point to the right */ + vector() { + return { + x: this.rightSE.point.x - this.leftSE.point.x, + y: this.rightSE.point.y - this.leftSE.point.y + }; + } + isAnEndpoint(pt) { + return pt.x === this.leftSE.point.x && pt.y === this.leftSE.point.y || pt.x === this.rightSE.point.x && pt.y === this.rightSE.point.y; + } + /* Compare this segment with a point. + * + * A point P is considered to be colinear to a segment if there + * exists a distance D such that if we travel along the segment + * from one * endpoint towards the other a distance D, we find + * ourselves at point P. + * + * Return value indicates: + * + * 1: point lies above the segment (to the left of vertical) + * 0: point is colinear to segment + * -1: point lies below the segment (to the right of vertical) + */ + comparePoint(point) { + if (this.isAnEndpoint(point)) return 0; + const lPt = this.leftSE.point; + const rPt = this.rightSE.point; + const v = this.vector(); + + // Exactly vertical segments. + if (lPt.x === rPt.x) { + if (point.x === lPt.x) return 0; + return point.x < lPt.x ? 1 : -1; + } - if (alreadyLinked) { - newLeftSE.checkForConsuming(); - newRightSE.checkForConsuming(); - } + // Nearly vertical segments with an intersection. + // Check to see where a point on the line with matching Y coordinate is. + const yDist = (point.y - lPt.y) / v.y; + const xFromYDist = lPt.x + yDist * v.x; + if (point.x === xFromYDist) return 0; + + // General case. + // Check to see where a point on the line with matching X coordinate is. + const xDist = (point.x - lPt.x) / v.x; + const yFromXDist = lPt.y + xDist * v.y; + if (point.y === yFromXDist) return 0; + return point.y < yFromXDist ? -1 : 1; + } - return newEvents; - } - /* Swap which event is left and right */ - - }, { - key: "swapEvents", - value: function swapEvents() { - var tmpEvt = this.rightSE; - this.rightSE = this.leftSE; - this.leftSE = tmpEvt; - this.leftSE.isLeft = true; - this.rightSE.isLeft = false; - - for (var i = 0, iMax = this.windings.length; i < iMax; i++) { - this.windings[i] *= -1; - } + /** + * Given another segment, returns the first non-trivial intersection + * between the two segments (in terms of sweep line ordering), if it exists. + * + * A 'non-trivial' intersection is one that will cause one or both of the + * segments to be split(). As such, 'trivial' vs. 'non-trivial' intersection: + * + * * endpoint of segA with endpoint of segB --> trivial + * * endpoint of segA with point along segB --> non-trivial + * * endpoint of segB with point along segA --> non-trivial + * * point along segA with point along segB --> non-trivial + * + * If no non-trivial intersection exists, return null + * Else, return null. + */ + getIntersection(other) { + // If bboxes don't overlap, there can't be any intersections + const tBbox = this.bbox(); + const oBbox = other.bbox(); + const bboxOverlap = getBboxOverlap(tBbox, oBbox); + if (bboxOverlap === null) return null; + + // We first check to see if the endpoints can be considered intersections. + // This will 'snap' intersections to endpoints if possible, and will + // handle cases of colinearity. + + const tlp = this.leftSE.point; + const trp = this.rightSE.point; + const olp = other.leftSE.point; + const orp = other.rightSE.point; + + // does each endpoint touch the other segment? + // note that we restrict the 'touching' definition to only allow segments + // to touch endpoints that lie forward from where we are in the sweep line pass + const touchesOtherLSE = isInBbox(tBbox, olp) && this.comparePoint(olp) === 0; + const touchesThisLSE = isInBbox(oBbox, tlp) && other.comparePoint(tlp) === 0; + const touchesOtherRSE = isInBbox(tBbox, orp) && this.comparePoint(orp) === 0; + const touchesThisRSE = isInBbox(oBbox, trp) && other.comparePoint(trp) === 0; + + // do left endpoints match? + if (touchesThisLSE && touchesOtherLSE) { + // these two cases are for colinear segments with matching left + // endpoints, and one segment being longer than the other + if (touchesThisRSE && !touchesOtherRSE) return trp; + if (!touchesThisRSE && touchesOtherRSE) return orp; + // either the two segments match exactly (two trival intersections) + // or just on their left endpoint (one trivial intersection + return null; } - /* Consume another segment. We take their rings under our wing - * and mark them as consumed. Use for perfectly overlapping segments */ - - }, { - key: "consume", - value: function consume(other) { - var consumer = this; - var consumee = other; - while (consumer.consumedBy) { - consumer = consumer.consumedBy; + // does this left endpoint matches (other doesn't) + if (touchesThisLSE) { + // check for segments that just intersect on opposing endpoints + if (touchesOtherRSE) { + if (tlp.x === orp.x && tlp.y === orp.y) return null; } + // t-intersection on left endpoint + return tlp; + } - while (consumee.consumedBy) { - consumee = consumee.consumedBy; + // does other left endpoint matches (this doesn't) + if (touchesOtherLSE) { + // check for segments that just intersect on opposing endpoints + if (touchesThisRSE) { + if (trp.x === olp.x && trp.y === olp.y) return null; } + // t-intersection on left endpoint + return olp; + } - var cmp = Segment.compare(consumer, consumee); - if (cmp === 0) return; // already consumed - // the winner of the consumption is the earlier segment - // according to sweep line ordering - - if (cmp > 0) { - var tmp = consumer; - consumer = consumee; - consumee = tmp; - } // make sure a segment doesn't consume it's prev + // trivial intersection on right endpoints + if (touchesThisRSE && touchesOtherRSE) return null; + // t-intersections on just one right endpoint + if (touchesThisRSE) return trp; + if (touchesOtherRSE) return orp; - if (consumer.prev === consumee) { - var _tmp = consumer; - consumer = consumee; - consumee = _tmp; - } + // None of our endpoints intersect. Look for a general intersection between + // infinite lines laid over the segments + const pt = intersection$1(tlp, this.vector(), olp, other.vector()); - for (var i = 0, iMax = consumee.rings.length; i < iMax; i++) { - var ring = consumee.rings[i]; - var winding = consumee.windings[i]; - var index = consumer.rings.indexOf(ring); + // are the segments parrallel? Note that if they were colinear with overlap, + // they would have an endpoint intersection and that case was already handled above + if (pt === null) return null; - if (index === -1) { - consumer.rings.push(ring); - consumer.windings.push(winding); - } else consumer.windings[index] += winding; - } + // is the intersection found between the lines not on the segments? + if (!isInBbox(bboxOverlap, pt)) return null; - consumee.rings = null; - consumee.windings = null; - consumee.consumedBy = consumer; // mark sweep events consumed as to maintain ordering in sweep event queue + // round the the computed point if needed + return rounder.round(pt.x, pt.y); + } - consumee.leftSE.consumedBy = consumer.leftSE; - consumee.rightSE.consumedBy = consumer.rightSE; - } - /* The first segment previous segment chain that is in the result */ - - }, { - key: "prevInResult", - value: function prevInResult() { - if (this._prevInResult !== undefined) return this._prevInResult; - if (!this.prev) this._prevInResult = null;else if (this.prev.isInResult()) this._prevInResult = this.prev;else this._prevInResult = this.prev.prevInResult(); - return this._prevInResult; + /** + * Split the given segment into multiple segments on the given points. + * * Each existing segment will retain its leftSE and a new rightSE will be + * generated for it. + * * A new segment will be generated which will adopt the original segment's + * rightSE, and a new leftSE will be generated for it. + * * If there are more than two points given to split on, new segments + * in the middle will be generated with new leftSE and rightSE's. + * * An array of the newly generated SweepEvents will be returned. + * + * Warning: input array of points is modified + */ + split(point) { + const newEvents = []; + const alreadyLinked = point.events !== undefined; + const newLeftSE = new SweepEvent(point, true); + const newRightSE = new SweepEvent(point, false); + const oldRightSE = this.rightSE; + this.replaceRightSE(newRightSE); + newEvents.push(newRightSE); + newEvents.push(newLeftSE); + const newSeg = new Segment(newLeftSE, oldRightSE, this.rings.slice(), this.windings.slice()); + + // when splitting a nearly vertical downward-facing segment, + // sometimes one of the resulting new segments is vertical, in which + // case its left and right events may need to be swapped + if (SweepEvent.comparePoints(newSeg.leftSE.point, newSeg.rightSE.point) > 0) { + newSeg.swapEvents(); } - }, { - key: "beforeState", - value: function beforeState() { - if (this._beforeState !== undefined) return this._beforeState; - if (!this.prev) this._beforeState = { - rings: [], - windings: [], - multiPolys: [] - };else { - var seg = this.prev.consumedBy || this.prev; - this._beforeState = seg.afterState(); - } - return this._beforeState; + if (SweepEvent.comparePoints(this.leftSE.point, this.rightSE.point) > 0) { + this.swapEvents(); } - }, { - key: "afterState", - value: function afterState() { - if (this._afterState !== undefined) return this._afterState; - var beforeState = this.beforeState(); - this._afterState = { - rings: beforeState.rings.slice(0), - windings: beforeState.windings.slice(0), - multiPolys: [] - }; - var ringsAfter = this._afterState.rings; - var windingsAfter = this._afterState.windings; - var mpsAfter = this._afterState.multiPolys; // calculate ringsAfter, windingsAfter - - for (var i = 0, iMax = this.rings.length; i < iMax; i++) { - var ring = this.rings[i]; - var winding = this.windings[i]; - var index = ringsAfter.indexOf(ring); - - if (index === -1) { - ringsAfter.push(ring); - windingsAfter.push(winding); - } else windingsAfter[index] += winding; - } // calcualte polysAfter - - - var polysAfter = []; - var polysExclude = []; - - for (var _i = 0, _iMax = ringsAfter.length; _i < _iMax; _i++) { - if (windingsAfter[_i] === 0) continue; // non-zero rule - - var _ring = ringsAfter[_i]; - var poly = _ring.poly; - if (polysExclude.indexOf(poly) !== -1) continue; - if (_ring.isExterior) polysAfter.push(poly);else { - if (polysExclude.indexOf(poly) === -1) polysExclude.push(poly); - - var _index = polysAfter.indexOf(_ring.poly); - - if (_index !== -1) polysAfter.splice(_index, 1); - } - } // calculate multiPolysAfter - - for (var _i2 = 0, _iMax2 = polysAfter.length; _i2 < _iMax2; _i2++) { - var mp = polysAfter[_i2].multiPoly; - if (mpsAfter.indexOf(mp) === -1) mpsAfter.push(mp); - } - - return this._afterState; + // in the point we just used to create new sweep events with was already + // linked to other events, we need to check if either of the affected + // segments should be consumed + if (alreadyLinked) { + newLeftSE.checkForConsuming(); + newRightSE.checkForConsuming(); } - /* Is this segment part of the final result? */ - - }, { - key: "isInResult", - value: function isInResult() { - // if we've been consumed, we're not in the result - if (this.consumedBy) return false; - if (this._isInResult !== undefined) return this._isInResult; - var mpsBefore = this.beforeState().multiPolys; - var mpsAfter = this.afterState().multiPolys; - - switch (operation.type) { - case 'union': - { - // UNION - included iff: - // * On one side of us there is 0 poly interiors AND - // * On the other side there is 1 or more. - var noBefores = mpsBefore.length === 0; - var noAfters = mpsAfter.length === 0; - this._isInResult = noBefores !== noAfters; - break; - } + return newEvents; + } - case 'intersection': - { - // INTERSECTION - included iff: - // * on one side of us all multipolys are rep. with poly interiors AND - // * on the other side of us, not all multipolys are repsented - // with poly interiors - var least; - var most; - - if (mpsBefore.length < mpsAfter.length) { - least = mpsBefore.length; - most = mpsAfter.length; - } else { - least = mpsAfter.length; - most = mpsBefore.length; - } + /* Swap which event is left and right */ + swapEvents() { + const tmpEvt = this.rightSE; + this.rightSE = this.leftSE; + this.leftSE = tmpEvt; + this.leftSE.isLeft = true; + this.rightSE.isLeft = false; + for (let i = 0, iMax = this.windings.length; i < iMax; i++) { + this.windings[i] *= -1; + } + } - this._isInResult = most === operation.numMultiPolys && least < most; - break; - } + /* Consume another segment. We take their rings under our wing + * and mark them as consumed. Use for perfectly overlapping segments */ + consume(other) { + let consumer = this; + let consumee = other; + while (consumer.consumedBy) consumer = consumer.consumedBy; + while (consumee.consumedBy) consumee = consumee.consumedBy; + const cmp = Segment.compare(consumer, consumee); + if (cmp === 0) return; // already consumed + // the winner of the consumption is the earlier segment + // according to sweep line ordering + if (cmp > 0) { + const tmp = consumer; + consumer = consumee; + consumee = tmp; + } - case 'xor': - { - // XOR - included iff: - // * the difference between the number of multipolys represented - // with poly interiors on our two sides is an odd number - var diff = Math.abs(mpsBefore.length - mpsAfter.length); - this._isInResult = diff % 2 === 1; - break; - } + // make sure a segment doesn't consume it's prev + if (consumer.prev === consumee) { + const tmp = consumer; + consumer = consumee; + consumee = tmp; + } + for (let i = 0, iMax = consumee.rings.length; i < iMax; i++) { + const ring = consumee.rings[i]; + const winding = consumee.windings[i]; + const index = consumer.rings.indexOf(ring); + if (index === -1) { + consumer.rings.push(ring); + consumer.windings.push(winding); + } else consumer.windings[index] += winding; + } + consumee.rings = null; + consumee.windings = null; + consumee.consumedBy = consumer; - case 'difference': - { - // DIFFERENCE included iff: - // * on exactly one side, we have just the subject - var isJustSubject = function isJustSubject(mps) { - return mps.length === 1 && mps[0].isSubject; - }; + // mark sweep events consumed as to maintain ordering in sweep event queue + consumee.leftSE.consumedBy = consumer.leftSE; + consumee.rightSE.consumedBy = consumer.rightSE; + } - this._isInResult = isJustSubject(mpsBefore) !== isJustSubject(mpsAfter); - break; - } + /* The first segment previous segment chain that is in the result */ + prevInResult() { + if (this._prevInResult !== undefined) return this._prevInResult; + if (!this.prev) this._prevInResult = null;else if (this.prev.isInResult()) this._prevInResult = this.prev;else this._prevInResult = this.prev.prevInResult(); + return this._prevInResult; + } + beforeState() { + if (this._beforeState !== undefined) return this._beforeState; + if (!this.prev) this._beforeState = { + rings: [], + windings: [], + multiPolys: [] + };else { + const seg = this.prev.consumedBy || this.prev; + this._beforeState = seg.afterState(); + } + return this._beforeState; + } + afterState() { + if (this._afterState !== undefined) return this._afterState; + const beforeState = this.beforeState(); + this._afterState = { + rings: beforeState.rings.slice(0), + windings: beforeState.windings.slice(0), + multiPolys: [] + }; + const ringsAfter = this._afterState.rings; + const windingsAfter = this._afterState.windings; + const mpsAfter = this._afterState.multiPolys; + + // calculate ringsAfter, windingsAfter + for (let i = 0, iMax = this.rings.length; i < iMax; i++) { + const ring = this.rings[i]; + const winding = this.windings[i]; + const index = ringsAfter.indexOf(ring); + if (index === -1) { + ringsAfter.push(ring); + windingsAfter.push(winding); + } else windingsAfter[index] += winding; + } - default: - throw new Error("Unrecognized operation type found ".concat(operation.type)); + // calcualte polysAfter + const polysAfter = []; + const polysExclude = []; + for (let i = 0, iMax = ringsAfter.length; i < iMax; i++) { + if (windingsAfter[i] === 0) continue; // non-zero rule + const ring = ringsAfter[i]; + const poly = ring.poly; + if (polysExclude.indexOf(poly) !== -1) continue; + if (ring.isExterior) polysAfter.push(poly);else { + if (polysExclude.indexOf(poly) === -1) polysExclude.push(poly); + const index = polysAfter.indexOf(ring.poly); + if (index !== -1) polysAfter.splice(index, 1); } - - return this._isInResult; } - }], [{ - key: "fromRing", - value: function fromRing(pt1, pt2, ring) { - var leftPt, rightPt, winding; // ordering the two points according to sweep line ordering - - var cmpPts = SweepEvent.comparePoints(pt1, pt2); - - if (cmpPts < 0) { - leftPt = pt1; - rightPt = pt2; - winding = 1; - } else if (cmpPts > 0) { - leftPt = pt2; - rightPt = pt1; - winding = -1; - } else throw new Error("Tried to create degenerate segment at [".concat(pt1.x, ", ").concat(pt1.y, "]")); - - var leftSE = new SweepEvent(leftPt, true); - var rightSE = new SweepEvent(rightPt, false); - return new Segment(leftSE, rightSE, [ring], [winding]); - } - }]); - return Segment; -}(); + // calculate multiPolysAfter + for (let i = 0, iMax = polysAfter.length; i < iMax; i++) { + const mp = polysAfter[i].multiPoly; + if (mpsAfter.indexOf(mp) === -1) mpsAfter.push(mp); + } + return this._afterState; + } -var RingIn = /*#__PURE__*/function () { - function RingIn(geomRing, poly, isExterior) { - _classCallCheck(this, RingIn); + /* Is this segment part of the final result? */ + isInResult() { + // if we've been consumed, we're not in the result + if (this.consumedBy) return false; + if (this._isInResult !== undefined) return this._isInResult; + const mpsBefore = this.beforeState().multiPolys; + const mpsAfter = this.afterState().multiPolys; + switch (operation.type) { + case "union": + { + // UNION - included iff: + // * On one side of us there is 0 poly interiors AND + // * On the other side there is 1 or more. + const noBefores = mpsBefore.length === 0; + const noAfters = mpsAfter.length === 0; + this._isInResult = noBefores !== noAfters; + break; + } + case "intersection": + { + // INTERSECTION - included iff: + // * on one side of us all multipolys are rep. with poly interiors AND + // * on the other side of us, not all multipolys are repsented + // with poly interiors + let least; + let most; + if (mpsBefore.length < mpsAfter.length) { + least = mpsBefore.length; + most = mpsAfter.length; + } else { + least = mpsAfter.length; + most = mpsBefore.length; + } + this._isInResult = most === operation.numMultiPolys && least < most; + break; + } + case "xor": + { + // XOR - included iff: + // * the difference between the number of multipolys represented + // with poly interiors on our two sides is an odd number + const diff = Math.abs(mpsBefore.length - mpsAfter.length); + this._isInResult = diff % 2 === 1; + break; + } + case "difference": + { + // DIFFERENCE included iff: + // * on exactly one side, we have just the subject + const isJustSubject = mps => mps.length === 1 && mps[0].isSubject; + this._isInResult = isJustSubject(mpsBefore) !== isJustSubject(mpsAfter); + break; + } + default: + throw new Error(`Unrecognized operation type found ${operation.type}`); + } + return this._isInResult; + } +} +class RingIn { + constructor(geomRing, poly, isExterior) { if (!Array.isArray(geomRing) || geomRing.length === 0) { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - this.poly = poly; this.isExterior = isExterior; this.segments = []; - - if (typeof geomRing[0][0] !== 'number' || typeof geomRing[0][1] !== 'number') { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + if (typeof geomRing[0][0] !== "number" || typeof geomRing[0][1] !== "number") { + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - - var firstPoint = rounder.round(geomRing[0][0], geomRing[0][1]); + const firstPoint = rounder.round(geomRing[0][0], geomRing[0][1]); this.bbox = { ll: { x: firstPoint.x, @@ -1060,15 +935,13 @@ var RingIn = /*#__PURE__*/function () { y: firstPoint.y } }; - var prevPoint = firstPoint; - - for (var i = 1, iMax = geomRing.length; i < iMax; i++) { - if (typeof geomRing[i][0] !== 'number' || typeof geomRing[i][1] !== 'number') { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + let prevPoint = firstPoint; + for (let i = 1, iMax = geomRing.length; i < iMax; i++) { + if (typeof geomRing[i][0] !== "number" || typeof geomRing[i][1] !== "number") { + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - - var point = rounder.round(geomRing[i][0], geomRing[i][1]); // skip repeated points - + let point = rounder.round(geomRing[i][0], geomRing[i][1]); + // skip repeated points if (point.x === prevPoint.x && point.y === prevPoint.y) continue; this.segments.push(Segment.fromRing(prevPoint, point, this)); if (point.x < this.bbox.ll.x) this.bbox.ll.x = point.x; @@ -1076,41 +949,29 @@ var RingIn = /*#__PURE__*/function () { if (point.x > this.bbox.ur.x) this.bbox.ur.x = point.x; if (point.y > this.bbox.ur.y) this.bbox.ur.y = point.y; prevPoint = point; - } // add segment from last to first if last is not the same as first - - + } + // add segment from last to first if last is not the same as first if (firstPoint.x !== prevPoint.x || firstPoint.y !== prevPoint.y) { this.segments.push(Segment.fromRing(prevPoint, firstPoint, this)); } } - - _createClass(RingIn, [{ - key: "getSweepEvents", - value: function getSweepEvents() { - var sweepEvents = []; - - for (var i = 0, iMax = this.segments.length; i < iMax; i++) { - var segment = this.segments[i]; - sweepEvents.push(segment.leftSE); - sweepEvents.push(segment.rightSE); - } - - return sweepEvents; + getSweepEvents() { + const sweepEvents = []; + for (let i = 0, iMax = this.segments.length; i < iMax; i++) { + const segment = this.segments[i]; + sweepEvents.push(segment.leftSE); + sweepEvents.push(segment.rightSE); } - }]); - - return RingIn; -}(); -var PolyIn = /*#__PURE__*/function () { - function PolyIn(geomPoly, multiPoly) { - _classCallCheck(this, PolyIn); - + return sweepEvents; + } +} +class PolyIn { + constructor(geomPoly, multiPoly) { if (!Array.isArray(geomPoly)) { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - - this.exteriorRing = new RingIn(geomPoly[0], this, true); // copy by value - + this.exteriorRing = new RingIn(geomPoly[0], this, true); + // copy by value this.bbox = { ll: { x: this.exteriorRing.bbox.ll.x, @@ -1122,53 +983,39 @@ var PolyIn = /*#__PURE__*/function () { } }; this.interiorRings = []; - - for (var i = 1, iMax = geomPoly.length; i < iMax; i++) { - var ring = new RingIn(geomPoly[i], this, false); + for (let i = 1, iMax = geomPoly.length; i < iMax; i++) { + const ring = new RingIn(geomPoly[i], this, false); if (ring.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = ring.bbox.ll.x; if (ring.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = ring.bbox.ll.y; if (ring.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = ring.bbox.ur.x; if (ring.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = ring.bbox.ur.y; this.interiorRings.push(ring); } - this.multiPoly = multiPoly; } - - _createClass(PolyIn, [{ - key: "getSweepEvents", - value: function getSweepEvents() { - var sweepEvents = this.exteriorRing.getSweepEvents(); - - for (var i = 0, iMax = this.interiorRings.length; i < iMax; i++) { - var ringSweepEvents = this.interiorRings[i].getSweepEvents(); - - for (var j = 0, jMax = ringSweepEvents.length; j < jMax; j++) { - sweepEvents.push(ringSweepEvents[j]); - } + getSweepEvents() { + const sweepEvents = this.exteriorRing.getSweepEvents(); + for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) { + const ringSweepEvents = this.interiorRings[i].getSweepEvents(); + for (let j = 0, jMax = ringSweepEvents.length; j < jMax; j++) { + sweepEvents.push(ringSweepEvents[j]); } - - return sweepEvents; } - }]); - - return PolyIn; -}(); -var MultiPolyIn = /*#__PURE__*/function () { - function MultiPolyIn(geom, isSubject) { - _classCallCheck(this, MultiPolyIn); - + return sweepEvents; + } +} +class MultiPolyIn { + constructor(geom, isSubject) { if (!Array.isArray(geom)) { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - try { // if the input looks like a polygon, convert it to a multipolygon - if (typeof geom[0][0][0] === 'number') geom = [geom]; - } catch (ex) {// The input is either malformed or has empty arrays. + if (typeof geom[0][0][0] === "number") geom = [geom]; + } catch (ex) { + // The input is either malformed or has empty arrays. // In either case, it will be handled later on. } - this.polys = []; this.bbox = { ll: { @@ -1180,311 +1027,234 @@ var MultiPolyIn = /*#__PURE__*/function () { y: Number.NEGATIVE_INFINITY } }; - - for (var i = 0, iMax = geom.length; i < iMax; i++) { - var poly = new PolyIn(geom[i], this); + for (let i = 0, iMax = geom.length; i < iMax; i++) { + const poly = new PolyIn(geom[i], this); if (poly.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = poly.bbox.ll.x; if (poly.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = poly.bbox.ll.y; if (poly.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = poly.bbox.ur.x; if (poly.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = poly.bbox.ur.y; this.polys.push(poly); } - this.isSubject = isSubject; } - - _createClass(MultiPolyIn, [{ - key: "getSweepEvents", - value: function getSweepEvents() { - var sweepEvents = []; - - for (var i = 0, iMax = this.polys.length; i < iMax; i++) { - var polySweepEvents = this.polys[i].getSweepEvents(); - - for (var j = 0, jMax = polySweepEvents.length; j < jMax; j++) { - sweepEvents.push(polySweepEvents[j]); - } + getSweepEvents() { + const sweepEvents = []; + for (let i = 0, iMax = this.polys.length; i < iMax; i++) { + const polySweepEvents = this.polys[i].getSweepEvents(); + for (let j = 0, jMax = polySweepEvents.length; j < jMax; j++) { + sweepEvents.push(polySweepEvents[j]); } - - return sweepEvents; } - }]); - - return MultiPolyIn; -}(); - -var RingOut = /*#__PURE__*/function () { - _createClass(RingOut, null, [{ - key: "factory", - - /* Given the segments from the sweep line pass, compute & return a series - * of closed rings from all the segments marked to be part of the result */ - value: function factory(allSegments) { - var ringsOut = []; - - for (var i = 0, iMax = allSegments.length; i < iMax; i++) { - var segment = allSegments[i]; - if (!segment.isInResult() || segment.ringOut) continue; - var prevEvent = null; - var event = segment.leftSE; - var nextEvent = segment.rightSE; - var events = [event]; - var startingPoint = event.point; - var intersectionLEs = []; - /* Walk the chain of linked events to form a closed ring */ + return sweepEvents; + } +} + +class RingOut { + /* Given the segments from the sweep line pass, compute & return a series + * of closed rings from all the segments marked to be part of the result */ + static factory(allSegments) { + const ringsOut = []; + for (let i = 0, iMax = allSegments.length; i < iMax; i++) { + const segment = allSegments[i]; + if (!segment.isInResult() || segment.ringOut) continue; + let prevEvent = null; + let event = segment.leftSE; + let nextEvent = segment.rightSE; + const events = [event]; + const startingPoint = event.point; + const intersectionLEs = []; + + /* Walk the chain of linked events to form a closed ring */ + while (true) { + prevEvent = event; + event = nextEvent; + events.push(event); + /* Is the ring complete? */ + if (event.point === startingPoint) break; while (true) { - prevEvent = event; - event = nextEvent; - events.push(event); - /* Is the ring complete? */ - - if (event.point === startingPoint) break; - - while (true) { - var availableLEs = event.getAvailableLinkedEvents(); - /* Did we hit a dead end? This shouldn't happen. Indicates some earlier - * part of the algorithm malfunctioned... please file a bug report. */ - - if (availableLEs.length === 0) { - var firstPt = events[0].point; - var lastPt = events[events.length - 1].point; - throw new Error("Unable to complete output ring starting at [".concat(firstPt.x, ",") + " ".concat(firstPt.y, "]. Last matching segment found ends at") + " [".concat(lastPt.x, ", ").concat(lastPt.y, "].")); - } - /* Only one way to go, so cotinue on the path */ + const availableLEs = event.getAvailableLinkedEvents(); + + /* Did we hit a dead end? This shouldn't happen. + * Indicates some earlier part of the algorithm malfunctioned. */ + if (availableLEs.length === 0) { + const firstPt = events[0].point; + const lastPt = events[events.length - 1].point; + throw new Error(`Unable to complete output ring starting at [${firstPt.x},` + ` ${firstPt.y}]. Last matching segment found ends at` + ` [${lastPt.x}, ${lastPt.y}].`); + } + /* Only one way to go, so cotinue on the path */ + if (availableLEs.length === 1) { + nextEvent = availableLEs[0].otherSE; + break; + } - if (availableLEs.length === 1) { - nextEvent = availableLEs[0].otherSE; + /* We must have an intersection. Check for a completed loop */ + let indexLE = null; + for (let j = 0, jMax = intersectionLEs.length; j < jMax; j++) { + if (intersectionLEs[j].point === event.point) { + indexLE = j; break; } - /* We must have an intersection. Check for a completed loop */ - - - var indexLE = null; - - for (var j = 0, jMax = intersectionLEs.length; j < jMax; j++) { - if (intersectionLEs[j].point === event.point) { - indexLE = j; - break; - } - } - /* Found a completed loop. Cut that off and make a ring */ - - - if (indexLE !== null) { - var intersectionLE = intersectionLEs.splice(indexLE)[0]; - var ringEvents = events.splice(intersectionLE.index); - ringEvents.unshift(ringEvents[0].otherSE); - ringsOut.push(new RingOut(ringEvents.reverse())); - continue; - } - /* register the intersection */ - - - intersectionLEs.push({ - index: events.length, - point: event.point - }); - /* Choose the left-most option to continue the walk */ - - var comparator = event.getLeftmostComparator(prevEvent); - nextEvent = availableLEs.sort(comparator)[0].otherSE; - break; } + /* Found a completed loop. Cut that off and make a ring */ + if (indexLE !== null) { + const intersectionLE = intersectionLEs.splice(indexLE)[0]; + const ringEvents = events.splice(intersectionLE.index); + ringEvents.unshift(ringEvents[0].otherSE); + ringsOut.push(new RingOut(ringEvents.reverse())); + continue; + } + /* register the intersection */ + intersectionLEs.push({ + index: events.length, + point: event.point + }); + /* Choose the left-most option to continue the walk */ + const comparator = event.getLeftmostComparator(prevEvent); + nextEvent = availableLEs.sort(comparator)[0].otherSE; + break; } - - ringsOut.push(new RingOut(events)); } - - return ringsOut; + ringsOut.push(new RingOut(events)); } - }]); - - function RingOut(events) { - _classCallCheck(this, RingOut); - + return ringsOut; + } + constructor(events) { this.events = events; - - for (var i = 0, iMax = events.length; i < iMax; i++) { + for (let i = 0, iMax = events.length; i < iMax; i++) { events[i].segment.ringOut = this; } - this.poly = null; } - - _createClass(RingOut, [{ - key: "getGeom", - value: function getGeom() { - // Remove superfluous points (ie extra points along a straight line), - var prevPt = this.events[0].point; - var points = [prevPt]; - - for (var i = 1, iMax = this.events.length - 1; i < iMax; i++) { - var _pt = this.events[i].point; - var _nextPt = this.events[i + 1].point; - if (compareVectorAngles(_pt, prevPt, _nextPt) === 0) continue; - points.push(_pt); - prevPt = _pt; - } // ring was all (within rounding error of angle calc) colinear points - - - if (points.length === 1) return null; // check if the starting point is necessary - - var pt = points[0]; - var nextPt = points[1]; - if (compareVectorAngles(pt, prevPt, nextPt) === 0) points.shift(); - points.push(points[0]); - var step = this.isExteriorRing() ? 1 : -1; - var iStart = this.isExteriorRing() ? 0 : points.length - 1; - var iEnd = this.isExteriorRing() ? points.length : -1; - var orderedPoints = []; - - for (var _i = iStart; _i != iEnd; _i += step) { - orderedPoints.push([points[_i].x, points[_i].y]); - } - - return orderedPoints; + getGeom() { + // Remove superfluous points (ie extra points along a straight line), + let prevPt = this.events[0].point; + const points = [prevPt]; + for (let i = 1, iMax = this.events.length - 1; i < iMax; i++) { + const pt = this.events[i].point; + const nextPt = this.events[i + 1].point; + if (compareVectorAngles(pt, prevPt, nextPt) === 0) continue; + points.push(pt); + prevPt = pt; } - }, { - key: "isExteriorRing", - value: function isExteriorRing() { - if (this._isExteriorRing === undefined) { - var enclosing = this.enclosingRing(); - this._isExteriorRing = enclosing ? !enclosing.isExteriorRing() : true; - } - return this._isExteriorRing; + // ring was all (within rounding error of angle calc) colinear points + if (points.length === 1) return null; + + // check if the starting point is necessary + const pt = points[0]; + const nextPt = points[1]; + if (compareVectorAngles(pt, prevPt, nextPt) === 0) points.shift(); + points.push(points[0]); + const step = this.isExteriorRing() ? 1 : -1; + const iStart = this.isExteriorRing() ? 0 : points.length - 1; + const iEnd = this.isExteriorRing() ? points.length : -1; + const orderedPoints = []; + for (let i = iStart; i != iEnd; i += step) orderedPoints.push([points[i].x, points[i].y]); + return orderedPoints; + } + isExteriorRing() { + if (this._isExteriorRing === undefined) { + const enclosing = this.enclosingRing(); + this._isExteriorRing = enclosing ? !enclosing.isExteriorRing() : true; } - }, { - key: "enclosingRing", - value: function enclosingRing() { - if (this._enclosingRing === undefined) { - this._enclosingRing = this._calcEnclosingRing(); - } + return this._isExteriorRing; + } + enclosingRing() { + if (this._enclosingRing === undefined) { + this._enclosingRing = this._calcEnclosingRing(); + } + return this._enclosingRing; + } - return this._enclosingRing; + /* Returns the ring that encloses this one, if any */ + _calcEnclosingRing() { + // start with the ealier sweep line event so that the prevSeg + // chain doesn't lead us inside of a loop of ours + let leftMostEvt = this.events[0]; + for (let i = 1, iMax = this.events.length; i < iMax; i++) { + const evt = this.events[i]; + if (SweepEvent.compare(leftMostEvt, evt) > 0) leftMostEvt = evt; } - /* Returns the ring that encloses this one, if any */ - - }, { - key: "_calcEnclosingRing", - value: function _calcEnclosingRing() { - // start with the ealier sweep line event so that the prevSeg - // chain doesn't lead us inside of a loop of ours - var leftMostEvt = this.events[0]; - - for (var i = 1, iMax = this.events.length; i < iMax; i++) { - var evt = this.events[i]; - if (SweepEvent.compare(leftMostEvt, evt) > 0) leftMostEvt = evt; + let prevSeg = leftMostEvt.segment.prevInResult(); + let prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; + while (true) { + // no segment found, thus no ring can enclose us + if (!prevSeg) return null; + + // no segments below prev segment found, thus the ring of the prev + // segment must loop back around and enclose us + if (!prevPrevSeg) return prevSeg.ringOut; + + // if the two segments are of different rings, the ring of the prev + // segment must either loop around us or the ring of the prev prev + // seg, which would make us and the ring of the prev peers + if (prevPrevSeg.ringOut !== prevSeg.ringOut) { + if (prevPrevSeg.ringOut.enclosingRing() !== prevSeg.ringOut) { + return prevSeg.ringOut; + } else return prevSeg.ringOut.enclosingRing(); } - var prevSeg = leftMostEvt.segment.prevInResult(); - var prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; - - while (true) { - // no segment found, thus no ring can enclose us - if (!prevSeg) return null; // no segments below prev segment found, thus the ring of the prev - // segment must loop back around and enclose us - - if (!prevPrevSeg) return prevSeg.ringOut; // if the two segments are of different rings, the ring of the prev - // segment must either loop around us or the ring of the prev prev - // seg, which would make us and the ring of the prev peers - - if (prevPrevSeg.ringOut !== prevSeg.ringOut) { - if (prevPrevSeg.ringOut.enclosingRing() !== prevSeg.ringOut) { - return prevSeg.ringOut; - } else return prevSeg.ringOut.enclosingRing(); - } // two segments are from the same ring, so this was a penisula - // of that ring. iterate downward, keep searching - - - prevSeg = prevPrevSeg.prevInResult(); - prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; - } + // two segments are from the same ring, so this was a penisula + // of that ring. iterate downward, keep searching + prevSeg = prevPrevSeg.prevInResult(); + prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; } - }]); - - return RingOut; -}(); -var PolyOut = /*#__PURE__*/function () { - function PolyOut(exteriorRing) { - _classCallCheck(this, PolyOut); - + } +} +class PolyOut { + constructor(exteriorRing) { this.exteriorRing = exteriorRing; exteriorRing.poly = this; this.interiorRings = []; } - - _createClass(PolyOut, [{ - key: "addInterior", - value: function addInterior(ring) { - this.interiorRings.push(ring); - ring.poly = this; - } - }, { - key: "getGeom", - value: function getGeom() { - var geom = [this.exteriorRing.getGeom()]; // exterior ring was all (within rounding error of angle calc) colinear points - - if (geom[0] === null) return null; - - for (var i = 0, iMax = this.interiorRings.length; i < iMax; i++) { - var ringGeom = this.interiorRings[i].getGeom(); // interior ring was all (within rounding error of angle calc) colinear points - - if (ringGeom === null) continue; - geom.push(ringGeom); - } - - return geom; + addInterior(ring) { + this.interiorRings.push(ring); + ring.poly = this; + } + getGeom() { + const geom = [this.exteriorRing.getGeom()]; + // exterior ring was all (within rounding error of angle calc) colinear points + if (geom[0] === null) return null; + for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) { + const ringGeom = this.interiorRings[i].getGeom(); + // interior ring was all (within rounding error of angle calc) colinear points + if (ringGeom === null) continue; + geom.push(ringGeom); } - }]); - - return PolyOut; -}(); -var MultiPolyOut = /*#__PURE__*/function () { - function MultiPolyOut(rings) { - _classCallCheck(this, MultiPolyOut); - + return geom; + } +} +class MultiPolyOut { + constructor(rings) { this.rings = rings; this.polys = this._composePolys(rings); } - - _createClass(MultiPolyOut, [{ - key: "getGeom", - value: function getGeom() { - var geom = []; - - for (var i = 0, iMax = this.polys.length; i < iMax; i++) { - var polyGeom = this.polys[i].getGeom(); // exterior ring was all (within rounding error of angle calc) colinear points - - if (polyGeom === null) continue; - geom.push(polyGeom); - } - - return geom; + getGeom() { + const geom = []; + for (let i = 0, iMax = this.polys.length; i < iMax; i++) { + const polyGeom = this.polys[i].getGeom(); + // exterior ring was all (within rounding error of angle calc) colinear points + if (polyGeom === null) continue; + geom.push(polyGeom); } - }, { - key: "_composePolys", - value: function _composePolys(rings) { - var polys = []; - - for (var i = 0, iMax = rings.length; i < iMax; i++) { - var ring = rings[i]; - if (ring.poly) continue; - if (ring.isExteriorRing()) polys.push(new PolyOut(ring));else { - var enclosingRing = ring.enclosingRing(); - if (!enclosingRing.poly) polys.push(new PolyOut(enclosingRing)); - enclosingRing.poly.addInterior(ring); - } + return geom; + } + _composePolys(rings) { + const polys = []; + for (let i = 0, iMax = rings.length; i < iMax; i++) { + const ring = rings[i]; + if (ring.poly) continue; + if (ring.isExteriorRing()) polys.push(new PolyOut(ring));else { + const enclosingRing = ring.enclosingRing(); + if (!enclosingRing.poly) polys.push(new PolyOut(enclosingRing)); + enclosingRing.poly.addInterior(ring); } - - return polys; } - }]); - - return MultiPolyOut; -}(); + return polys; + } +} /** * NOTE: We must be careful not to change any segments while @@ -1497,328 +1267,270 @@ var MultiPolyOut = /*#__PURE__*/function () { * it sometimes does.) */ -var SweepLine = /*#__PURE__*/function () { - function SweepLine(queue) { - var comparator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Segment.compare; - - _classCallCheck(this, SweepLine); - +class SweepLine { + constructor(queue) { + let comparator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Segment.compare; this.queue = queue; - this.tree = new SplayTree__default['default'](comparator); + this.tree = new SplayTree__default["default"](comparator); this.segments = []; } + process(event) { + const segment = event.segment; + const newEvents = []; + + // if we've already been consumed by another segment, + // clean up our body parts and get out + if (event.consumedBy) { + if (event.isLeft) this.queue.remove(event.otherSE);else this.tree.remove(segment); + return newEvents; + } + const node = event.isLeft ? this.tree.add(segment) : this.tree.find(segment); + if (!node) throw new Error(`Unable to find segment #${segment.id} ` + `[${segment.leftSE.point.x}, ${segment.leftSE.point.y}] -> ` + `[${segment.rightSE.point.x}, ${segment.rightSE.point.y}] ` + "in SweepLine tree."); + let prevNode = node; + let nextNode = node; + let prevSeg = undefined; + let nextSeg = undefined; + + // skip consumed segments still in tree + while (prevSeg === undefined) { + prevNode = this.tree.prev(prevNode); + if (prevNode === null) prevSeg = null;else if (prevNode.key.consumedBy === undefined) prevSeg = prevNode.key; + } - _createClass(SweepLine, [{ - key: "process", - value: function process(event) { - var segment = event.segment; - var newEvents = []; // if we've already been consumed by another segment, - // clean up our body parts and get out - - if (event.consumedBy) { - if (event.isLeft) this.queue.remove(event.otherSE);else this.tree.remove(segment); - return newEvents; - } - - var node = event.isLeft ? this.tree.insert(segment) : this.tree.find(segment); - if (!node) throw new Error("Unable to find segment #".concat(segment.id, " ") + "[".concat(segment.leftSE.point.x, ", ").concat(segment.leftSE.point.y, "] -> ") + "[".concat(segment.rightSE.point.x, ", ").concat(segment.rightSE.point.y, "] ") + 'in SweepLine tree. Please submit a bug report.'); - var prevNode = node; - var nextNode = node; - var prevSeg = undefined; - var nextSeg = undefined; // skip consumed segments still in tree - - while (prevSeg === undefined) { - prevNode = this.tree.prev(prevNode); - if (prevNode === null) prevSeg = null;else if (prevNode.key.consumedBy === undefined) prevSeg = prevNode.key; - } // skip consumed segments still in tree - - - while (nextSeg === undefined) { - nextNode = this.tree.next(nextNode); - if (nextNode === null) nextSeg = null;else if (nextNode.key.consumedBy === undefined) nextSeg = nextNode.key; - } - - if (event.isLeft) { - // Check for intersections against the previous segment in the sweep line - var prevMySplitter = null; - - if (prevSeg) { - var prevInter = prevSeg.getIntersection(segment); - - if (prevInter !== null) { - if (!segment.isAnEndpoint(prevInter)) prevMySplitter = prevInter; - - if (!prevSeg.isAnEndpoint(prevInter)) { - var newEventsFromSplit = this._splitSafely(prevSeg, prevInter); - - for (var i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { - newEvents.push(newEventsFromSplit[i]); - } + // skip consumed segments still in tree + while (nextSeg === undefined) { + nextNode = this.tree.next(nextNode); + if (nextNode === null) nextSeg = null;else if (nextNode.key.consumedBy === undefined) nextSeg = nextNode.key; + } + if (event.isLeft) { + // Check for intersections against the previous segment in the sweep line + let prevMySplitter = null; + if (prevSeg) { + const prevInter = prevSeg.getIntersection(segment); + if (prevInter !== null) { + if (!segment.isAnEndpoint(prevInter)) prevMySplitter = prevInter; + if (!prevSeg.isAnEndpoint(prevInter)) { + const newEventsFromSplit = this._splitSafely(prevSeg, prevInter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } } - } // Check for intersections against the next segment in the sweep line - - - var nextMySplitter = null; - - if (nextSeg) { - var nextInter = nextSeg.getIntersection(segment); - - if (nextInter !== null) { - if (!segment.isAnEndpoint(nextInter)) nextMySplitter = nextInter; - - if (!nextSeg.isAnEndpoint(nextInter)) { - var _newEventsFromSplit = this._splitSafely(nextSeg, nextInter); + } + } - for (var _i = 0, _iMax = _newEventsFromSplit.length; _i < _iMax; _i++) { - newEvents.push(_newEventsFromSplit[_i]); - } + // Check for intersections against the next segment in the sweep line + let nextMySplitter = null; + if (nextSeg) { + const nextInter = nextSeg.getIntersection(segment); + if (nextInter !== null) { + if (!segment.isAnEndpoint(nextInter)) nextMySplitter = nextInter; + if (!nextSeg.isAnEndpoint(nextInter)) { + const newEventsFromSplit = this._splitSafely(nextSeg, nextInter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } } - } // For simplicity, even if we find more than one intersection we only - // spilt on the 'earliest' (sweep-line style) of the intersections. - // The other intersection will be handled in a future process(). - - - if (prevMySplitter !== null || nextMySplitter !== null) { - var mySplitter = null; - if (prevMySplitter === null) mySplitter = nextMySplitter;else if (nextMySplitter === null) mySplitter = prevMySplitter;else { - var cmpSplitters = SweepEvent.comparePoints(prevMySplitter, nextMySplitter); - mySplitter = cmpSplitters <= 0 ? prevMySplitter : nextMySplitter; - } // Rounding errors can cause changes in ordering, - // so remove afected segments and right sweep events before splitting - - this.queue.remove(segment.rightSE); - newEvents.push(segment.rightSE); - - var _newEventsFromSplit2 = segment.split(mySplitter); + } + } - for (var _i2 = 0, _iMax2 = _newEventsFromSplit2.length; _i2 < _iMax2; _i2++) { - newEvents.push(_newEventsFromSplit2[_i2]); - } + // For simplicity, even if we find more than one intersection we only + // spilt on the 'earliest' (sweep-line style) of the intersections. + // The other intersection will be handled in a future process(). + if (prevMySplitter !== null || nextMySplitter !== null) { + let mySplitter = null; + if (prevMySplitter === null) mySplitter = nextMySplitter;else if (nextMySplitter === null) mySplitter = prevMySplitter;else { + const cmpSplitters = SweepEvent.comparePoints(prevMySplitter, nextMySplitter); + mySplitter = cmpSplitters <= 0 ? prevMySplitter : nextMySplitter; } - if (newEvents.length > 0) { - // We found some intersections, so re-do the current event to - // make sure sweep line ordering is totally consistent for later - // use with the segment 'prev' pointers - this.tree.remove(segment); - newEvents.push(event); - } else { - // done with left event - this.segments.push(segment); - segment.prev = prevSeg; + // Rounding errors can cause changes in ordering, + // so remove afected segments and right sweep events before splitting + this.queue.remove(segment.rightSE); + newEvents.push(segment.rightSE); + const newEventsFromSplit = segment.split(mySplitter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } + } + if (newEvents.length > 0) { + // We found some intersections, so re-do the current event to + // make sure sweep line ordering is totally consistent for later + // use with the segment 'prev' pointers + this.tree.remove(segment); + newEvents.push(event); } else { - // event.isRight - // since we're about to be removed from the sweep line, check for - // intersections between our previous and next segments - if (prevSeg && nextSeg) { - var inter = prevSeg.getIntersection(nextSeg); - - if (inter !== null) { - if (!prevSeg.isAnEndpoint(inter)) { - var _newEventsFromSplit3 = this._splitSafely(prevSeg, inter); - - for (var _i3 = 0, _iMax3 = _newEventsFromSplit3.length; _i3 < _iMax3; _i3++) { - newEvents.push(_newEventsFromSplit3[_i3]); - } + // done with left event + this.segments.push(segment); + segment.prev = prevSeg; + } + } else { + // event.isRight + + // since we're about to be removed from the sweep line, check for + // intersections between our previous and next segments + if (prevSeg && nextSeg) { + const inter = prevSeg.getIntersection(nextSeg); + if (inter !== null) { + if (!prevSeg.isAnEndpoint(inter)) { + const newEventsFromSplit = this._splitSafely(prevSeg, inter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } - - if (!nextSeg.isAnEndpoint(inter)) { - var _newEventsFromSplit4 = this._splitSafely(nextSeg, inter); - - for (var _i4 = 0, _iMax4 = _newEventsFromSplit4.length; _i4 < _iMax4; _i4++) { - newEvents.push(_newEventsFromSplit4[_i4]); - } + } + if (!nextSeg.isAnEndpoint(inter)) { + const newEventsFromSplit = this._splitSafely(nextSeg, inter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } } } - - this.tree.remove(segment); } - - return newEvents; - } - /* Safely split a segment that is currently in the datastructures - * IE - a segment other than the one that is currently being processed. */ - - }, { - key: "_splitSafely", - value: function _splitSafely(seg, pt) { - // Rounding errors can cause changes in ordering, - // so remove afected segments and right sweep events before splitting - // removeNode() doesn't work, so have re-find the seg - // https://github.com/w8r/splay-tree/pull/5 - this.tree.remove(seg); - var rightSE = seg.rightSE; - this.queue.remove(rightSE); - var newEvents = seg.split(pt); - newEvents.push(rightSE); // splitting can trigger consumption - - if (seg.consumedBy === undefined) this.tree.insert(seg); - return newEvents; + this.tree.remove(segment); } - }]); - - return SweepLine; -}(); - -var POLYGON_CLIPPING_MAX_QUEUE_SIZE = typeof process !== 'undefined' && process.env.POLYGON_CLIPPING_MAX_QUEUE_SIZE || 1000000; -var POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS = typeof process !== 'undefined' && process.env.POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS || 1000000; -var Operation = /*#__PURE__*/function () { - function Operation() { - _classCallCheck(this, Operation); + return newEvents; } - _createClass(Operation, [{ - key: "run", - value: function run(type, geom, moreGeoms) { - operation.type = type; - rounder.reset(); - /* Convert inputs to MultiPoly objects */ - - var multipolys = [new MultiPolyIn(geom, true)]; - - for (var i = 0, iMax = moreGeoms.length; i < iMax; i++) { - multipolys.push(new MultiPolyIn(moreGeoms[i], false)); - } - - operation.numMultiPolys = multipolys.length; - /* BBox optimization for difference operation - * If the bbox of a multipolygon that's part of the clipping doesn't - * intersect the bbox of the subject at all, we can just drop that - * multiploygon. */ - - if (operation.type === 'difference') { - // in place removal - var subject = multipolys[0]; - var _i = 1; - - while (_i < multipolys.length) { - if (getBboxOverlap(multipolys[_i].bbox, subject.bbox) !== null) _i++;else multipolys.splice(_i, 1); - } - } - /* BBox optimization for intersection operation - * If we can find any pair of multipolygons whose bbox does not overlap, - * then the result will be empty. */ - - - if (operation.type === 'intersection') { - // TODO: this is O(n^2) in number of polygons. By sorting the bboxes, - // it could be optimized to O(n * ln(n)) - for (var _i2 = 0, _iMax = multipolys.length; _i2 < _iMax; _i2++) { - var mpA = multipolys[_i2]; + /* Safely split a segment that is currently in the datastructures + * IE - a segment other than the one that is currently being processed. */ + _splitSafely(seg, pt) { + // Rounding errors can cause changes in ordering, + // so remove afected segments and right sweep events before splitting + // removeNode() doesn't work, so have re-find the seg + // https://github.com/w8r/splay-tree/pull/5 + this.tree.remove(seg); + const rightSE = seg.rightSE; + this.queue.remove(rightSE); + const newEvents = seg.split(pt); + newEvents.push(rightSE); + // splitting can trigger consumption + if (seg.consumedBy === undefined) this.tree.add(seg); + return newEvents; + } +} - for (var j = _i2 + 1, jMax = multipolys.length; j < jMax; j++) { - if (getBboxOverlap(mpA.bbox, multipolys[j].bbox) === null) return []; - } - } +// Limits on iterative processes to prevent infinite loops - usually caused by floating-point math round-off errors. +const POLYGON_CLIPPING_MAX_QUEUE_SIZE = typeof process !== "undefined" && process.env.POLYGON_CLIPPING_MAX_QUEUE_SIZE || 1000000; +const POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS = typeof process !== "undefined" && process.env.POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS || 1000000; +class Operation { + run(type, geom, moreGeoms) { + operation.type = type; + rounder.reset(); + + /* Convert inputs to MultiPoly objects */ + const multipolys = [new MultiPolyIn(geom, true)]; + for (let i = 0, iMax = moreGeoms.length; i < iMax; i++) { + multipolys.push(new MultiPolyIn(moreGeoms[i], false)); + } + operation.numMultiPolys = multipolys.length; + + /* BBox optimization for difference operation + * If the bbox of a multipolygon that's part of the clipping doesn't + * intersect the bbox of the subject at all, we can just drop that + * multiploygon. */ + if (operation.type === "difference") { + // in place removal + const subject = multipolys[0]; + let i = 1; + while (i < multipolys.length) { + if (getBboxOverlap(multipolys[i].bbox, subject.bbox) !== null) i++;else multipolys.splice(i, 1); } - /* Put segment endpoints in a priority queue */ - - - var queue = new SplayTree__default['default'](SweepEvent.compare); - - for (var _i3 = 0, _iMax2 = multipolys.length; _i3 < _iMax2; _i3++) { - var sweepEvents = multipolys[_i3].getSweepEvents(); - - for (var _j = 0, _jMax = sweepEvents.length; _j < _jMax; _j++) { - queue.insert(sweepEvents[_j]); + } - if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) { - // prevents an infinite loop, an otherwise common manifestation of bugs - throw new Error('Infinite loop when putting segment endpoints in a priority queue ' + '(queue size too big). Please file a bug report.'); - } + /* BBox optimization for intersection operation + * If we can find any pair of multipolygons whose bbox does not overlap, + * then the result will be empty. */ + if (operation.type === "intersection") { + // TODO: this is O(n^2) in number of polygons. By sorting the bboxes, + // it could be optimized to O(n * ln(n)) + for (let i = 0, iMax = multipolys.length; i < iMax; i++) { + const mpA = multipolys[i]; + for (let j = i + 1, jMax = multipolys.length; j < jMax; j++) { + if (getBboxOverlap(mpA.bbox, multipolys[j].bbox) === null) return []; } } - /* Pass the sweep line over those endpoints */ - - - var sweepLine = new SweepLine(queue); - var prevQueueSize = queue.size; - var node = queue.pop(); - - while (node) { - var evt = node.key; - - if (queue.size === prevQueueSize) { - // prevents an infinite loop, an otherwise common manifestation of bugs - var seg = evt.segment; - throw new Error("Unable to pop() ".concat(evt.isLeft ? 'left' : 'right', " SweepEvent ") + "[".concat(evt.point.x, ", ").concat(evt.point.y, "] from segment #").concat(seg.id, " ") + "[".concat(seg.leftSE.point.x, ", ").concat(seg.leftSE.point.y, "] -> ") + "[".concat(seg.rightSE.point.x, ", ").concat(seg.rightSE.point.y, "] from queue. ") + 'Please file a bug report.'); - } + } + /* Put segment endpoints in a priority queue */ + const queue = new SplayTree__default["default"](SweepEvent.compare); + for (let i = 0, iMax = multipolys.length; i < iMax; i++) { + const sweepEvents = multipolys[i].getSweepEvents(); + for (let j = 0, jMax = sweepEvents.length; j < jMax; j++) { + queue.insert(sweepEvents[j]); if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) { // prevents an infinite loop, an otherwise common manifestation of bugs - throw new Error('Infinite loop when passing sweep line over endpoints ' + '(queue size too big). Please file a bug report.'); - } - - if (sweepLine.segments.length > POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS) { - // prevents an infinite loop, an otherwise common manifestation of bugs - throw new Error('Infinite loop when passing sweep line over endpoints ' + '(too many sweep line segments). Please file a bug report.'); + throw new Error("Infinite loop when putting segment endpoints in a priority queue " + "(queue size too big)."); } + } + } - var newEvents = sweepLine.process(evt); - - for (var _i4 = 0, _iMax3 = newEvents.length; _i4 < _iMax3; _i4++) { - var _evt = newEvents[_i4]; - if (_evt.consumedBy === undefined) queue.insert(_evt); - } - - prevQueueSize = queue.size; - node = queue.pop(); - } // free some memory we don't need anymore - - - rounder.reset(); - /* Collect and compile segments we're keeping into a multipolygon */ - - var ringsOut = RingOut.factory(sweepLine.segments); - var result = new MultiPolyOut(ringsOut); - return result.getGeom(); + /* Pass the sweep line over those endpoints */ + const sweepLine = new SweepLine(queue); + let prevQueueSize = queue.size; + let node = queue.pop(); + while (node) { + const evt = node.key; + if (queue.size === prevQueueSize) { + // prevents an infinite loop, an otherwise common manifestation of bugs + const seg = evt.segment; + throw new Error(`Unable to pop() ${evt.isLeft ? "left" : "right"} SweepEvent ` + `[${evt.point.x}, ${evt.point.y}] from segment #${seg.id} ` + `[${seg.leftSE.point.x}, ${seg.leftSE.point.y}] -> ` + `[${seg.rightSE.point.x}, ${seg.rightSE.point.y}] from queue.`); + } + if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) { + // prevents an infinite loop, an otherwise common manifestation of bugs + throw new Error("Infinite loop when passing sweep line over endpoints " + "(queue size too big)."); + } + if (sweepLine.segments.length > POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS) { + // prevents an infinite loop, an otherwise common manifestation of bugs + throw new Error("Infinite loop when passing sweep line over endpoints " + "(too many sweep line segments)."); + } + const newEvents = sweepLine.process(evt); + for (let i = 0, iMax = newEvents.length; i < iMax; i++) { + const evt = newEvents[i]; + if (evt.consumedBy === undefined) queue.insert(evt); + } + prevQueueSize = queue.size; + node = queue.pop(); } - }]); - return Operation; -}(); // singleton available by import + // free some memory we don't need anymore + rounder.reset(); -var operation = new Operation(); + /* Collect and compile segments we're keeping into a multipolygon */ + const ringsOut = RingOut.factory(sweepLine.segments); + const result = new MultiPolyOut(ringsOut); + return result.getGeom(); + } +} + +// singleton available by import +const operation = new Operation(); -var union = function union(geom) { +const union = function (geom) { for (var _len = arguments.length, moreGeoms = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { moreGeoms[_key - 1] = arguments[_key]; } - - return operation.run('union', geom, moreGeoms); + return operation.run("union", geom, moreGeoms); }; - -var intersection$1 = function intersection(geom) { +const intersection = function (geom) { for (var _len2 = arguments.length, moreGeoms = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { moreGeoms[_key2 - 1] = arguments[_key2]; } - - return operation.run('intersection', geom, moreGeoms); + return operation.run("intersection", geom, moreGeoms); }; - -var xor = function xor(geom) { +const xor = function (geom) { for (var _len3 = arguments.length, moreGeoms = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { moreGeoms[_key3 - 1] = arguments[_key3]; } - - return operation.run('xor', geom, moreGeoms); + return operation.run("xor", geom, moreGeoms); }; - -var difference = function difference(subjectGeom) { +const difference = function (subjectGeom) { for (var _len4 = arguments.length, clippingGeoms = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) { clippingGeoms[_key4 - 1] = arguments[_key4]; } - - return operation.run('difference', subjectGeom, clippingGeoms); + return operation.run("difference", subjectGeom, clippingGeoms); }; - var index = { union: union, - intersection: intersection$1, + intersection: intersection, xor: xor, difference: difference }; diff --git a/dist/polygon-clipping.d.ts b/dist/polygon-clipping.d.ts index 19fc088..47c3974 100644 --- a/dist/polygon-clipping.d.ts +++ b/dist/polygon-clipping.d.ts @@ -1,11 +1,14 @@ -declare module 'polygon-clipping' { - export type Pair = [number, number]; - export type Ring = Pair[]; - export type Polygon = Ring[]; - export type MultiPolygon = Polygon[]; - type Geom = Polygon | MultiPolygon; - export function intersection(geom: Geom, ...geoms: Geom[]): MultiPolygon; - export function xor(geom: Geom, ...geoms: Geom[]): MultiPolygon; - export function union(geom: Geom, ...geoms: Geom[]): MultiPolygon; - export function difference(subjectGeom: Geom, ...clipGeoms: Geom[]): MultiPolygon; +declare module "polygon-clipping" { + export type Pair = [number, number] + export type Ring = Pair[] + export type Polygon = Ring[] + export type MultiPolygon = Polygon[] + type Geom = Polygon | MultiPolygon + export function intersection(geom: Geom, ...geoms: Geom[]): MultiPolygon + export function xor(geom: Geom, ...geoms: Geom[]): MultiPolygon + export function union(geom: Geom, ...geoms: Geom[]): MultiPolygon + export function difference( + subjectGeom: Geom, + ...clipGeoms: Geom[] + ): MultiPolygon } diff --git a/dist/polygon-clipping.esm.js b/dist/polygon-clipping.esm.js index af05b2d..c824ce6 100644 --- a/dist/polygon-clipping.esm.js +++ b/dist/polygon-clipping.esm.js @@ -1,26 +1,5 @@ import SplayTree from 'splaytree'; - -function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } -} - -function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } -} - -function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; -} +import { orient2d } from 'robust-predicates'; /** * A bounding box has the format: @@ -28,23 +7,27 @@ function _createClass(Constructor, protoProps, staticProps) { * { ll: { x: xmin, y: ymin }, ur: { x: xmax, y: ymax } } * */ -var isInBbox = function isInBbox(bbox, point) { + +const isInBbox = (bbox, point) => { return bbox.ll.x <= point.x && point.x <= bbox.ur.x && bbox.ll.y <= point.y && point.y <= bbox.ur.y; }; + /* Returns either null, or a bbox (aka an ordered pair of points) * If there is only one point of overlap, a bbox with identical points * will be returned */ - -var getBboxOverlap = function getBboxOverlap(b1, b2) { +const getBboxOverlap = (b1, b2) => { // check if the bboxes overlap at all - if (b2.ur.x < b1.ll.x || b1.ur.x < b2.ll.x || b2.ur.y < b1.ll.y || b1.ur.y < b2.ll.y) return null; // find the middle two X values + if (b2.ur.x < b1.ll.x || b1.ur.x < b2.ll.x || b2.ur.y < b1.ll.y || b1.ur.y < b2.ll.y) return null; - var lowerX = b1.ll.x < b2.ll.x ? b2.ll.x : b1.ll.x; - var upperX = b1.ur.x < b2.ur.x ? b1.ur.x : b2.ur.x; // find the middle two Y values + // find the middle two X values + const lowerX = b1.ll.x < b2.ll.x ? b2.ll.x : b1.ll.x; + const upperX = b1.ur.x < b2.ur.x ? b1.ur.x : b2.ur.x; - var lowerY = b1.ll.y < b2.ll.y ? b2.ll.y : b1.ll.y; - var upperY = b1.ur.y < b2.ur.y ? b1.ur.y : b2.ur.y; // put those middle values together to get the overlap + // find the middle two Y values + const lowerY = b1.ll.y < b2.ll.y ? b2.ll.y : b1.ll.y; + const upperY = b1.ur.y < b2.ur.y ? b1.ur.y : b2.ur.y; + // put those middle values together to get the overlap return { ll: { x: lowerX, @@ -62,28 +45,29 @@ var getBboxOverlap = function getBboxOverlap(b1, b2) { * * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON */ -var epsilon = Number.EPSILON; // IE Polyfill +let epsilon = Number.EPSILON; + +// IE Polyfill if (epsilon === undefined) epsilon = Math.pow(2, -52); -var EPSILON_SQ = epsilon * epsilon; -/* FLP comparator */ +const EPSILON_SQ = epsilon * epsilon; -var cmp = function cmp(a, b) { +/* FLP comparator */ +const cmp = (a, b) => { // check if they're both 0 if (-epsilon < a && a < epsilon) { if (-epsilon < b && b < epsilon) { return 0; } - } // check if they're flp equal - - - var ab = a - b; + } + // check if they're flp equal + const ab = a - b; if (ab * ab < EPSILON_SQ * a * b) { return 0; - } // normal comparison - + } + // normal comparison return a < b ? -1 : 1; }; @@ -100,467 +84,414 @@ var cmp = function cmp(a, b) { * stored in any data structures in the rest of this algorithm. */ -var PtRounder = /*#__PURE__*/function () { - function PtRounder() { - _classCallCheck(this, PtRounder); - +class PtRounder { + constructor() { this.reset(); } - - _createClass(PtRounder, [{ - key: "reset", - value: function reset() { - this.xRounder = new CoordRounder(); - this.yRounder = new CoordRounder(); - } - }, { - key: "round", - value: function round(x, y) { - return { - x: this.xRounder.round(x), - y: this.yRounder.round(y) - }; - } - }]); - - return PtRounder; -}(); - -var CoordRounder = /*#__PURE__*/function () { - function CoordRounder() { - _classCallCheck(this, CoordRounder); - - this.tree = new SplayTree(); // preseed with 0 so we don't end up with values < Number.EPSILON - + reset() { + this.xRounder = new CoordRounder(); + this.yRounder = new CoordRounder(); + } + round(x, y) { + return { + x: this.xRounder.round(x), + y: this.yRounder.round(y) + }; + } +} +class CoordRounder { + constructor() { + this.tree = new SplayTree(); + // preseed with 0 so we don't end up with values < Number.EPSILON this.round(0); - } // Note: this can rounds input values backwards or forwards. + } + + // Note: this can rounds input values backwards or forwards. // You might ask, why not restrict this to just rounding // forwards? Wouldn't that allow left endpoints to always // remain left endpoints during splitting (never change to // right). No - it wouldn't, because we snap intersections // to endpoints (to establish independence from the segment // angle for t-intersections). - - - _createClass(CoordRounder, [{ - key: "round", - value: function round(coord) { - var node = this.tree.add(coord); - var prevNode = this.tree.prev(node); - - if (prevNode !== null && cmp(node.key, prevNode.key) === 0) { - this.tree.remove(coord); - return prevNode.key; - } - - var nextNode = this.tree.next(node); - - if (nextNode !== null && cmp(node.key, nextNode.key) === 0) { - this.tree.remove(coord); - return nextNode.key; - } - - return coord; + round(coord) { + const node = this.tree.add(coord); + const prevNode = this.tree.prev(node); + if (prevNode !== null && cmp(node.key, prevNode.key) === 0) { + this.tree.remove(coord); + return prevNode.key; } - }]); - - return CoordRounder; -}(); // singleton available by import - + const nextNode = this.tree.next(node); + if (nextNode !== null && cmp(node.key, nextNode.key) === 0) { + this.tree.remove(coord); + return nextNode.key; + } + return coord; + } +} -var rounder = new PtRounder(); +// singleton available by import +const rounder = new PtRounder(); /* Cross Product of two vectors with first point at origin */ +const crossProduct = (a, b) => a.x * b.y - a.y * b.x; -var crossProduct = function crossProduct(a, b) { - return a.x * b.y - a.y * b.x; -}; /* Dot Product of two vectors with first point at origin */ +const dotProduct = (a, b) => a.x * b.x + a.y * b.y; -var dotProduct = function dotProduct(a, b) { - return a.x * b.x + a.y * b.y; -}; /* Comparator for two vectors with same starting point */ - -var compareVectorAngles = function compareVectorAngles(basePt, endPt1, endPt2) { - var v1 = { - x: endPt1.x - basePt.x, - y: endPt1.y - basePt.y - }; - var v2 = { - x: endPt2.x - basePt.x, - y: endPt2.y - basePt.y - }; - var kross = crossProduct(v1, v2); - return cmp(kross, 0); +const compareVectorAngles = (basePt, endPt1, endPt2) => { + const res = orient2d(basePt.x, basePt.y, endPt1.x, endPt1.y, endPt2.x, endPt2.y); + if (res > 0) return -1; + if (res < 0) return 1; + return 0; }; -var length = function length(v) { - return Math.sqrt(dotProduct(v, v)); -}; -/* Get the sine of the angle from pShared -> pAngle to pShaed -> pBase */ +const length = v => Math.sqrt(dotProduct(v, v)); -var sineOfAngle = function sineOfAngle(pShared, pBase, pAngle) { - var vBase = { +/* Get the sine of the angle from pShared -> pAngle to pShaed -> pBase */ +const sineOfAngle = (pShared, pBase, pAngle) => { + const vBase = { x: pBase.x - pShared.x, y: pBase.y - pShared.y }; - var vAngle = { + const vAngle = { x: pAngle.x - pShared.x, y: pAngle.y - pShared.y }; return crossProduct(vAngle, vBase) / length(vAngle) / length(vBase); }; -/* Get the cosine of the angle from pShared -> pAngle to pShaed -> pBase */ -var cosineOfAngle = function cosineOfAngle(pShared, pBase, pAngle) { - var vBase = { +/* Get the cosine of the angle from pShared -> pAngle to pShaed -> pBase */ +const cosineOfAngle = (pShared, pBase, pAngle) => { + const vBase = { x: pBase.x - pShared.x, y: pBase.y - pShared.y }; - var vAngle = { + const vAngle = { x: pAngle.x - pShared.x, y: pAngle.y - pShared.y }; return dotProduct(vAngle, vBase) / length(vAngle) / length(vBase); }; + /* Get the x coordinate where the given line (defined by a point and vector) * crosses the horizontal line with the given y coordiante. * In the case of parrallel lines (including overlapping ones) returns null. */ - -var horizontalIntersection = function horizontalIntersection(pt, v, y) { +const horizontalIntersection = (pt, v, y) => { if (v.y === 0) return null; return { x: pt.x + v.x / v.y * (y - pt.y), y: y }; }; + /* Get the y coordinate where the given line (defined by a point and vector) * crosses the vertical line with the given x coordiante. * In the case of parrallel lines (including overlapping ones) returns null. */ - -var verticalIntersection = function verticalIntersection(pt, v, x) { +const verticalIntersection = (pt, v, x) => { if (v.x === 0) return null; return { x: x, y: pt.y + v.y / v.x * (x - pt.x) }; }; + /* Get the intersection of two lines, each defined by a base point and a vector. * In the case of parrallel lines (including overlapping ones) returns null. */ - -var intersection = function intersection(pt1, v1, pt2, v2) { +const intersection$1 = (pt1, v1, pt2, v2) => { // take some shortcuts for vertical and horizontal lines // this also ensures we don't calculate an intersection and then discover // it's actually outside the bounding box of the line if (v1.x === 0) return verticalIntersection(pt2, v2, pt1.x); if (v2.x === 0) return verticalIntersection(pt1, v1, pt2.x); if (v1.y === 0) return horizontalIntersection(pt2, v2, pt1.y); - if (v2.y === 0) return horizontalIntersection(pt1, v1, pt2.y); // General case for non-overlapping segments. + if (v2.y === 0) return horizontalIntersection(pt1, v1, pt2.y); + + // General case for non-overlapping segments. // This algorithm is based on Schneider and Eberly. // http://www.cimec.org.ar/~ncalvo/Schneider_Eberly.pdf - pg 244 - var kross = crossProduct(v1, v2); + const kross = crossProduct(v1, v2); if (kross == 0) return null; - var ve = { + const ve = { x: pt2.x - pt1.x, y: pt2.y - pt1.y }; - var d1 = crossProduct(ve, v1) / kross; - var d2 = crossProduct(ve, v2) / kross; // take the average of the two calculations to minimize rounding error - - var x1 = pt1.x + d2 * v1.x, - x2 = pt2.x + d1 * v2.x; - var y1 = pt1.y + d2 * v1.y, - y2 = pt2.y + d1 * v2.y; - var x = (x1 + x2) / 2; - var y = (y1 + y2) / 2; + const d1 = crossProduct(ve, v1) / kross; + const d2 = crossProduct(ve, v2) / kross; + + // take the average of the two calculations to minimize rounding error + const x1 = pt1.x + d2 * v1.x, + x2 = pt2.x + d1 * v2.x; + const y1 = pt1.y + d2 * v1.y, + y2 = pt2.y + d1 * v2.y; + const x = (x1 + x2) / 2; + const y = (y1 + y2) / 2; return { x: x, y: y }; }; -var SweepEvent = /*#__PURE__*/function () { - _createClass(SweepEvent, null, [{ - key: "compare", - // for ordering sweep events in the sweep event queue - value: function compare(a, b) { - // favor event with a point that the sweep line hits first - var ptCmp = SweepEvent.comparePoints(a.point, b.point); - if (ptCmp !== 0) return ptCmp; // the points are the same, so link them if needed - - if (a.point !== b.point) a.link(b); // favor right events over left - - if (a.isLeft !== b.isLeft) return a.isLeft ? 1 : -1; // we have two matching left or right endpoints - // ordering of this case is the same as for their segments - - return Segment.compare(a.segment, b.segment); - } // for ordering points in sweep line order - - }, { - key: "comparePoints", - value: function comparePoints(aPt, bPt) { - if (aPt.x < bPt.x) return -1; - if (aPt.x > bPt.x) return 1; - if (aPt.y < bPt.y) return -1; - if (aPt.y > bPt.y) return 1; - return 0; - } // Warning: 'point' input will be modified and re-used (for performance) +class SweepEvent { + // for ordering sweep events in the sweep event queue + static compare(a, b) { + // favor event with a point that the sweep line hits first + const ptCmp = SweepEvent.comparePoints(a.point, b.point); + if (ptCmp !== 0) return ptCmp; - }]); + // the points are the same, so link them if needed + if (a.point !== b.point) a.link(b); - function SweepEvent(point, isLeft) { - _classCallCheck(this, SweepEvent); + // favor right events over left + if (a.isLeft !== b.isLeft) return a.isLeft ? 1 : -1; - if (point.events === undefined) point.events = [this];else point.events.push(this); - this.point = point; - this.isLeft = isLeft; // this.segment, this.otherSE set by factory + // we have two matching left or right endpoints + // ordering of this case is the same as for their segments + return Segment.compare(a.segment, b.segment); } - _createClass(SweepEvent, [{ - key: "link", - value: function link(other) { - if (other.point === this.point) { - throw new Error('Tried to link already linked events'); - } + // for ordering points in sweep line order + static comparePoints(aPt, bPt) { + if (aPt.x < bPt.x) return -1; + if (aPt.x > bPt.x) return 1; + if (aPt.y < bPt.y) return -1; + if (aPt.y > bPt.y) return 1; + return 0; + } - var otherEvents = other.point.events; + // Warning: 'point' input will be modified and re-used (for performance) + constructor(point, isLeft) { + if (point.events === undefined) point.events = [this];else point.events.push(this); + this.point = point; + this.isLeft = isLeft; + // this.segment, this.otherSE set by factory + } + link(other) { + if (other.point === this.point) { + throw new Error("Tried to link already linked events"); + } + const otherEvents = other.point.events; + for (let i = 0, iMax = otherEvents.length; i < iMax; i++) { + const evt = otherEvents[i]; + this.point.events.push(evt); + evt.point = this.point; + } + this.checkForConsuming(); + } - for (var i = 0, iMax = otherEvents.length; i < iMax; i++) { - var evt = otherEvents[i]; - this.point.events.push(evt); - evt.point = this.point; + /* Do a pass over our linked events and check to see if any pair + * of segments match, and should be consumed. */ + checkForConsuming() { + // FIXME: The loops in this method run O(n^2) => no good. + // Maintain little ordered sweep event trees? + // Can we maintaining an ordering that avoids the need + // for the re-sorting with getLeftmostComparator in geom-out? + + // Compare each pair of events to see if other events also match + const numEvents = this.point.events.length; + for (let i = 0; i < numEvents; i++) { + const evt1 = this.point.events[i]; + if (evt1.segment.consumedBy !== undefined) continue; + for (let j = i + 1; j < numEvents; j++) { + const evt2 = this.point.events[j]; + if (evt2.consumedBy !== undefined) continue; + if (evt1.otherSE.point.events !== evt2.otherSE.point.events) continue; + evt1.segment.consume(evt2.segment); } - - this.checkForConsuming(); } - /* Do a pass over our linked events and check to see if any pair - * of segments match, and should be consumed. */ - - }, { - key: "checkForConsuming", - value: function checkForConsuming() { - // FIXME: The loops in this method run O(n^2) => no good. - // Maintain little ordered sweep event trees? - // Can we maintaining an ordering that avoids the need - // for the re-sorting with getLeftmostComparator in geom-out? - // Compare each pair of events to see if other events also match - var numEvents = this.point.events.length; - - for (var i = 0; i < numEvents; i++) { - var evt1 = this.point.events[i]; - if (evt1.segment.consumedBy !== undefined) continue; - - for (var j = i + 1; j < numEvents; j++) { - var evt2 = this.point.events[j]; - if (evt2.consumedBy !== undefined) continue; - if (evt1.otherSE.point.events !== evt2.otherSE.point.events) continue; - evt1.segment.consume(evt2.segment); - } + } + getAvailableLinkedEvents() { + // point.events is always of length 2 or greater + const events = []; + for (let i = 0, iMax = this.point.events.length; i < iMax; i++) { + const evt = this.point.events[i]; + if (evt !== this && !evt.segment.ringOut && evt.segment.isInResult()) { + events.push(evt); } } - }, { - key: "getAvailableLinkedEvents", - value: function getAvailableLinkedEvents() { - // point.events is always of length 2 or greater - var events = []; - - for (var i = 0, iMax = this.point.events.length; i < iMax; i++) { - var evt = this.point.events[i]; + return events; + } - if (evt !== this && !evt.segment.ringOut && evt.segment.isInResult()) { - events.push(evt); - } + /** + * Returns a comparator function for sorting linked events that will + * favor the event that will give us the smallest left-side angle. + * All ring construction starts as low as possible heading to the right, + * so by always turning left as sharp as possible we'll get polygons + * without uncessary loops & holes. + * + * The comparator function has a compute cache such that it avoids + * re-computing already-computed values. + */ + getLeftmostComparator(baseEvent) { + const cache = new Map(); + const fillCache = linkedEvent => { + const nextEvent = linkedEvent.otherSE; + cache.set(linkedEvent, { + sine: sineOfAngle(this.point, baseEvent.point, nextEvent.point), + cosine: cosineOfAngle(this.point, baseEvent.point, nextEvent.point) + }); + }; + return (a, b) => { + if (!cache.has(a)) fillCache(a); + if (!cache.has(b)) fillCache(b); + const { + sine: asine, + cosine: acosine + } = cache.get(a); + const { + sine: bsine, + cosine: bcosine + } = cache.get(b); + + // both on or above x-axis + if (asine >= 0 && bsine >= 0) { + if (acosine < bcosine) return 1; + if (acosine > bcosine) return -1; + return 0; } - return events; - } - /** - * Returns a comparator function for sorting linked events that will - * favor the event that will give us the smallest left-side angle. - * All ring construction starts as low as possible heading to the right, - * so by always turning left as sharp as possible we'll get polygons - * without uncessary loops & holes. - * - * The comparator function has a compute cache such that it avoids - * re-computing already-computed values. - */ - - }, { - key: "getLeftmostComparator", - value: function getLeftmostComparator(baseEvent) { - var _this = this; - - var cache = new Map(); - - var fillCache = function fillCache(linkedEvent) { - var nextEvent = linkedEvent.otherSE; - cache.set(linkedEvent, { - sine: sineOfAngle(_this.point, baseEvent.point, nextEvent.point), - cosine: cosineOfAngle(_this.point, baseEvent.point, nextEvent.point) - }); - }; - - return function (a, b) { - if (!cache.has(a)) fillCache(a); - if (!cache.has(b)) fillCache(b); - - var _cache$get = cache.get(a), - asine = _cache$get.sine, - acosine = _cache$get.cosine; - - var _cache$get2 = cache.get(b), - bsine = _cache$get2.sine, - bcosine = _cache$get2.cosine; // both on or above x-axis - - - if (asine >= 0 && bsine >= 0) { - if (acosine < bcosine) return 1; - if (acosine > bcosine) return -1; - return 0; - } // both below x-axis - - - if (asine < 0 && bsine < 0) { - if (acosine < bcosine) return -1; - if (acosine > bcosine) return 1; - return 0; - } // one above x-axis, one below - - - if (bsine < asine) return -1; - if (bsine > asine) return 1; + // both below x-axis + if (asine < 0 && bsine < 0) { + if (acosine < bcosine) return -1; + if (acosine > bcosine) return 1; return 0; - }; - } - }]); + } - return SweepEvent; -}(); + // one above x-axis, one below + if (bsine < asine) return -1; + if (bsine > asine) return 1; + return 0; + }; + } +} +// Give segments unique ID's to get consistent sorting of // segments and sweep events when all else is identical +let segmentId = 0; +class Segment { + /* This compare() function is for ordering segments in the sweep + * line tree, and does so according to the following criteria: + * + * Consider the vertical line that lies an infinestimal step to the + * right of the right-more of the two left endpoints of the input + * segments. Imagine slowly moving a point up from negative infinity + * in the increasing y direction. Which of the two segments will that + * point intersect first? That segment comes 'before' the other one. + * + * If neither segment would be intersected by such a line, (if one + * or more of the segments are vertical) then the line to be considered + * is directly on the right-more of the two left inputs. + */ + static compare(a, b) { + const alx = a.leftSE.point.x; + const blx = b.leftSE.point.x; + const arx = a.rightSE.point.x; + const brx = b.rightSE.point.x; + + // check if they're even in the same vertical plane + if (brx < alx) return 1; + if (arx < blx) return -1; + const aly = a.leftSE.point.y; + const bly = b.leftSE.point.y; + const ary = a.rightSE.point.y; + const bry = b.rightSE.point.y; + + // is left endpoint of segment B the right-more? + if (alx < blx) { + // are the two segments in the same horizontal plane? + if (bly < aly && bly < ary) return 1; + if (bly > aly && bly > ary) return -1; + + // is the B left endpoint colinear to segment A? + const aCmpBLeft = a.comparePoint(b.leftSE.point); + if (aCmpBLeft < 0) return 1; + if (aCmpBLeft > 0) return -1; + + // is the A right endpoint colinear to segment B ? + const bCmpARight = b.comparePoint(a.rightSE.point); + if (bCmpARight !== 0) return bCmpARight; + + // colinear segments, consider the one with left-more + // left endpoint to be first (arbitrary?) + return -1; + } -var segmentId = 0; - -var Segment = /*#__PURE__*/function () { - _createClass(Segment, null, [{ - key: "compare", - - /* This compare() function is for ordering segments in the sweep - * line tree, and does so according to the following criteria: - * - * Consider the vertical line that lies an infinestimal step to the - * right of the right-more of the two left endpoints of the input - * segments. Imagine slowly moving a point up from negative infinity - * in the increasing y direction. Which of the two segments will that - * point intersect first? That segment comes 'before' the other one. - * - * If neither segment would be intersected by such a line, (if one - * or more of the segments are vertical) then the line to be considered - * is directly on the right-more of the two left inputs. - */ - value: function compare(a, b) { - var alx = a.leftSE.point.x; - var blx = b.leftSE.point.x; - var arx = a.rightSE.point.x; - var brx = b.rightSE.point.x; // check if they're even in the same vertical plane - - if (brx < alx) return 1; - if (arx < blx) return -1; - var aly = a.leftSE.point.y; - var bly = b.leftSE.point.y; - var ary = a.rightSE.point.y; - var bry = b.rightSE.point.y; // is left endpoint of segment B the right-more? - - if (alx < blx) { - // are the two segments in the same horizontal plane? - if (bly < aly && bly < ary) return 1; - if (bly > aly && bly > ary) return -1; // is the B left endpoint colinear to segment A? - - var aCmpBLeft = a.comparePoint(b.leftSE.point); - if (aCmpBLeft < 0) return 1; - if (aCmpBLeft > 0) return -1; // is the A right endpoint colinear to segment B ? - - var bCmpARight = b.comparePoint(a.rightSE.point); - if (bCmpARight !== 0) return bCmpARight; // colinear segments, consider the one with left-more - // left endpoint to be first (arbitrary?) - - return -1; - } // is left endpoint of segment A the right-more? - - - if (alx > blx) { - if (aly < bly && aly < bry) return -1; - if (aly > bly && aly > bry) return 1; // is the A left endpoint colinear to segment B? - - var bCmpALeft = b.comparePoint(a.leftSE.point); - if (bCmpALeft !== 0) return bCmpALeft; // is the B right endpoint colinear to segment A? - - var aCmpBRight = a.comparePoint(b.rightSE.point); - if (aCmpBRight < 0) return 1; - if (aCmpBRight > 0) return -1; // colinear segments, consider the one with left-more - // left endpoint to be first (arbitrary?) - - return 1; - } // if we get here, the two left endpoints are in the same - // vertical plane, ie alx === blx - // consider the lower left-endpoint to come first - - - if (aly < bly) return -1; - if (aly > bly) return 1; // left endpoints are identical - // check for colinearity by using the left-more right endpoint - // is the A right endpoint more left-more? - - if (arx < brx) { - var _bCmpARight = b.comparePoint(a.rightSE.point); - - if (_bCmpARight !== 0) return _bCmpARight; - } // is the B right endpoint more left-more? - - - if (arx > brx) { - var _aCmpBRight = a.comparePoint(b.rightSE.point); - - if (_aCmpBRight < 0) return 1; - if (_aCmpBRight > 0) return -1; - } + // is left endpoint of segment A the right-more? + if (alx > blx) { + if (aly < bly && aly < bry) return -1; + if (aly > bly && aly > bry) return 1; + + // is the A left endpoint colinear to segment B? + const bCmpALeft = b.comparePoint(a.leftSE.point); + if (bCmpALeft !== 0) return bCmpALeft; - if (arx !== brx) { - // are these two [almost] vertical segments with opposite orientation? - // if so, the one with the lower right endpoint comes first - var ay = ary - aly; - var ax = arx - alx; - var by = bry - bly; - var bx = brx - blx; - if (ay > ax && by < bx) return 1; - if (ay < ax && by > bx) return -1; - } // we have colinear segments with matching orientation - // consider the one with more left-more right endpoint to be first + // is the B right endpoint colinear to segment A? + const aCmpBRight = a.comparePoint(b.rightSE.point); + if (aCmpBRight < 0) return 1; + if (aCmpBRight > 0) return -1; + // colinear segments, consider the one with left-more + // left endpoint to be first (arbitrary?) + return 1; + } - if (arx > brx) return 1; - if (arx < brx) return -1; // if we get here, two two right endpoints are in the same - // vertical plane, ie arx === brx - // consider the lower right-endpoint to come first + // if we get here, the two left endpoints are in the same + // vertical plane, ie alx === blx - if (ary < bry) return -1; - if (ary > bry) return 1; // right endpoints identical as well, so the segments are idential - // fall back on creation order as consistent tie-breaker + // consider the lower left-endpoint to come first + if (aly < bly) return -1; + if (aly > bly) return 1; - if (a.id < b.id) return -1; - if (a.id > b.id) return 1; // identical segment, ie a === b + // left endpoints are identical + // check for colinearity by using the left-more right endpoint - return 0; + // is the A right endpoint more left-more? + if (arx < brx) { + const bCmpARight = b.comparePoint(a.rightSE.point); + if (bCmpARight !== 0) return bCmpARight; + } + + // is the B right endpoint more left-more? + if (arx > brx) { + const aCmpBRight = a.comparePoint(b.rightSE.point); + if (aCmpBRight < 0) return 1; + if (aCmpBRight > 0) return -1; + } + if (arx !== brx) { + // are these two [almost] vertical segments with opposite orientation? + // if so, the one with the lower right endpoint comes first + const ay = ary - aly; + const ax = arx - alx; + const by = bry - bly; + const bx = brx - blx; + if (ay > ax && by < bx) return 1; + if (ay < ax && by > bx) return -1; } - /* Warning: a reference to ringWindings input will be stored, - * and possibly will be later modified */ - }]); + // we have colinear segments with matching orientation + // consider the one with more left-more right endpoint to be first + if (arx > brx) return 1; + if (arx < brx) return -1; + + // if we get here, two two right endpoints are in the same + // vertical plane, ie arx === brx - function Segment(leftSE, rightSE, rings, windings) { - _classCallCheck(this, Segment); + // consider the lower right-endpoint to come first + if (ary < bry) return -1; + if (ary > bry) return 1; + + // right endpoints identical as well, so the segments are idential + // fall back on creation order as consistent tie-breaker + if (a.id < b.id) return -1; + if (a.id > b.id) return 1; + + // identical segment, ie a === b + return 0; + } + /* Warning: a reference to ringWindings input will be stored, + * and possibly will be later modified */ + constructor(leftSE, rightSE, rings, windings) { this.id = ++segmentId; this.leftSE = leftSE; leftSE.segment = this; @@ -569,481 +500,425 @@ var Segment = /*#__PURE__*/function () { rightSE.segment = this; rightSE.otherSE = leftSE; this.rings = rings; - this.windings = windings; // left unset for performance, set later in algorithm + this.windings = windings; + // left unset for performance, set later in algorithm // this.ringOut, this.consumedBy, this.prev } + static fromRing(pt1, pt2, ring) { + let leftPt, rightPt, winding; + + // ordering the two points according to sweep line ordering + const cmpPts = SweepEvent.comparePoints(pt1, pt2); + if (cmpPts < 0) { + leftPt = pt1; + rightPt = pt2; + winding = 1; + } else if (cmpPts > 0) { + leftPt = pt2; + rightPt = pt1; + winding = -1; + } else throw new Error(`Tried to create degenerate segment at [${pt1.x}, ${pt1.y}]`); + const leftSE = new SweepEvent(leftPt, true); + const rightSE = new SweepEvent(rightPt, false); + return new Segment(leftSE, rightSE, [ring], [winding]); + } - _createClass(Segment, [{ - key: "replaceRightSE", - - /* When a segment is split, the rightSE is replaced with a new sweep event */ - value: function replaceRightSE(newRightSE) { - this.rightSE = newRightSE; - this.rightSE.segment = this; - this.rightSE.otherSE = this.leftSE; - this.leftSE.otherSE = this.rightSE; - } - }, { - key: "bbox", - value: function bbox() { - var y1 = this.leftSE.point.y; - var y2 = this.rightSE.point.y; - return { - ll: { - x: this.leftSE.point.x, - y: y1 < y2 ? y1 : y2 - }, - ur: { - x: this.rightSE.point.x, - y: y1 > y2 ? y1 : y2 - } - }; - } - /* A vector from the left point to the right */ - - }, { - key: "vector", - value: function vector() { - return { - x: this.rightSE.point.x - this.leftSE.point.x, - y: this.rightSE.point.y - this.leftSE.point.y - }; - } - }, { - key: "isAnEndpoint", - value: function isAnEndpoint(pt) { - return pt.x === this.leftSE.point.x && pt.y === this.leftSE.point.y || pt.x === this.rightSE.point.x && pt.y === this.rightSE.point.y; - } - /* Compare this segment with a point. - * - * A point P is considered to be colinear to a segment if there - * exists a distance D such that if we travel along the segment - * from one * endpoint towards the other a distance D, we find - * ourselves at point P. - * - * Return value indicates: - * - * 1: point lies above the segment (to the left of vertical) - * 0: point is colinear to segment - * -1: point lies below the segment (to the right of vertical) - */ - - }, { - key: "comparePoint", - value: function comparePoint(point) { - if (this.isAnEndpoint(point)) return 0; - var lPt = this.leftSE.point; - var rPt = this.rightSE.point; - var v = this.vector(); // Exactly vertical segments. - - if (lPt.x === rPt.x) { - if (point.x === lPt.x) return 0; - return point.x < lPt.x ? 1 : -1; - } // Nearly vertical segments with an intersection. - // Check to see where a point on the line with matching Y coordinate is. - - - var yDist = (point.y - lPt.y) / v.y; - var xFromYDist = lPt.x + yDist * v.x; - if (point.x === xFromYDist) return 0; // General case. - // Check to see where a point on the line with matching X coordinate is. - - var xDist = (point.x - lPt.x) / v.x; - var yFromXDist = lPt.y + xDist * v.y; - if (point.y === yFromXDist) return 0; - return point.y < yFromXDist ? -1 : 1; - } - /** - * Given another segment, returns the first non-trivial intersection - * between the two segments (in terms of sweep line ordering), if it exists. - * - * A 'non-trivial' intersection is one that will cause one or both of the - * segments to be split(). As such, 'trivial' vs. 'non-trivial' intersection: - * - * * endpoint of segA with endpoint of segB --> trivial - * * endpoint of segA with point along segB --> non-trivial - * * endpoint of segB with point along segA --> non-trivial - * * point along segA with point along segB --> non-trivial - * - * If no non-trivial intersection exists, return null - * Else, return null. - */ - - }, { - key: "getIntersection", - value: function getIntersection(other) { - // If bboxes don't overlap, there can't be any intersections - var tBbox = this.bbox(); - var oBbox = other.bbox(); - var bboxOverlap = getBboxOverlap(tBbox, oBbox); - if (bboxOverlap === null) return null; // We first check to see if the endpoints can be considered intersections. - // This will 'snap' intersections to endpoints if possible, and will - // handle cases of colinearity. - - var tlp = this.leftSE.point; - var trp = this.rightSE.point; - var olp = other.leftSE.point; - var orp = other.rightSE.point; // does each endpoint touch the other segment? - // note that we restrict the 'touching' definition to only allow segments - // to touch endpoints that lie forward from where we are in the sweep line pass - - var touchesOtherLSE = isInBbox(tBbox, olp) && this.comparePoint(olp) === 0; - var touchesThisLSE = isInBbox(oBbox, tlp) && other.comparePoint(tlp) === 0; - var touchesOtherRSE = isInBbox(tBbox, orp) && this.comparePoint(orp) === 0; - var touchesThisRSE = isInBbox(oBbox, trp) && other.comparePoint(trp) === 0; // do left endpoints match? - - if (touchesThisLSE && touchesOtherLSE) { - // these two cases are for colinear segments with matching left - // endpoints, and one segment being longer than the other - if (touchesThisRSE && !touchesOtherRSE) return trp; - if (!touchesThisRSE && touchesOtherRSE) return orp; // either the two segments match exactly (two trival intersections) - // or just on their left endpoint (one trivial intersection - - return null; - } // does this left endpoint matches (other doesn't) - - - if (touchesThisLSE) { - // check for segments that just intersect on opposing endpoints - if (touchesOtherRSE) { - if (tlp.x === orp.x && tlp.y === orp.y) return null; - } // t-intersection on left endpoint - - - return tlp; - } // does other left endpoint matches (this doesn't) - - - if (touchesOtherLSE) { - // check for segments that just intersect on opposing endpoints - if (touchesThisRSE) { - if (trp.x === olp.x && trp.y === olp.y) return null; - } // t-intersection on left endpoint - - - return olp; - } // trivial intersection on right endpoints - - - if (touchesThisRSE && touchesOtherRSE) return null; // t-intersections on just one right endpoint - - if (touchesThisRSE) return trp; - if (touchesOtherRSE) return orp; // None of our endpoints intersect. Look for a general intersection between - // infinite lines laid over the segments - - var pt = intersection(tlp, this.vector(), olp, other.vector()); // are the segments parrallel? Note that if they were colinear with overlap, - // they would have an endpoint intersection and that case was already handled above - - if (pt === null) return null; // is the intersection found between the lines not on the segments? - - if (!isInBbox(bboxOverlap, pt)) return null; // round the the computed point if needed - - return rounder.round(pt.x, pt.y); - } - /** - * Split the given segment into multiple segments on the given points. - * * Each existing segment will retain its leftSE and a new rightSE will be - * generated for it. - * * A new segment will be generated which will adopt the original segment's - * rightSE, and a new leftSE will be generated for it. - * * If there are more than two points given to split on, new segments - * in the middle will be generated with new leftSE and rightSE's. - * * An array of the newly generated SweepEvents will be returned. - * - * Warning: input array of points is modified - */ - - }, { - key: "split", - value: function split(point) { - var newEvents = []; - var alreadyLinked = point.events !== undefined; - var newLeftSE = new SweepEvent(point, true); - var newRightSE = new SweepEvent(point, false); - var oldRightSE = this.rightSE; - this.replaceRightSE(newRightSE); - newEvents.push(newRightSE); - newEvents.push(newLeftSE); - var newSeg = new Segment(newLeftSE, oldRightSE, this.rings.slice(), this.windings.slice()); // when splitting a nearly vertical downward-facing segment, - // sometimes one of the resulting new segments is vertical, in which - // case its left and right events may need to be swapped - - if (SweepEvent.comparePoints(newSeg.leftSE.point, newSeg.rightSE.point) > 0) { - newSeg.swapEvents(); + /* When a segment is split, the rightSE is replaced with a new sweep event */ + replaceRightSE(newRightSE) { + this.rightSE = newRightSE; + this.rightSE.segment = this; + this.rightSE.otherSE = this.leftSE; + this.leftSE.otherSE = this.rightSE; + } + bbox() { + const y1 = this.leftSE.point.y; + const y2 = this.rightSE.point.y; + return { + ll: { + x: this.leftSE.point.x, + y: y1 < y2 ? y1 : y2 + }, + ur: { + x: this.rightSE.point.x, + y: y1 > y2 ? y1 : y2 } + }; + } - if (SweepEvent.comparePoints(this.leftSE.point, this.rightSE.point) > 0) { - this.swapEvents(); - } // in the point we just used to create new sweep events with was already - // linked to other events, we need to check if either of the affected - // segments should be consumed + /* A vector from the left point to the right */ + vector() { + return { + x: this.rightSE.point.x - this.leftSE.point.x, + y: this.rightSE.point.y - this.leftSE.point.y + }; + } + isAnEndpoint(pt) { + return pt.x === this.leftSE.point.x && pt.y === this.leftSE.point.y || pt.x === this.rightSE.point.x && pt.y === this.rightSE.point.y; + } + /* Compare this segment with a point. + * + * A point P is considered to be colinear to a segment if there + * exists a distance D such that if we travel along the segment + * from one * endpoint towards the other a distance D, we find + * ourselves at point P. + * + * Return value indicates: + * + * 1: point lies above the segment (to the left of vertical) + * 0: point is colinear to segment + * -1: point lies below the segment (to the right of vertical) + */ + comparePoint(point) { + if (this.isAnEndpoint(point)) return 0; + const lPt = this.leftSE.point; + const rPt = this.rightSE.point; + const v = this.vector(); + + // Exactly vertical segments. + if (lPt.x === rPt.x) { + if (point.x === lPt.x) return 0; + return point.x < lPt.x ? 1 : -1; + } - if (alreadyLinked) { - newLeftSE.checkForConsuming(); - newRightSE.checkForConsuming(); - } + // Nearly vertical segments with an intersection. + // Check to see where a point on the line with matching Y coordinate is. + const yDist = (point.y - lPt.y) / v.y; + const xFromYDist = lPt.x + yDist * v.x; + if (point.x === xFromYDist) return 0; + + // General case. + // Check to see where a point on the line with matching X coordinate is. + const xDist = (point.x - lPt.x) / v.x; + const yFromXDist = lPt.y + xDist * v.y; + if (point.y === yFromXDist) return 0; + return point.y < yFromXDist ? -1 : 1; + } - return newEvents; - } - /* Swap which event is left and right */ - - }, { - key: "swapEvents", - value: function swapEvents() { - var tmpEvt = this.rightSE; - this.rightSE = this.leftSE; - this.leftSE = tmpEvt; - this.leftSE.isLeft = true; - this.rightSE.isLeft = false; - - for (var i = 0, iMax = this.windings.length; i < iMax; i++) { - this.windings[i] *= -1; - } + /** + * Given another segment, returns the first non-trivial intersection + * between the two segments (in terms of sweep line ordering), if it exists. + * + * A 'non-trivial' intersection is one that will cause one or both of the + * segments to be split(). As such, 'trivial' vs. 'non-trivial' intersection: + * + * * endpoint of segA with endpoint of segB --> trivial + * * endpoint of segA with point along segB --> non-trivial + * * endpoint of segB with point along segA --> non-trivial + * * point along segA with point along segB --> non-trivial + * + * If no non-trivial intersection exists, return null + * Else, return null. + */ + getIntersection(other) { + // If bboxes don't overlap, there can't be any intersections + const tBbox = this.bbox(); + const oBbox = other.bbox(); + const bboxOverlap = getBboxOverlap(tBbox, oBbox); + if (bboxOverlap === null) return null; + + // We first check to see if the endpoints can be considered intersections. + // This will 'snap' intersections to endpoints if possible, and will + // handle cases of colinearity. + + const tlp = this.leftSE.point; + const trp = this.rightSE.point; + const olp = other.leftSE.point; + const orp = other.rightSE.point; + + // does each endpoint touch the other segment? + // note that we restrict the 'touching' definition to only allow segments + // to touch endpoints that lie forward from where we are in the sweep line pass + const touchesOtherLSE = isInBbox(tBbox, olp) && this.comparePoint(olp) === 0; + const touchesThisLSE = isInBbox(oBbox, tlp) && other.comparePoint(tlp) === 0; + const touchesOtherRSE = isInBbox(tBbox, orp) && this.comparePoint(orp) === 0; + const touchesThisRSE = isInBbox(oBbox, trp) && other.comparePoint(trp) === 0; + + // do left endpoints match? + if (touchesThisLSE && touchesOtherLSE) { + // these two cases are for colinear segments with matching left + // endpoints, and one segment being longer than the other + if (touchesThisRSE && !touchesOtherRSE) return trp; + if (!touchesThisRSE && touchesOtherRSE) return orp; + // either the two segments match exactly (two trival intersections) + // or just on their left endpoint (one trivial intersection + return null; } - /* Consume another segment. We take their rings under our wing - * and mark them as consumed. Use for perfectly overlapping segments */ - - }, { - key: "consume", - value: function consume(other) { - var consumer = this; - var consumee = other; - while (consumer.consumedBy) { - consumer = consumer.consumedBy; + // does this left endpoint matches (other doesn't) + if (touchesThisLSE) { + // check for segments that just intersect on opposing endpoints + if (touchesOtherRSE) { + if (tlp.x === orp.x && tlp.y === orp.y) return null; } + // t-intersection on left endpoint + return tlp; + } - while (consumee.consumedBy) { - consumee = consumee.consumedBy; + // does other left endpoint matches (this doesn't) + if (touchesOtherLSE) { + // check for segments that just intersect on opposing endpoints + if (touchesThisRSE) { + if (trp.x === olp.x && trp.y === olp.y) return null; } + // t-intersection on left endpoint + return olp; + } - var cmp = Segment.compare(consumer, consumee); - if (cmp === 0) return; // already consumed - // the winner of the consumption is the earlier segment - // according to sweep line ordering - - if (cmp > 0) { - var tmp = consumer; - consumer = consumee; - consumee = tmp; - } // make sure a segment doesn't consume it's prev + // trivial intersection on right endpoints + if (touchesThisRSE && touchesOtherRSE) return null; + // t-intersections on just one right endpoint + if (touchesThisRSE) return trp; + if (touchesOtherRSE) return orp; - if (consumer.prev === consumee) { - var _tmp = consumer; - consumer = consumee; - consumee = _tmp; - } + // None of our endpoints intersect. Look for a general intersection between + // infinite lines laid over the segments + const pt = intersection$1(tlp, this.vector(), olp, other.vector()); - for (var i = 0, iMax = consumee.rings.length; i < iMax; i++) { - var ring = consumee.rings[i]; - var winding = consumee.windings[i]; - var index = consumer.rings.indexOf(ring); + // are the segments parrallel? Note that if they were colinear with overlap, + // they would have an endpoint intersection and that case was already handled above + if (pt === null) return null; - if (index === -1) { - consumer.rings.push(ring); - consumer.windings.push(winding); - } else consumer.windings[index] += winding; - } + // is the intersection found between the lines not on the segments? + if (!isInBbox(bboxOverlap, pt)) return null; - consumee.rings = null; - consumee.windings = null; - consumee.consumedBy = consumer; // mark sweep events consumed as to maintain ordering in sweep event queue + // round the the computed point if needed + return rounder.round(pt.x, pt.y); + } - consumee.leftSE.consumedBy = consumer.leftSE; - consumee.rightSE.consumedBy = consumer.rightSE; - } - /* The first segment previous segment chain that is in the result */ - - }, { - key: "prevInResult", - value: function prevInResult() { - if (this._prevInResult !== undefined) return this._prevInResult; - if (!this.prev) this._prevInResult = null;else if (this.prev.isInResult()) this._prevInResult = this.prev;else this._prevInResult = this.prev.prevInResult(); - return this._prevInResult; + /** + * Split the given segment into multiple segments on the given points. + * * Each existing segment will retain its leftSE and a new rightSE will be + * generated for it. + * * A new segment will be generated which will adopt the original segment's + * rightSE, and a new leftSE will be generated for it. + * * If there are more than two points given to split on, new segments + * in the middle will be generated with new leftSE and rightSE's. + * * An array of the newly generated SweepEvents will be returned. + * + * Warning: input array of points is modified + */ + split(point) { + const newEvents = []; + const alreadyLinked = point.events !== undefined; + const newLeftSE = new SweepEvent(point, true); + const newRightSE = new SweepEvent(point, false); + const oldRightSE = this.rightSE; + this.replaceRightSE(newRightSE); + newEvents.push(newRightSE); + newEvents.push(newLeftSE); + const newSeg = new Segment(newLeftSE, oldRightSE, this.rings.slice(), this.windings.slice()); + + // when splitting a nearly vertical downward-facing segment, + // sometimes one of the resulting new segments is vertical, in which + // case its left and right events may need to be swapped + if (SweepEvent.comparePoints(newSeg.leftSE.point, newSeg.rightSE.point) > 0) { + newSeg.swapEvents(); } - }, { - key: "beforeState", - value: function beforeState() { - if (this._beforeState !== undefined) return this._beforeState; - if (!this.prev) this._beforeState = { - rings: [], - windings: [], - multiPolys: [] - };else { - var seg = this.prev.consumedBy || this.prev; - this._beforeState = seg.afterState(); - } - return this._beforeState; + if (SweepEvent.comparePoints(this.leftSE.point, this.rightSE.point) > 0) { + this.swapEvents(); } - }, { - key: "afterState", - value: function afterState() { - if (this._afterState !== undefined) return this._afterState; - var beforeState = this.beforeState(); - this._afterState = { - rings: beforeState.rings.slice(0), - windings: beforeState.windings.slice(0), - multiPolys: [] - }; - var ringsAfter = this._afterState.rings; - var windingsAfter = this._afterState.windings; - var mpsAfter = this._afterState.multiPolys; // calculate ringsAfter, windingsAfter - - for (var i = 0, iMax = this.rings.length; i < iMax; i++) { - var ring = this.rings[i]; - var winding = this.windings[i]; - var index = ringsAfter.indexOf(ring); - - if (index === -1) { - ringsAfter.push(ring); - windingsAfter.push(winding); - } else windingsAfter[index] += winding; - } // calcualte polysAfter - - - var polysAfter = []; - var polysExclude = []; - - for (var _i = 0, _iMax = ringsAfter.length; _i < _iMax; _i++) { - if (windingsAfter[_i] === 0) continue; // non-zero rule - - var _ring = ringsAfter[_i]; - var poly = _ring.poly; - if (polysExclude.indexOf(poly) !== -1) continue; - if (_ring.isExterior) polysAfter.push(poly);else { - if (polysExclude.indexOf(poly) === -1) polysExclude.push(poly); - - var _index = polysAfter.indexOf(_ring.poly); - - if (_index !== -1) polysAfter.splice(_index, 1); - } - } // calculate multiPolysAfter - - for (var _i2 = 0, _iMax2 = polysAfter.length; _i2 < _iMax2; _i2++) { - var mp = polysAfter[_i2].multiPoly; - if (mpsAfter.indexOf(mp) === -1) mpsAfter.push(mp); - } - - return this._afterState; + // in the point we just used to create new sweep events with was already + // linked to other events, we need to check if either of the affected + // segments should be consumed + if (alreadyLinked) { + newLeftSE.checkForConsuming(); + newRightSE.checkForConsuming(); } - /* Is this segment part of the final result? */ - - }, { - key: "isInResult", - value: function isInResult() { - // if we've been consumed, we're not in the result - if (this.consumedBy) return false; - if (this._isInResult !== undefined) return this._isInResult; - var mpsBefore = this.beforeState().multiPolys; - var mpsAfter = this.afterState().multiPolys; - - switch (operation.type) { - case 'union': - { - // UNION - included iff: - // * On one side of us there is 0 poly interiors AND - // * On the other side there is 1 or more. - var noBefores = mpsBefore.length === 0; - var noAfters = mpsAfter.length === 0; - this._isInResult = noBefores !== noAfters; - break; - } + return newEvents; + } - case 'intersection': - { - // INTERSECTION - included iff: - // * on one side of us all multipolys are rep. with poly interiors AND - // * on the other side of us, not all multipolys are repsented - // with poly interiors - var least; - var most; - - if (mpsBefore.length < mpsAfter.length) { - least = mpsBefore.length; - most = mpsAfter.length; - } else { - least = mpsAfter.length; - most = mpsBefore.length; - } + /* Swap which event is left and right */ + swapEvents() { + const tmpEvt = this.rightSE; + this.rightSE = this.leftSE; + this.leftSE = tmpEvt; + this.leftSE.isLeft = true; + this.rightSE.isLeft = false; + for (let i = 0, iMax = this.windings.length; i < iMax; i++) { + this.windings[i] *= -1; + } + } - this._isInResult = most === operation.numMultiPolys && least < most; - break; - } + /* Consume another segment. We take their rings under our wing + * and mark them as consumed. Use for perfectly overlapping segments */ + consume(other) { + let consumer = this; + let consumee = other; + while (consumer.consumedBy) consumer = consumer.consumedBy; + while (consumee.consumedBy) consumee = consumee.consumedBy; + const cmp = Segment.compare(consumer, consumee); + if (cmp === 0) return; // already consumed + // the winner of the consumption is the earlier segment + // according to sweep line ordering + if (cmp > 0) { + const tmp = consumer; + consumer = consumee; + consumee = tmp; + } - case 'xor': - { - // XOR - included iff: - // * the difference between the number of multipolys represented - // with poly interiors on our two sides is an odd number - var diff = Math.abs(mpsBefore.length - mpsAfter.length); - this._isInResult = diff % 2 === 1; - break; - } + // make sure a segment doesn't consume it's prev + if (consumer.prev === consumee) { + const tmp = consumer; + consumer = consumee; + consumee = tmp; + } + for (let i = 0, iMax = consumee.rings.length; i < iMax; i++) { + const ring = consumee.rings[i]; + const winding = consumee.windings[i]; + const index = consumer.rings.indexOf(ring); + if (index === -1) { + consumer.rings.push(ring); + consumer.windings.push(winding); + } else consumer.windings[index] += winding; + } + consumee.rings = null; + consumee.windings = null; + consumee.consumedBy = consumer; - case 'difference': - { - // DIFFERENCE included iff: - // * on exactly one side, we have just the subject - var isJustSubject = function isJustSubject(mps) { - return mps.length === 1 && mps[0].isSubject; - }; + // mark sweep events consumed as to maintain ordering in sweep event queue + consumee.leftSE.consumedBy = consumer.leftSE; + consumee.rightSE.consumedBy = consumer.rightSE; + } - this._isInResult = isJustSubject(mpsBefore) !== isJustSubject(mpsAfter); - break; - } + /* The first segment previous segment chain that is in the result */ + prevInResult() { + if (this._prevInResult !== undefined) return this._prevInResult; + if (!this.prev) this._prevInResult = null;else if (this.prev.isInResult()) this._prevInResult = this.prev;else this._prevInResult = this.prev.prevInResult(); + return this._prevInResult; + } + beforeState() { + if (this._beforeState !== undefined) return this._beforeState; + if (!this.prev) this._beforeState = { + rings: [], + windings: [], + multiPolys: [] + };else { + const seg = this.prev.consumedBy || this.prev; + this._beforeState = seg.afterState(); + } + return this._beforeState; + } + afterState() { + if (this._afterState !== undefined) return this._afterState; + const beforeState = this.beforeState(); + this._afterState = { + rings: beforeState.rings.slice(0), + windings: beforeState.windings.slice(0), + multiPolys: [] + }; + const ringsAfter = this._afterState.rings; + const windingsAfter = this._afterState.windings; + const mpsAfter = this._afterState.multiPolys; + + // calculate ringsAfter, windingsAfter + for (let i = 0, iMax = this.rings.length; i < iMax; i++) { + const ring = this.rings[i]; + const winding = this.windings[i]; + const index = ringsAfter.indexOf(ring); + if (index === -1) { + ringsAfter.push(ring); + windingsAfter.push(winding); + } else windingsAfter[index] += winding; + } - default: - throw new Error("Unrecognized operation type found ".concat(operation.type)); + // calcualte polysAfter + const polysAfter = []; + const polysExclude = []; + for (let i = 0, iMax = ringsAfter.length; i < iMax; i++) { + if (windingsAfter[i] === 0) continue; // non-zero rule + const ring = ringsAfter[i]; + const poly = ring.poly; + if (polysExclude.indexOf(poly) !== -1) continue; + if (ring.isExterior) polysAfter.push(poly);else { + if (polysExclude.indexOf(poly) === -1) polysExclude.push(poly); + const index = polysAfter.indexOf(ring.poly); + if (index !== -1) polysAfter.splice(index, 1); } - - return this._isInResult; } - }], [{ - key: "fromRing", - value: function fromRing(pt1, pt2, ring) { - var leftPt, rightPt, winding; // ordering the two points according to sweep line ordering - - var cmpPts = SweepEvent.comparePoints(pt1, pt2); - - if (cmpPts < 0) { - leftPt = pt1; - rightPt = pt2; - winding = 1; - } else if (cmpPts > 0) { - leftPt = pt2; - rightPt = pt1; - winding = -1; - } else throw new Error("Tried to create degenerate segment at [".concat(pt1.x, ", ").concat(pt1.y, "]")); - - var leftSE = new SweepEvent(leftPt, true); - var rightSE = new SweepEvent(rightPt, false); - return new Segment(leftSE, rightSE, [ring], [winding]); - } - }]); - return Segment; -}(); + // calculate multiPolysAfter + for (let i = 0, iMax = polysAfter.length; i < iMax; i++) { + const mp = polysAfter[i].multiPoly; + if (mpsAfter.indexOf(mp) === -1) mpsAfter.push(mp); + } + return this._afterState; + } -var RingIn = /*#__PURE__*/function () { - function RingIn(geomRing, poly, isExterior) { - _classCallCheck(this, RingIn); + /* Is this segment part of the final result? */ + isInResult() { + // if we've been consumed, we're not in the result + if (this.consumedBy) return false; + if (this._isInResult !== undefined) return this._isInResult; + const mpsBefore = this.beforeState().multiPolys; + const mpsAfter = this.afterState().multiPolys; + switch (operation.type) { + case "union": + { + // UNION - included iff: + // * On one side of us there is 0 poly interiors AND + // * On the other side there is 1 or more. + const noBefores = mpsBefore.length === 0; + const noAfters = mpsAfter.length === 0; + this._isInResult = noBefores !== noAfters; + break; + } + case "intersection": + { + // INTERSECTION - included iff: + // * on one side of us all multipolys are rep. with poly interiors AND + // * on the other side of us, not all multipolys are repsented + // with poly interiors + let least; + let most; + if (mpsBefore.length < mpsAfter.length) { + least = mpsBefore.length; + most = mpsAfter.length; + } else { + least = mpsAfter.length; + most = mpsBefore.length; + } + this._isInResult = most === operation.numMultiPolys && least < most; + break; + } + case "xor": + { + // XOR - included iff: + // * the difference between the number of multipolys represented + // with poly interiors on our two sides is an odd number + const diff = Math.abs(mpsBefore.length - mpsAfter.length); + this._isInResult = diff % 2 === 1; + break; + } + case "difference": + { + // DIFFERENCE included iff: + // * on exactly one side, we have just the subject + const isJustSubject = mps => mps.length === 1 && mps[0].isSubject; + this._isInResult = isJustSubject(mpsBefore) !== isJustSubject(mpsAfter); + break; + } + default: + throw new Error(`Unrecognized operation type found ${operation.type}`); + } + return this._isInResult; + } +} +class RingIn { + constructor(geomRing, poly, isExterior) { if (!Array.isArray(geomRing) || geomRing.length === 0) { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - this.poly = poly; this.isExterior = isExterior; this.segments = []; - - if (typeof geomRing[0][0] !== 'number' || typeof geomRing[0][1] !== 'number') { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + if (typeof geomRing[0][0] !== "number" || typeof geomRing[0][1] !== "number") { + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - - var firstPoint = rounder.round(geomRing[0][0], geomRing[0][1]); + const firstPoint = rounder.round(geomRing[0][0], geomRing[0][1]); this.bbox = { ll: { x: firstPoint.x, @@ -1054,15 +929,13 @@ var RingIn = /*#__PURE__*/function () { y: firstPoint.y } }; - var prevPoint = firstPoint; - - for (var i = 1, iMax = geomRing.length; i < iMax; i++) { - if (typeof geomRing[i][0] !== 'number' || typeof geomRing[i][1] !== 'number') { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + let prevPoint = firstPoint; + for (let i = 1, iMax = geomRing.length; i < iMax; i++) { + if (typeof geomRing[i][0] !== "number" || typeof geomRing[i][1] !== "number") { + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - - var point = rounder.round(geomRing[i][0], geomRing[i][1]); // skip repeated points - + let point = rounder.round(geomRing[i][0], geomRing[i][1]); + // skip repeated points if (point.x === prevPoint.x && point.y === prevPoint.y) continue; this.segments.push(Segment.fromRing(prevPoint, point, this)); if (point.x < this.bbox.ll.x) this.bbox.ll.x = point.x; @@ -1070,41 +943,29 @@ var RingIn = /*#__PURE__*/function () { if (point.x > this.bbox.ur.x) this.bbox.ur.x = point.x; if (point.y > this.bbox.ur.y) this.bbox.ur.y = point.y; prevPoint = point; - } // add segment from last to first if last is not the same as first - - + } + // add segment from last to first if last is not the same as first if (firstPoint.x !== prevPoint.x || firstPoint.y !== prevPoint.y) { this.segments.push(Segment.fromRing(prevPoint, firstPoint, this)); } } - - _createClass(RingIn, [{ - key: "getSweepEvents", - value: function getSweepEvents() { - var sweepEvents = []; - - for (var i = 0, iMax = this.segments.length; i < iMax; i++) { - var segment = this.segments[i]; - sweepEvents.push(segment.leftSE); - sweepEvents.push(segment.rightSE); - } - - return sweepEvents; + getSweepEvents() { + const sweepEvents = []; + for (let i = 0, iMax = this.segments.length; i < iMax; i++) { + const segment = this.segments[i]; + sweepEvents.push(segment.leftSE); + sweepEvents.push(segment.rightSE); } - }]); - - return RingIn; -}(); -var PolyIn = /*#__PURE__*/function () { - function PolyIn(geomPoly, multiPoly) { - _classCallCheck(this, PolyIn); - + return sweepEvents; + } +} +class PolyIn { + constructor(geomPoly, multiPoly) { if (!Array.isArray(geomPoly)) { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - - this.exteriorRing = new RingIn(geomPoly[0], this, true); // copy by value - + this.exteriorRing = new RingIn(geomPoly[0], this, true); + // copy by value this.bbox = { ll: { x: this.exteriorRing.bbox.ll.x, @@ -1116,53 +977,39 @@ var PolyIn = /*#__PURE__*/function () { } }; this.interiorRings = []; - - for (var i = 1, iMax = geomPoly.length; i < iMax; i++) { - var ring = new RingIn(geomPoly[i], this, false); + for (let i = 1, iMax = geomPoly.length; i < iMax; i++) { + const ring = new RingIn(geomPoly[i], this, false); if (ring.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = ring.bbox.ll.x; if (ring.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = ring.bbox.ll.y; if (ring.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = ring.bbox.ur.x; if (ring.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = ring.bbox.ur.y; this.interiorRings.push(ring); } - this.multiPoly = multiPoly; } - - _createClass(PolyIn, [{ - key: "getSweepEvents", - value: function getSweepEvents() { - var sweepEvents = this.exteriorRing.getSweepEvents(); - - for (var i = 0, iMax = this.interiorRings.length; i < iMax; i++) { - var ringSweepEvents = this.interiorRings[i].getSweepEvents(); - - for (var j = 0, jMax = ringSweepEvents.length; j < jMax; j++) { - sweepEvents.push(ringSweepEvents[j]); - } + getSweepEvents() { + const sweepEvents = this.exteriorRing.getSweepEvents(); + for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) { + const ringSweepEvents = this.interiorRings[i].getSweepEvents(); + for (let j = 0, jMax = ringSweepEvents.length; j < jMax; j++) { + sweepEvents.push(ringSweepEvents[j]); } - - return sweepEvents; } - }]); - - return PolyIn; -}(); -var MultiPolyIn = /*#__PURE__*/function () { - function MultiPolyIn(geom, isSubject) { - _classCallCheck(this, MultiPolyIn); - + return sweepEvents; + } +} +class MultiPolyIn { + constructor(geom, isSubject) { if (!Array.isArray(geom)) { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - try { // if the input looks like a polygon, convert it to a multipolygon - if (typeof geom[0][0][0] === 'number') geom = [geom]; - } catch (ex) {// The input is either malformed or has empty arrays. + if (typeof geom[0][0][0] === "number") geom = [geom]; + } catch (ex) { + // The input is either malformed or has empty arrays. // In either case, it will be handled later on. } - this.polys = []; this.bbox = { ll: { @@ -1174,311 +1021,234 @@ var MultiPolyIn = /*#__PURE__*/function () { y: Number.NEGATIVE_INFINITY } }; - - for (var i = 0, iMax = geom.length; i < iMax; i++) { - var poly = new PolyIn(geom[i], this); + for (let i = 0, iMax = geom.length; i < iMax; i++) { + const poly = new PolyIn(geom[i], this); if (poly.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = poly.bbox.ll.x; if (poly.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = poly.bbox.ll.y; if (poly.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = poly.bbox.ur.x; if (poly.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = poly.bbox.ur.y; this.polys.push(poly); } - this.isSubject = isSubject; } - - _createClass(MultiPolyIn, [{ - key: "getSweepEvents", - value: function getSweepEvents() { - var sweepEvents = []; - - for (var i = 0, iMax = this.polys.length; i < iMax; i++) { - var polySweepEvents = this.polys[i].getSweepEvents(); - - for (var j = 0, jMax = polySweepEvents.length; j < jMax; j++) { - sweepEvents.push(polySweepEvents[j]); - } + getSweepEvents() { + const sweepEvents = []; + for (let i = 0, iMax = this.polys.length; i < iMax; i++) { + const polySweepEvents = this.polys[i].getSweepEvents(); + for (let j = 0, jMax = polySweepEvents.length; j < jMax; j++) { + sweepEvents.push(polySweepEvents[j]); } - - return sweepEvents; } - }]); - - return MultiPolyIn; -}(); - -var RingOut = /*#__PURE__*/function () { - _createClass(RingOut, null, [{ - key: "factory", - - /* Given the segments from the sweep line pass, compute & return a series - * of closed rings from all the segments marked to be part of the result */ - value: function factory(allSegments) { - var ringsOut = []; - - for (var i = 0, iMax = allSegments.length; i < iMax; i++) { - var segment = allSegments[i]; - if (!segment.isInResult() || segment.ringOut) continue; - var prevEvent = null; - var event = segment.leftSE; - var nextEvent = segment.rightSE; - var events = [event]; - var startingPoint = event.point; - var intersectionLEs = []; - /* Walk the chain of linked events to form a closed ring */ + return sweepEvents; + } +} + +class RingOut { + /* Given the segments from the sweep line pass, compute & return a series + * of closed rings from all the segments marked to be part of the result */ + static factory(allSegments) { + const ringsOut = []; + for (let i = 0, iMax = allSegments.length; i < iMax; i++) { + const segment = allSegments[i]; + if (!segment.isInResult() || segment.ringOut) continue; + let prevEvent = null; + let event = segment.leftSE; + let nextEvent = segment.rightSE; + const events = [event]; + const startingPoint = event.point; + const intersectionLEs = []; + + /* Walk the chain of linked events to form a closed ring */ + while (true) { + prevEvent = event; + event = nextEvent; + events.push(event); + /* Is the ring complete? */ + if (event.point === startingPoint) break; while (true) { - prevEvent = event; - event = nextEvent; - events.push(event); - /* Is the ring complete? */ - - if (event.point === startingPoint) break; - - while (true) { - var availableLEs = event.getAvailableLinkedEvents(); - /* Did we hit a dead end? This shouldn't happen. Indicates some earlier - * part of the algorithm malfunctioned... please file a bug report. */ - - if (availableLEs.length === 0) { - var firstPt = events[0].point; - var lastPt = events[events.length - 1].point; - throw new Error("Unable to complete output ring starting at [".concat(firstPt.x, ",") + " ".concat(firstPt.y, "]. Last matching segment found ends at") + " [".concat(lastPt.x, ", ").concat(lastPt.y, "].")); - } - /* Only one way to go, so cotinue on the path */ + const availableLEs = event.getAvailableLinkedEvents(); + + /* Did we hit a dead end? This shouldn't happen. + * Indicates some earlier part of the algorithm malfunctioned. */ + if (availableLEs.length === 0) { + const firstPt = events[0].point; + const lastPt = events[events.length - 1].point; + throw new Error(`Unable to complete output ring starting at [${firstPt.x},` + ` ${firstPt.y}]. Last matching segment found ends at` + ` [${lastPt.x}, ${lastPt.y}].`); + } + /* Only one way to go, so cotinue on the path */ + if (availableLEs.length === 1) { + nextEvent = availableLEs[0].otherSE; + break; + } - if (availableLEs.length === 1) { - nextEvent = availableLEs[0].otherSE; + /* We must have an intersection. Check for a completed loop */ + let indexLE = null; + for (let j = 0, jMax = intersectionLEs.length; j < jMax; j++) { + if (intersectionLEs[j].point === event.point) { + indexLE = j; break; } - /* We must have an intersection. Check for a completed loop */ - - - var indexLE = null; - - for (var j = 0, jMax = intersectionLEs.length; j < jMax; j++) { - if (intersectionLEs[j].point === event.point) { - indexLE = j; - break; - } - } - /* Found a completed loop. Cut that off and make a ring */ - - - if (indexLE !== null) { - var intersectionLE = intersectionLEs.splice(indexLE)[0]; - var ringEvents = events.splice(intersectionLE.index); - ringEvents.unshift(ringEvents[0].otherSE); - ringsOut.push(new RingOut(ringEvents.reverse())); - continue; - } - /* register the intersection */ - - - intersectionLEs.push({ - index: events.length, - point: event.point - }); - /* Choose the left-most option to continue the walk */ - - var comparator = event.getLeftmostComparator(prevEvent); - nextEvent = availableLEs.sort(comparator)[0].otherSE; - break; } + /* Found a completed loop. Cut that off and make a ring */ + if (indexLE !== null) { + const intersectionLE = intersectionLEs.splice(indexLE)[0]; + const ringEvents = events.splice(intersectionLE.index); + ringEvents.unshift(ringEvents[0].otherSE); + ringsOut.push(new RingOut(ringEvents.reverse())); + continue; + } + /* register the intersection */ + intersectionLEs.push({ + index: events.length, + point: event.point + }); + /* Choose the left-most option to continue the walk */ + const comparator = event.getLeftmostComparator(prevEvent); + nextEvent = availableLEs.sort(comparator)[0].otherSE; + break; } - - ringsOut.push(new RingOut(events)); } - - return ringsOut; + ringsOut.push(new RingOut(events)); } - }]); - - function RingOut(events) { - _classCallCheck(this, RingOut); - + return ringsOut; + } + constructor(events) { this.events = events; - - for (var i = 0, iMax = events.length; i < iMax; i++) { + for (let i = 0, iMax = events.length; i < iMax; i++) { events[i].segment.ringOut = this; } - this.poly = null; } - - _createClass(RingOut, [{ - key: "getGeom", - value: function getGeom() { - // Remove superfluous points (ie extra points along a straight line), - var prevPt = this.events[0].point; - var points = [prevPt]; - - for (var i = 1, iMax = this.events.length - 1; i < iMax; i++) { - var _pt = this.events[i].point; - var _nextPt = this.events[i + 1].point; - if (compareVectorAngles(_pt, prevPt, _nextPt) === 0) continue; - points.push(_pt); - prevPt = _pt; - } // ring was all (within rounding error of angle calc) colinear points - - - if (points.length === 1) return null; // check if the starting point is necessary - - var pt = points[0]; - var nextPt = points[1]; - if (compareVectorAngles(pt, prevPt, nextPt) === 0) points.shift(); - points.push(points[0]); - var step = this.isExteriorRing() ? 1 : -1; - var iStart = this.isExteriorRing() ? 0 : points.length - 1; - var iEnd = this.isExteriorRing() ? points.length : -1; - var orderedPoints = []; - - for (var _i = iStart; _i != iEnd; _i += step) { - orderedPoints.push([points[_i].x, points[_i].y]); - } - - return orderedPoints; + getGeom() { + // Remove superfluous points (ie extra points along a straight line), + let prevPt = this.events[0].point; + const points = [prevPt]; + for (let i = 1, iMax = this.events.length - 1; i < iMax; i++) { + const pt = this.events[i].point; + const nextPt = this.events[i + 1].point; + if (compareVectorAngles(pt, prevPt, nextPt) === 0) continue; + points.push(pt); + prevPt = pt; } - }, { - key: "isExteriorRing", - value: function isExteriorRing() { - if (this._isExteriorRing === undefined) { - var enclosing = this.enclosingRing(); - this._isExteriorRing = enclosing ? !enclosing.isExteriorRing() : true; - } - return this._isExteriorRing; + // ring was all (within rounding error of angle calc) colinear points + if (points.length === 1) return null; + + // check if the starting point is necessary + const pt = points[0]; + const nextPt = points[1]; + if (compareVectorAngles(pt, prevPt, nextPt) === 0) points.shift(); + points.push(points[0]); + const step = this.isExteriorRing() ? 1 : -1; + const iStart = this.isExteriorRing() ? 0 : points.length - 1; + const iEnd = this.isExteriorRing() ? points.length : -1; + const orderedPoints = []; + for (let i = iStart; i != iEnd; i += step) orderedPoints.push([points[i].x, points[i].y]); + return orderedPoints; + } + isExteriorRing() { + if (this._isExteriorRing === undefined) { + const enclosing = this.enclosingRing(); + this._isExteriorRing = enclosing ? !enclosing.isExteriorRing() : true; } - }, { - key: "enclosingRing", - value: function enclosingRing() { - if (this._enclosingRing === undefined) { - this._enclosingRing = this._calcEnclosingRing(); - } + return this._isExteriorRing; + } + enclosingRing() { + if (this._enclosingRing === undefined) { + this._enclosingRing = this._calcEnclosingRing(); + } + return this._enclosingRing; + } - return this._enclosingRing; + /* Returns the ring that encloses this one, if any */ + _calcEnclosingRing() { + // start with the ealier sweep line event so that the prevSeg + // chain doesn't lead us inside of a loop of ours + let leftMostEvt = this.events[0]; + for (let i = 1, iMax = this.events.length; i < iMax; i++) { + const evt = this.events[i]; + if (SweepEvent.compare(leftMostEvt, evt) > 0) leftMostEvt = evt; } - /* Returns the ring that encloses this one, if any */ - - }, { - key: "_calcEnclosingRing", - value: function _calcEnclosingRing() { - // start with the ealier sweep line event so that the prevSeg - // chain doesn't lead us inside of a loop of ours - var leftMostEvt = this.events[0]; - - for (var i = 1, iMax = this.events.length; i < iMax; i++) { - var evt = this.events[i]; - if (SweepEvent.compare(leftMostEvt, evt) > 0) leftMostEvt = evt; + let prevSeg = leftMostEvt.segment.prevInResult(); + let prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; + while (true) { + // no segment found, thus no ring can enclose us + if (!prevSeg) return null; + + // no segments below prev segment found, thus the ring of the prev + // segment must loop back around and enclose us + if (!prevPrevSeg) return prevSeg.ringOut; + + // if the two segments are of different rings, the ring of the prev + // segment must either loop around us or the ring of the prev prev + // seg, which would make us and the ring of the prev peers + if (prevPrevSeg.ringOut !== prevSeg.ringOut) { + if (prevPrevSeg.ringOut.enclosingRing() !== prevSeg.ringOut) { + return prevSeg.ringOut; + } else return prevSeg.ringOut.enclosingRing(); } - var prevSeg = leftMostEvt.segment.prevInResult(); - var prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; - - while (true) { - // no segment found, thus no ring can enclose us - if (!prevSeg) return null; // no segments below prev segment found, thus the ring of the prev - // segment must loop back around and enclose us - - if (!prevPrevSeg) return prevSeg.ringOut; // if the two segments are of different rings, the ring of the prev - // segment must either loop around us or the ring of the prev prev - // seg, which would make us and the ring of the prev peers - - if (prevPrevSeg.ringOut !== prevSeg.ringOut) { - if (prevPrevSeg.ringOut.enclosingRing() !== prevSeg.ringOut) { - return prevSeg.ringOut; - } else return prevSeg.ringOut.enclosingRing(); - } // two segments are from the same ring, so this was a penisula - // of that ring. iterate downward, keep searching - - - prevSeg = prevPrevSeg.prevInResult(); - prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; - } + // two segments are from the same ring, so this was a penisula + // of that ring. iterate downward, keep searching + prevSeg = prevPrevSeg.prevInResult(); + prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; } - }]); - - return RingOut; -}(); -var PolyOut = /*#__PURE__*/function () { - function PolyOut(exteriorRing) { - _classCallCheck(this, PolyOut); - + } +} +class PolyOut { + constructor(exteriorRing) { this.exteriorRing = exteriorRing; exteriorRing.poly = this; this.interiorRings = []; } - - _createClass(PolyOut, [{ - key: "addInterior", - value: function addInterior(ring) { - this.interiorRings.push(ring); - ring.poly = this; - } - }, { - key: "getGeom", - value: function getGeom() { - var geom = [this.exteriorRing.getGeom()]; // exterior ring was all (within rounding error of angle calc) colinear points - - if (geom[0] === null) return null; - - for (var i = 0, iMax = this.interiorRings.length; i < iMax; i++) { - var ringGeom = this.interiorRings[i].getGeom(); // interior ring was all (within rounding error of angle calc) colinear points - - if (ringGeom === null) continue; - geom.push(ringGeom); - } - - return geom; + addInterior(ring) { + this.interiorRings.push(ring); + ring.poly = this; + } + getGeom() { + const geom = [this.exteriorRing.getGeom()]; + // exterior ring was all (within rounding error of angle calc) colinear points + if (geom[0] === null) return null; + for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) { + const ringGeom = this.interiorRings[i].getGeom(); + // interior ring was all (within rounding error of angle calc) colinear points + if (ringGeom === null) continue; + geom.push(ringGeom); } - }]); - - return PolyOut; -}(); -var MultiPolyOut = /*#__PURE__*/function () { - function MultiPolyOut(rings) { - _classCallCheck(this, MultiPolyOut); - + return geom; + } +} +class MultiPolyOut { + constructor(rings) { this.rings = rings; this.polys = this._composePolys(rings); } - - _createClass(MultiPolyOut, [{ - key: "getGeom", - value: function getGeom() { - var geom = []; - - for (var i = 0, iMax = this.polys.length; i < iMax; i++) { - var polyGeom = this.polys[i].getGeom(); // exterior ring was all (within rounding error of angle calc) colinear points - - if (polyGeom === null) continue; - geom.push(polyGeom); - } - - return geom; + getGeom() { + const geom = []; + for (let i = 0, iMax = this.polys.length; i < iMax; i++) { + const polyGeom = this.polys[i].getGeom(); + // exterior ring was all (within rounding error of angle calc) colinear points + if (polyGeom === null) continue; + geom.push(polyGeom); } - }, { - key: "_composePolys", - value: function _composePolys(rings) { - var polys = []; - - for (var i = 0, iMax = rings.length; i < iMax; i++) { - var ring = rings[i]; - if (ring.poly) continue; - if (ring.isExteriorRing()) polys.push(new PolyOut(ring));else { - var enclosingRing = ring.enclosingRing(); - if (!enclosingRing.poly) polys.push(new PolyOut(enclosingRing)); - enclosingRing.poly.addInterior(ring); - } + return geom; + } + _composePolys(rings) { + const polys = []; + for (let i = 0, iMax = rings.length; i < iMax; i++) { + const ring = rings[i]; + if (ring.poly) continue; + if (ring.isExteriorRing()) polys.push(new PolyOut(ring));else { + const enclosingRing = ring.enclosingRing(); + if (!enclosingRing.poly) polys.push(new PolyOut(enclosingRing)); + enclosingRing.poly.addInterior(ring); } - - return polys; } - }]); - - return MultiPolyOut; -}(); + return polys; + } +} /** * NOTE: We must be careful not to change any segments while @@ -1491,330 +1261,272 @@ var MultiPolyOut = /*#__PURE__*/function () { * it sometimes does.) */ -var SweepLine = /*#__PURE__*/function () { - function SweepLine(queue) { - var comparator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Segment.compare; - - _classCallCheck(this, SweepLine); - +class SweepLine { + constructor(queue) { + let comparator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Segment.compare; this.queue = queue; this.tree = new SplayTree(comparator); this.segments = []; } + process(event) { + const segment = event.segment; + const newEvents = []; + + // if we've already been consumed by another segment, + // clean up our body parts and get out + if (event.consumedBy) { + if (event.isLeft) this.queue.remove(event.otherSE);else this.tree.remove(segment); + return newEvents; + } + const node = event.isLeft ? this.tree.add(segment) : this.tree.find(segment); + if (!node) throw new Error(`Unable to find segment #${segment.id} ` + `[${segment.leftSE.point.x}, ${segment.leftSE.point.y}] -> ` + `[${segment.rightSE.point.x}, ${segment.rightSE.point.y}] ` + "in SweepLine tree."); + let prevNode = node; + let nextNode = node; + let prevSeg = undefined; + let nextSeg = undefined; + + // skip consumed segments still in tree + while (prevSeg === undefined) { + prevNode = this.tree.prev(prevNode); + if (prevNode === null) prevSeg = null;else if (prevNode.key.consumedBy === undefined) prevSeg = prevNode.key; + } - _createClass(SweepLine, [{ - key: "process", - value: function process(event) { - var segment = event.segment; - var newEvents = []; // if we've already been consumed by another segment, - // clean up our body parts and get out - - if (event.consumedBy) { - if (event.isLeft) this.queue.remove(event.otherSE);else this.tree.remove(segment); - return newEvents; - } - - var node = event.isLeft ? this.tree.insert(segment) : this.tree.find(segment); - if (!node) throw new Error("Unable to find segment #".concat(segment.id, " ") + "[".concat(segment.leftSE.point.x, ", ").concat(segment.leftSE.point.y, "] -> ") + "[".concat(segment.rightSE.point.x, ", ").concat(segment.rightSE.point.y, "] ") + 'in SweepLine tree. Please submit a bug report.'); - var prevNode = node; - var nextNode = node; - var prevSeg = undefined; - var nextSeg = undefined; // skip consumed segments still in tree - - while (prevSeg === undefined) { - prevNode = this.tree.prev(prevNode); - if (prevNode === null) prevSeg = null;else if (prevNode.key.consumedBy === undefined) prevSeg = prevNode.key; - } // skip consumed segments still in tree - - - while (nextSeg === undefined) { - nextNode = this.tree.next(nextNode); - if (nextNode === null) nextSeg = null;else if (nextNode.key.consumedBy === undefined) nextSeg = nextNode.key; - } - - if (event.isLeft) { - // Check for intersections against the previous segment in the sweep line - var prevMySplitter = null; - - if (prevSeg) { - var prevInter = prevSeg.getIntersection(segment); - - if (prevInter !== null) { - if (!segment.isAnEndpoint(prevInter)) prevMySplitter = prevInter; - - if (!prevSeg.isAnEndpoint(prevInter)) { - var newEventsFromSplit = this._splitSafely(prevSeg, prevInter); - - for (var i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { - newEvents.push(newEventsFromSplit[i]); - } + // skip consumed segments still in tree + while (nextSeg === undefined) { + nextNode = this.tree.next(nextNode); + if (nextNode === null) nextSeg = null;else if (nextNode.key.consumedBy === undefined) nextSeg = nextNode.key; + } + if (event.isLeft) { + // Check for intersections against the previous segment in the sweep line + let prevMySplitter = null; + if (prevSeg) { + const prevInter = prevSeg.getIntersection(segment); + if (prevInter !== null) { + if (!segment.isAnEndpoint(prevInter)) prevMySplitter = prevInter; + if (!prevSeg.isAnEndpoint(prevInter)) { + const newEventsFromSplit = this._splitSafely(prevSeg, prevInter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } } - } // Check for intersections against the next segment in the sweep line - - - var nextMySplitter = null; - - if (nextSeg) { - var nextInter = nextSeg.getIntersection(segment); - - if (nextInter !== null) { - if (!segment.isAnEndpoint(nextInter)) nextMySplitter = nextInter; - - if (!nextSeg.isAnEndpoint(nextInter)) { - var _newEventsFromSplit = this._splitSafely(nextSeg, nextInter); + } + } - for (var _i = 0, _iMax = _newEventsFromSplit.length; _i < _iMax; _i++) { - newEvents.push(_newEventsFromSplit[_i]); - } + // Check for intersections against the next segment in the sweep line + let nextMySplitter = null; + if (nextSeg) { + const nextInter = nextSeg.getIntersection(segment); + if (nextInter !== null) { + if (!segment.isAnEndpoint(nextInter)) nextMySplitter = nextInter; + if (!nextSeg.isAnEndpoint(nextInter)) { + const newEventsFromSplit = this._splitSafely(nextSeg, nextInter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } } - } // For simplicity, even if we find more than one intersection we only - // spilt on the 'earliest' (sweep-line style) of the intersections. - // The other intersection will be handled in a future process(). - - - if (prevMySplitter !== null || nextMySplitter !== null) { - var mySplitter = null; - if (prevMySplitter === null) mySplitter = nextMySplitter;else if (nextMySplitter === null) mySplitter = prevMySplitter;else { - var cmpSplitters = SweepEvent.comparePoints(prevMySplitter, nextMySplitter); - mySplitter = cmpSplitters <= 0 ? prevMySplitter : nextMySplitter; - } // Rounding errors can cause changes in ordering, - // so remove afected segments and right sweep events before splitting - - this.queue.remove(segment.rightSE); - newEvents.push(segment.rightSE); - - var _newEventsFromSplit2 = segment.split(mySplitter); + } + } - for (var _i2 = 0, _iMax2 = _newEventsFromSplit2.length; _i2 < _iMax2; _i2++) { - newEvents.push(_newEventsFromSplit2[_i2]); - } + // For simplicity, even if we find more than one intersection we only + // spilt on the 'earliest' (sweep-line style) of the intersections. + // The other intersection will be handled in a future process(). + if (prevMySplitter !== null || nextMySplitter !== null) { + let mySplitter = null; + if (prevMySplitter === null) mySplitter = nextMySplitter;else if (nextMySplitter === null) mySplitter = prevMySplitter;else { + const cmpSplitters = SweepEvent.comparePoints(prevMySplitter, nextMySplitter); + mySplitter = cmpSplitters <= 0 ? prevMySplitter : nextMySplitter; } - if (newEvents.length > 0) { - // We found some intersections, so re-do the current event to - // make sure sweep line ordering is totally consistent for later - // use with the segment 'prev' pointers - this.tree.remove(segment); - newEvents.push(event); - } else { - // done with left event - this.segments.push(segment); - segment.prev = prevSeg; + // Rounding errors can cause changes in ordering, + // so remove afected segments and right sweep events before splitting + this.queue.remove(segment.rightSE); + newEvents.push(segment.rightSE); + const newEventsFromSplit = segment.split(mySplitter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } + } + if (newEvents.length > 0) { + // We found some intersections, so re-do the current event to + // make sure sweep line ordering is totally consistent for later + // use with the segment 'prev' pointers + this.tree.remove(segment); + newEvents.push(event); } else { - // event.isRight - // since we're about to be removed from the sweep line, check for - // intersections between our previous and next segments - if (prevSeg && nextSeg) { - var inter = prevSeg.getIntersection(nextSeg); - - if (inter !== null) { - if (!prevSeg.isAnEndpoint(inter)) { - var _newEventsFromSplit3 = this._splitSafely(prevSeg, inter); - - for (var _i3 = 0, _iMax3 = _newEventsFromSplit3.length; _i3 < _iMax3; _i3++) { - newEvents.push(_newEventsFromSplit3[_i3]); - } + // done with left event + this.segments.push(segment); + segment.prev = prevSeg; + } + } else { + // event.isRight + + // since we're about to be removed from the sweep line, check for + // intersections between our previous and next segments + if (prevSeg && nextSeg) { + const inter = prevSeg.getIntersection(nextSeg); + if (inter !== null) { + if (!prevSeg.isAnEndpoint(inter)) { + const newEventsFromSplit = this._splitSafely(prevSeg, inter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } - - if (!nextSeg.isAnEndpoint(inter)) { - var _newEventsFromSplit4 = this._splitSafely(nextSeg, inter); - - for (var _i4 = 0, _iMax4 = _newEventsFromSplit4.length; _i4 < _iMax4; _i4++) { - newEvents.push(_newEventsFromSplit4[_i4]); - } + } + if (!nextSeg.isAnEndpoint(inter)) { + const newEventsFromSplit = this._splitSafely(nextSeg, inter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } } } - - this.tree.remove(segment); } - - return newEvents; - } - /* Safely split a segment that is currently in the datastructures - * IE - a segment other than the one that is currently being processed. */ - - }, { - key: "_splitSafely", - value: function _splitSafely(seg, pt) { - // Rounding errors can cause changes in ordering, - // so remove afected segments and right sweep events before splitting - // removeNode() doesn't work, so have re-find the seg - // https://github.com/w8r/splay-tree/pull/5 - this.tree.remove(seg); - var rightSE = seg.rightSE; - this.queue.remove(rightSE); - var newEvents = seg.split(pt); - newEvents.push(rightSE); // splitting can trigger consumption - - if (seg.consumedBy === undefined) this.tree.insert(seg); - return newEvents; + this.tree.remove(segment); } - }]); - - return SweepLine; -}(); - -var POLYGON_CLIPPING_MAX_QUEUE_SIZE = typeof process !== 'undefined' && process.env.POLYGON_CLIPPING_MAX_QUEUE_SIZE || 1000000; -var POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS = typeof process !== 'undefined' && process.env.POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS || 1000000; -var Operation = /*#__PURE__*/function () { - function Operation() { - _classCallCheck(this, Operation); + return newEvents; } - _createClass(Operation, [{ - key: "run", - value: function run(type, geom, moreGeoms) { - operation.type = type; - rounder.reset(); - /* Convert inputs to MultiPoly objects */ - - var multipolys = [new MultiPolyIn(geom, true)]; - - for (var i = 0, iMax = moreGeoms.length; i < iMax; i++) { - multipolys.push(new MultiPolyIn(moreGeoms[i], false)); - } - - operation.numMultiPolys = multipolys.length; - /* BBox optimization for difference operation - * If the bbox of a multipolygon that's part of the clipping doesn't - * intersect the bbox of the subject at all, we can just drop that - * multiploygon. */ - - if (operation.type === 'difference') { - // in place removal - var subject = multipolys[0]; - var _i = 1; - - while (_i < multipolys.length) { - if (getBboxOverlap(multipolys[_i].bbox, subject.bbox) !== null) _i++;else multipolys.splice(_i, 1); - } - } - /* BBox optimization for intersection operation - * If we can find any pair of multipolygons whose bbox does not overlap, - * then the result will be empty. */ - - - if (operation.type === 'intersection') { - // TODO: this is O(n^2) in number of polygons. By sorting the bboxes, - // it could be optimized to O(n * ln(n)) - for (var _i2 = 0, _iMax = multipolys.length; _i2 < _iMax; _i2++) { - var mpA = multipolys[_i2]; + /* Safely split a segment that is currently in the datastructures + * IE - a segment other than the one that is currently being processed. */ + _splitSafely(seg, pt) { + // Rounding errors can cause changes in ordering, + // so remove afected segments and right sweep events before splitting + // removeNode() doesn't work, so have re-find the seg + // https://github.com/w8r/splay-tree/pull/5 + this.tree.remove(seg); + const rightSE = seg.rightSE; + this.queue.remove(rightSE); + const newEvents = seg.split(pt); + newEvents.push(rightSE); + // splitting can trigger consumption + if (seg.consumedBy === undefined) this.tree.add(seg); + return newEvents; + } +} - for (var j = _i2 + 1, jMax = multipolys.length; j < jMax; j++) { - if (getBboxOverlap(mpA.bbox, multipolys[j].bbox) === null) return []; - } - } +// Limits on iterative processes to prevent infinite loops - usually caused by floating-point math round-off errors. +const POLYGON_CLIPPING_MAX_QUEUE_SIZE = typeof process !== "undefined" && process.env.POLYGON_CLIPPING_MAX_QUEUE_SIZE || 1000000; +const POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS = typeof process !== "undefined" && process.env.POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS || 1000000; +class Operation { + run(type, geom, moreGeoms) { + operation.type = type; + rounder.reset(); + + /* Convert inputs to MultiPoly objects */ + const multipolys = [new MultiPolyIn(geom, true)]; + for (let i = 0, iMax = moreGeoms.length; i < iMax; i++) { + multipolys.push(new MultiPolyIn(moreGeoms[i], false)); + } + operation.numMultiPolys = multipolys.length; + + /* BBox optimization for difference operation + * If the bbox of a multipolygon that's part of the clipping doesn't + * intersect the bbox of the subject at all, we can just drop that + * multiploygon. */ + if (operation.type === "difference") { + // in place removal + const subject = multipolys[0]; + let i = 1; + while (i < multipolys.length) { + if (getBboxOverlap(multipolys[i].bbox, subject.bbox) !== null) i++;else multipolys.splice(i, 1); } - /* Put segment endpoints in a priority queue */ - - - var queue = new SplayTree(SweepEvent.compare); - - for (var _i3 = 0, _iMax2 = multipolys.length; _i3 < _iMax2; _i3++) { - var sweepEvents = multipolys[_i3].getSweepEvents(); - - for (var _j = 0, _jMax = sweepEvents.length; _j < _jMax; _j++) { - queue.insert(sweepEvents[_j]); + } - if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) { - // prevents an infinite loop, an otherwise common manifestation of bugs - throw new Error('Infinite loop when putting segment endpoints in a priority queue ' + '(queue size too big). Please file a bug report.'); - } + /* BBox optimization for intersection operation + * If we can find any pair of multipolygons whose bbox does not overlap, + * then the result will be empty. */ + if (operation.type === "intersection") { + // TODO: this is O(n^2) in number of polygons. By sorting the bboxes, + // it could be optimized to O(n * ln(n)) + for (let i = 0, iMax = multipolys.length; i < iMax; i++) { + const mpA = multipolys[i]; + for (let j = i + 1, jMax = multipolys.length; j < jMax; j++) { + if (getBboxOverlap(mpA.bbox, multipolys[j].bbox) === null) return []; } } - /* Pass the sweep line over those endpoints */ - - - var sweepLine = new SweepLine(queue); - var prevQueueSize = queue.size; - var node = queue.pop(); - - while (node) { - var evt = node.key; - - if (queue.size === prevQueueSize) { - // prevents an infinite loop, an otherwise common manifestation of bugs - var seg = evt.segment; - throw new Error("Unable to pop() ".concat(evt.isLeft ? 'left' : 'right', " SweepEvent ") + "[".concat(evt.point.x, ", ").concat(evt.point.y, "] from segment #").concat(seg.id, " ") + "[".concat(seg.leftSE.point.x, ", ").concat(seg.leftSE.point.y, "] -> ") + "[".concat(seg.rightSE.point.x, ", ").concat(seg.rightSE.point.y, "] from queue. ") + 'Please file a bug report.'); - } + } + /* Put segment endpoints in a priority queue */ + const queue = new SplayTree(SweepEvent.compare); + for (let i = 0, iMax = multipolys.length; i < iMax; i++) { + const sweepEvents = multipolys[i].getSweepEvents(); + for (let j = 0, jMax = sweepEvents.length; j < jMax; j++) { + queue.insert(sweepEvents[j]); if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) { // prevents an infinite loop, an otherwise common manifestation of bugs - throw new Error('Infinite loop when passing sweep line over endpoints ' + '(queue size too big). Please file a bug report.'); - } - - if (sweepLine.segments.length > POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS) { - // prevents an infinite loop, an otherwise common manifestation of bugs - throw new Error('Infinite loop when passing sweep line over endpoints ' + '(too many sweep line segments). Please file a bug report.'); + throw new Error("Infinite loop when putting segment endpoints in a priority queue " + "(queue size too big)."); } + } + } - var newEvents = sweepLine.process(evt); - - for (var _i4 = 0, _iMax3 = newEvents.length; _i4 < _iMax3; _i4++) { - var _evt = newEvents[_i4]; - if (_evt.consumedBy === undefined) queue.insert(_evt); - } - - prevQueueSize = queue.size; - node = queue.pop(); - } // free some memory we don't need anymore - - - rounder.reset(); - /* Collect and compile segments we're keeping into a multipolygon */ - - var ringsOut = RingOut.factory(sweepLine.segments); - var result = new MultiPolyOut(ringsOut); - return result.getGeom(); + /* Pass the sweep line over those endpoints */ + const sweepLine = new SweepLine(queue); + let prevQueueSize = queue.size; + let node = queue.pop(); + while (node) { + const evt = node.key; + if (queue.size === prevQueueSize) { + // prevents an infinite loop, an otherwise common manifestation of bugs + const seg = evt.segment; + throw new Error(`Unable to pop() ${evt.isLeft ? "left" : "right"} SweepEvent ` + `[${evt.point.x}, ${evt.point.y}] from segment #${seg.id} ` + `[${seg.leftSE.point.x}, ${seg.leftSE.point.y}] -> ` + `[${seg.rightSE.point.x}, ${seg.rightSE.point.y}] from queue.`); + } + if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) { + // prevents an infinite loop, an otherwise common manifestation of bugs + throw new Error("Infinite loop when passing sweep line over endpoints " + "(queue size too big)."); + } + if (sweepLine.segments.length > POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS) { + // prevents an infinite loop, an otherwise common manifestation of bugs + throw new Error("Infinite loop when passing sweep line over endpoints " + "(too many sweep line segments)."); + } + const newEvents = sweepLine.process(evt); + for (let i = 0, iMax = newEvents.length; i < iMax; i++) { + const evt = newEvents[i]; + if (evt.consumedBy === undefined) queue.insert(evt); + } + prevQueueSize = queue.size; + node = queue.pop(); } - }]); - return Operation; -}(); // singleton available by import + // free some memory we don't need anymore + rounder.reset(); -var operation = new Operation(); + /* Collect and compile segments we're keeping into a multipolygon */ + const ringsOut = RingOut.factory(sweepLine.segments); + const result = new MultiPolyOut(ringsOut); + return result.getGeom(); + } +} + +// singleton available by import +const operation = new Operation(); -var union = function union(geom) { +const union = function (geom) { for (var _len = arguments.length, moreGeoms = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { moreGeoms[_key - 1] = arguments[_key]; } - - return operation.run('union', geom, moreGeoms); + return operation.run("union", geom, moreGeoms); }; - -var intersection$1 = function intersection(geom) { +const intersection = function (geom) { for (var _len2 = arguments.length, moreGeoms = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { moreGeoms[_key2 - 1] = arguments[_key2]; } - - return operation.run('intersection', geom, moreGeoms); + return operation.run("intersection", geom, moreGeoms); }; - -var xor = function xor(geom) { +const xor = function (geom) { for (var _len3 = arguments.length, moreGeoms = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { moreGeoms[_key3 - 1] = arguments[_key3]; } - - return operation.run('xor', geom, moreGeoms); + return operation.run("xor", geom, moreGeoms); }; - -var difference = function difference(subjectGeom) { +const difference = function (subjectGeom) { for (var _len4 = arguments.length, clippingGeoms = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) { clippingGeoms[_key4 - 1] = arguments[_key4]; } - - return operation.run('difference', subjectGeom, clippingGeoms); + return operation.run("difference", subjectGeom, clippingGeoms); }; - var index = { union: union, - intersection: intersection$1, + intersection: intersection, xor: xor, difference: difference }; -export default index; +export { index as default }; diff --git a/dist/polygon-clipping.umd.js b/dist/polygon-clipping.umd.js index 8840fcc..98c3d0e 100644 --- a/dist/polygon-clipping.umd.js +++ b/dist/polygon-clipping.umd.js @@ -1,1115 +1,1279 @@ (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.polygonClipping = factory()); -}(this, (function () { 'use strict'; - - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; - } - - /** - * splaytree v3.1.0 - * Fast Splay tree for Node and browser - * - * @author Alexander Milevski - * @license MIT - * @preserve - */ - var Node = - /** @class */ - function () { - function Node(key, data) { - this.next = null; - this.key = key; - this.data = data; - this.left = null; - this.right = null; - } - - return Node; - }(); - /* follows "An implementation of top-down splaying" - * by D. Sleator March 1992 - */ - - - function DEFAULT_COMPARE(a, b) { - return a > b ? 1 : a < b ? -1 : 0; - } - /** - * Simple top down splay, not requiring i to be in the tree t. - */ - - - function splay(i, t, comparator) { - var N = new Node(null, null); - var l = N; - var r = N; - - while (true) { - var cmp = comparator(i, t.key); //if (i < t.key) { - - if (cmp < 0) { - if (t.left === null) break; //if (i < t.left.key) { - - if (comparator(i, t.left.key) < 0) { - var y = t.left; - /* rotate right */ - - t.left = y.right; - y.right = t; - t = y; - if (t.left === null) break; - } - - r.left = t; - /* link right */ - - r = t; - t = t.left; //} else if (i > t.key) { - } else if (cmp > 0) { - if (t.right === null) break; //if (i > t.right.key) { - - if (comparator(i, t.right.key) > 0) { - var y = t.right; - /* rotate left */ - - t.right = y.left; - y.left = t; - t = y; - if (t.right === null) break; - } - - l.right = t; - /* link left */ - - l = t; - t = t.right; - } else break; - } - /* assemble */ - - - l.right = t.left; - r.left = t.right; - t.left = N.right; - t.right = N.left; - return t; - } - - function insert(i, data, t, comparator) { - var node = new Node(i, data); - - if (t === null) { - node.left = node.right = null; - return node; - } - - t = splay(i, t, comparator); - var cmp = comparator(i, t.key); - - if (cmp < 0) { - node.left = t.left; - node.right = t; - t.left = null; - } else if (cmp >= 0) { - node.right = t.right; - node.left = t; - t.right = null; - } - - return node; - } - - function split(key, v, comparator) { - var left = null; - var right = null; - - if (v) { - v = splay(key, v, comparator); - var cmp = comparator(v.key, key); + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.polygonClipping = factory()); +})(this, (function () { 'use strict'; + + /** + * splaytree v3.1.2 + * Fast Splay tree for Node and browser + * + * @author Alexander Milevski + * @license MIT + * @preserve + */ - if (cmp === 0) { - left = v.left; - right = v.right; - } else if (cmp < 0) { - right = v.right; - v.right = null; - left = v; - } else { - left = v.left; - v.left = null; - right = v; + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ + + function __generator(thisArg, body) { + var _ = { + label: 0, + sent: function () { + if (t[0] & 1) throw t[1]; + return t[1]; + }, + trys: [], + ops: [] + }, + f, + y, + t, + g; + return g = { + next: verb(0), + "throw": verb(1), + "return": verb(2) + }, typeof Symbol === "function" && (g[Symbol.iterator] = function () { + return this; + }), g; + function verb(n) { + return function (v) { + return step([n, v]); + }; } - } - - return { - left: left, - right: right - }; - } - - function merge(left, right, comparator) { - if (right === null) return left; - if (left === null) return right; - right = splay(left.key, right, comparator); - right.left = left; - return right; - } - /** - * Prints level of the tree - */ - - - function printRow(root, prefix, isTail, out, printNode) { - if (root) { - out("" + prefix + (isTail ? '└── ' : '├── ') + printNode(root) + "\n"); - var indent = prefix + (isTail ? ' ' : '│ '); - if (root.left) printRow(root.left, indent, false, out, printNode); - if (root.right) printRow(root.right, indent, true, out, printNode); - } - } - - var Tree = - /** @class */ - function () { - function Tree(comparator) { - if (comparator === void 0) { - comparator = DEFAULT_COMPARE; + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: + case 1: + t = op; + break; + case 4: + _.label++; + return { + value: op[1], + done: false + }; + case 5: + _.label++; + y = op[1]; + op = [0]; + continue; + case 7: + op = _.ops.pop(); + _.trys.pop(); + continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { + _ = 0; + continue; + } + if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) { + _.label = op[1]; + break; + } + if (op[0] === 6 && _.label < t[1]) { + _.label = t[1]; + t = op; + break; + } + if (t && _.label < t[2]) { + _.label = t[2]; + _.ops.push(op); + break; + } + if (t[2]) _.ops.pop(); + _.trys.pop(); + continue; + } + op = body.call(thisArg, _); + } catch (e) { + op = [6, e]; + y = 0; + } finally { + f = t = 0; + } + if (op[0] & 5) throw op[1]; + return { + value: op[0] ? op[1] : void 0, + done: true + }; } - - this._root = null; - this._size = 0; - this._comparator = comparator; } - /** - * Inserts a key, allows duplicates + var Node = /** @class */function () { + function Node(key, data) { + this.next = null; + this.key = key; + this.data = data; + this.left = null; + this.right = null; + } + return Node; + }(); + + /* follows "An implementation of top-down splaying" + * by D. Sleator March 1992 */ - - - Tree.prototype.insert = function (key, data) { - this._size++; - return this._root = insert(key, data, this._root, this._comparator); - }; + function DEFAULT_COMPARE(a, b) { + return a > b ? 1 : a < b ? -1 : 0; + } /** - * Adds a key, if it is not present in the tree + * Simple top down splay, not requiring i to be in the tree t. */ - - - Tree.prototype.add = function (key, data) { - var node = new Node(key, data); - - if (this._root === null) { - node.left = node.right = null; - this._size++; - this._root = node; - } - - var comparator = this._comparator; - var t = splay(key, this._root, comparator); - var cmp = comparator(key, t.key); - if (cmp === 0) this._root = t;else { + function splay(i, t, comparator) { + var N = new Node(null, null); + var l = N; + var r = N; + while (true) { + var cmp = comparator(i, t.key); + //if (i < t.key) { if (cmp < 0) { - node.left = t.left; - node.right = t; - t.left = null; + if (t.left === null) break; + //if (i < t.left.key) { + if (comparator(i, t.left.key) < 0) { + var y = t.left; /* rotate right */ + t.left = y.right; + y.right = t; + t = y; + if (t.left === null) break; + } + r.left = t; /* link right */ + r = t; + t = t.left; + //} else if (i > t.key) { } else if (cmp > 0) { - node.right = t.right; - node.left = t; - t.right = null; - } - - this._size++; - this._root = node; + if (t.right === null) break; + //if (i > t.right.key) { + if (comparator(i, t.right.key) > 0) { + var y = t.right; /* rotate left */ + t.right = y.left; + y.left = t; + t = y; + if (t.right === null) break; + } + l.right = t; /* link left */ + l = t; + t = t.right; + } else break; + } + /* assemble */ + l.right = t.left; + r.left = t.right; + t.left = N.right; + t.right = N.left; + return t; + } + function insert(i, data, t, comparator) { + var node = new Node(i, data); + if (t === null) { + node.left = node.right = null; + return node; } - return this._root; - }; - /** - * @param {Key} key - * @return {Node|null} - */ - - - Tree.prototype.remove = function (key) { - this._root = this._remove(key, this._root, this._comparator); - }; - /** - * Deletes i from the tree if it's there - */ - - - Tree.prototype._remove = function (i, t, comparator) { - var x; - if (t === null) return null; t = splay(i, t, comparator); var cmp = comparator(i, t.key); - - if (cmp === 0) { - /* found it */ - if (t.left === null) { - x = t.right; - } else { - x = splay(i, t.left, comparator); - x.right = t.right; - } - - this._size--; - return x; + if (cmp < 0) { + node.left = t.left; + node.right = t; + t.left = null; + } else if (cmp >= 0) { + node.right = t.right; + node.left = t; + t.right = null; } - - return t; - /* It wasn't there */ - }; - /** - * Removes and returns the node with smallest key - */ - - - Tree.prototype.pop = function () { - var node = this._root; - - if (node) { - while (node.left) { - node = node.left; + return node; + } + function split(key, v, comparator) { + var left = null; + var right = null; + if (v) { + v = splay(key, v, comparator); + var cmp = comparator(v.key, key); + if (cmp === 0) { + left = v.left; + right = v.right; + } else if (cmp < 0) { + right = v.right; + v.right = null; + left = v; + } else { + left = v.left; + v.left = null; + right = v; } - - this._root = splay(node.key, this._root, this._comparator); - this._root = this._remove(node.key, this._root, this._comparator); - return { - key: node.key, - data: node.data - }; } - - return null; - }; + return { + left: left, + right: right + }; + } + function merge(left, right, comparator) { + if (right === null) return left; + if (left === null) return right; + right = splay(left.key, right, comparator); + right.left = left; + return right; + } /** - * Find without splaying + * Prints level of the tree */ - - - Tree.prototype.findStatic = function (key) { - var current = this._root; - var compare = this._comparator; - - while (current) { - var cmp = compare(key, current.key); - if (cmp === 0) return current;else if (cmp < 0) current = current.left;else current = current.right; + function printRow(root, prefix, isTail, out, printNode) { + if (root) { + out("" + prefix + (isTail ? '└── ' : '├── ') + printNode(root) + "\n"); + var indent = prefix + (isTail ? ' ' : '│ '); + if (root.left) printRow(root.left, indent, false, out, printNode); + if (root.right) printRow(root.right, indent, true, out, printNode); } - - return null; - }; - - Tree.prototype.find = function (key) { - if (this._root) { - this._root = splay(key, this._root, this._comparator); - if (this._comparator(key, this._root.key) !== 0) return null; - } - - return this._root; - }; - - Tree.prototype.contains = function (key) { - var current = this._root; - var compare = this._comparator; - - while (current) { - var cmp = compare(key, current.key); - if (cmp === 0) return true;else if (cmp < 0) current = current.left;else current = current.right; - } - - return false; - }; - - Tree.prototype.forEach = function (visitor, ctx) { - var current = this._root; - var Q = []; - /* Initialize stack s */ - - var done = false; - - while (!done) { - if (current !== null) { - Q.push(current); - current = current.left; - } else { - if (Q.length !== 0) { - current = Q.pop(); - visitor.call(ctx, current); - current = current.right; - } else done = true; + } + var Tree = /** @class */function () { + function Tree(comparator) { + if (comparator === void 0) { + comparator = DEFAULT_COMPARE; } + this._root = null; + this._size = 0; + this._comparator = comparator; } - - return this; - }; - /** - * Walk key range from `low` to `high`. Stops if `fn` returns a value. - */ - - - Tree.prototype.range = function (low, high, fn, ctx) { - var Q = []; - var compare = this._comparator; - var node = this._root; - var cmp; - - while (Q.length !== 0 || node) { + /** + * Inserts a key, allows duplicates + */ + Tree.prototype.insert = function (key, data) { + this._size++; + return this._root = insert(key, data, this._root, this._comparator); + }; + /** + * Adds a key, if it is not present in the tree + */ + Tree.prototype.add = function (key, data) { + var node = new Node(key, data); + if (this._root === null) { + node.left = node.right = null; + this._size++; + this._root = node; + } + var comparator = this._comparator; + var t = splay(key, this._root, comparator); + var cmp = comparator(key, t.key); + if (cmp === 0) this._root = t;else { + if (cmp < 0) { + node.left = t.left; + node.right = t; + t.left = null; + } else if (cmp > 0) { + node.right = t.right; + node.left = t; + t.right = null; + } + this._size++; + this._root = node; + } + return this._root; + }; + /** + * @param {Key} key + * @return {Node|null} + */ + Tree.prototype.remove = function (key) { + this._root = this._remove(key, this._root, this._comparator); + }; + /** + * Deletes i from the tree if it's there + */ + Tree.prototype._remove = function (i, t, comparator) { + var x; + if (t === null) return null; + t = splay(i, t, comparator); + var cmp = comparator(i, t.key); + if (cmp === 0) { + /* found it */ + if (t.left === null) { + x = t.right; + } else { + x = splay(i, t.left, comparator); + x.right = t.right; + } + this._size--; + return x; + } + return t; /* It wasn't there */ + }; + /** + * Removes and returns the node with smallest key + */ + Tree.prototype.pop = function () { + var node = this._root; if (node) { - Q.push(node); - node = node.left; - } else { - node = Q.pop(); - cmp = compare(node.key, high); - - if (cmp > 0) { - break; - } else if (compare(node.key, low) >= 0) { - if (fn.call(ctx, node)) return this; // stop if smth is returned + while (node.left) node = node.left; + this._root = splay(node.key, this._root, this._comparator); + this._root = this._remove(node.key, this._root, this._comparator); + return { + key: node.key, + data: node.data + }; + } + return null; + }; + /** + * Find without splaying + */ + Tree.prototype.findStatic = function (key) { + var current = this._root; + var compare = this._comparator; + while (current) { + var cmp = compare(key, current.key); + if (cmp === 0) return current;else if (cmp < 0) current = current.left;else current = current.right; + } + return null; + }; + Tree.prototype.find = function (key) { + if (this._root) { + this._root = splay(key, this._root, this._comparator); + if (this._comparator(key, this._root.key) !== 0) return null; + } + return this._root; + }; + Tree.prototype.contains = function (key) { + var current = this._root; + var compare = this._comparator; + while (current) { + var cmp = compare(key, current.key); + if (cmp === 0) return true;else if (cmp < 0) current = current.left;else current = current.right; + } + return false; + }; + Tree.prototype.forEach = function (visitor, ctx) { + var current = this._root; + var Q = []; /* Initialize stack s */ + var done = false; + while (!done) { + if (current !== null) { + Q.push(current); + current = current.left; + } else { + if (Q.length !== 0) { + current = Q.pop(); + visitor.call(ctx, current); + current = current.right; + } else done = true; } - - node = node.right; } - } - - return this; - }; - /** - * Returns array of keys - */ - - - Tree.prototype.keys = function () { - var keys = []; - this.forEach(function (_a) { - var key = _a.key; - return keys.push(key); + return this; + }; + /** + * Walk key range from `low` to `high`. Stops if `fn` returns a value. + */ + Tree.prototype.range = function (low, high, fn, ctx) { + var Q = []; + var compare = this._comparator; + var node = this._root; + var cmp; + while (Q.length !== 0 || node) { + if (node) { + Q.push(node); + node = node.left; + } else { + node = Q.pop(); + cmp = compare(node.key, high); + if (cmp > 0) { + break; + } else if (compare(node.key, low) >= 0) { + if (fn.call(ctx, node)) return this; // stop if smth is returned + } + node = node.right; + } + } + return this; + }; + /** + * Returns array of keys + */ + Tree.prototype.keys = function () { + var keys = []; + this.forEach(function (_a) { + var key = _a.key; + return keys.push(key); + }); + return keys; + }; + /** + * Returns array of all the data in the nodes + */ + Tree.prototype.values = function () { + var values = []; + this.forEach(function (_a) { + var data = _a.data; + return values.push(data); + }); + return values; + }; + Tree.prototype.min = function () { + if (this._root) return this.minNode(this._root).key; + return null; + }; + Tree.prototype.max = function () { + if (this._root) return this.maxNode(this._root).key; + return null; + }; + Tree.prototype.minNode = function (t) { + if (t === void 0) { + t = this._root; + } + if (t) while (t.left) t = t.left; + return t; + }; + Tree.prototype.maxNode = function (t) { + if (t === void 0) { + t = this._root; + } + if (t) while (t.right) t = t.right; + return t; + }; + /** + * Returns node at given index + */ + Tree.prototype.at = function (index) { + var current = this._root; + var done = false; + var i = 0; + var Q = []; + while (!done) { + if (current) { + Q.push(current); + current = current.left; + } else { + if (Q.length > 0) { + current = Q.pop(); + if (i === index) return current; + i++; + current = current.right; + } else done = true; + } + } + return null; + }; + Tree.prototype.next = function (d) { + var root = this._root; + var successor = null; + if (d.right) { + successor = d.right; + while (successor.left) successor = successor.left; + return successor; + } + var comparator = this._comparator; + while (root) { + var cmp = comparator(d.key, root.key); + if (cmp === 0) break;else if (cmp < 0) { + successor = root; + root = root.left; + } else root = root.right; + } + return successor; + }; + Tree.prototype.prev = function (d) { + var root = this._root; + var predecessor = null; + if (d.left !== null) { + predecessor = d.left; + while (predecessor.right) predecessor = predecessor.right; + return predecessor; + } + var comparator = this._comparator; + while (root) { + var cmp = comparator(d.key, root.key); + if (cmp === 0) break;else if (cmp < 0) root = root.left;else { + predecessor = root; + root = root.right; + } + } + return predecessor; + }; + Tree.prototype.clear = function () { + this._root = null; + this._size = 0; + return this; + }; + Tree.prototype.toList = function () { + return toList(this._root); + }; + /** + * Bulk-load items. Both array have to be same size + */ + Tree.prototype.load = function (keys, values, presort) { + if (values === void 0) { + values = []; + } + if (presort === void 0) { + presort = false; + } + var size = keys.length; + var comparator = this._comparator; + // sort if needed + if (presort) sort(keys, values, 0, size - 1, comparator); + if (this._root === null) { + // empty tree + this._root = loadRecursive(keys, values, 0, size); + this._size = size; + } else { + // that re-builds the whole tree from two in-order traversals + var mergedList = mergeLists(this.toList(), createList(keys, values), comparator); + size = this._size + size; + this._root = sortedListToBST({ + head: mergedList + }, 0, size); + } + return this; + }; + Tree.prototype.isEmpty = function () { + return this._root === null; + }; + Object.defineProperty(Tree.prototype, "size", { + get: function () { + return this._size; + }, + enumerable: true, + configurable: true }); - return keys; - }; - /** - * Returns array of all the data in the nodes - */ - - - Tree.prototype.values = function () { - var values = []; - this.forEach(function (_a) { - var data = _a.data; - return values.push(data); + Object.defineProperty(Tree.prototype, "root", { + get: function () { + return this._root; + }, + enumerable: true, + configurable: true }); - return values; - }; - - Tree.prototype.min = function () { - if (this._root) return this.minNode(this._root).key; - return null; - }; - - Tree.prototype.max = function () { - if (this._root) return this.maxNode(this._root).key; - return null; - }; - - Tree.prototype.minNode = function (t) { - if (t === void 0) { - t = this._root; - } - - if (t) while (t.left) { - t = t.left; - } - return t; - }; - - Tree.prototype.maxNode = function (t) { - if (t === void 0) { - t = this._root; - } - - if (t) while (t.right) { - t = t.right; + Tree.prototype.toString = function (printNode) { + if (printNode === void 0) { + printNode = function (n) { + return String(n.key); + }; + } + var out = []; + printRow(this._root, '', true, function (v) { + return out.push(v); + }, printNode); + return out.join(''); + }; + Tree.prototype.update = function (key, newKey, newData) { + var comparator = this._comparator; + var _a = split(key, this._root, comparator), + left = _a.left, + right = _a.right; + if (comparator(key, newKey) < 0) { + right = insert(newKey, newData, right, comparator); + } else { + left = insert(newKey, newData, left, comparator); + } + this._root = merge(left, right, comparator); + }; + Tree.prototype.split = function (key) { + return split(key, this._root, this._comparator); + }; + Tree.prototype[Symbol.iterator] = function () { + var current, Q, done; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + current = this._root; + Q = []; + done = false; + _a.label = 1; + case 1: + if (!!done) return [3 /*break*/, 6]; + if (!(current !== null)) return [3 /*break*/, 2]; + Q.push(current); + current = current.left; + return [3 /*break*/, 5]; + case 2: + if (!(Q.length !== 0)) return [3 /*break*/, 4]; + current = Q.pop(); + return [4 /*yield*/, current]; + case 3: + _a.sent(); + current = current.right; + return [3 /*break*/, 5]; + case 4: + done = true; + _a.label = 5; + case 5: + return [3 /*break*/, 1]; + case 6: + return [2 /*return*/]; + } + }); + }; + return Tree; + }(); + function loadRecursive(keys, values, start, end) { + var size = end - start; + if (size > 0) { + var middle = start + Math.floor(size / 2); + var key = keys[middle]; + var data = values[middle]; + var node = new Node(key, data); + node.left = loadRecursive(keys, values, start, middle); + node.right = loadRecursive(keys, values, middle + 1, end); + return node; } - return t; - }; - /** - * Returns node at given index - */ - - - Tree.prototype.at = function (index) { - var current = this._root; - var done = false; - var i = 0; + return null; + } + function createList(keys, values) { + var head = new Node(null, null); + var p = head; + for (var i = 0; i < keys.length; i++) { + p = p.next = new Node(keys[i], values[i]); + } + p.next = null; + return head.next; + } + function toList(root) { + var current = root; var Q = []; - + var done = false; + var head = new Node(null, null); + var p = head; while (!done) { if (current) { Q.push(current); current = current.left; } else { if (Q.length > 0) { - current = Q.pop(); - if (i === index) return current; - i++; + current = p = p.next = Q.pop(); current = current.right; - } else done = true; - } - } - - return null; - }; - - Tree.prototype.next = function (d) { - var root = this._root; - var successor = null; - - if (d.right) { - successor = d.right; - - while (successor.left) { - successor = successor.left; - } - - return successor; - } - - var comparator = this._comparator; - - while (root) { - var cmp = comparator(d.key, root.key); - if (cmp === 0) break;else if (cmp < 0) { - successor = root; - root = root.left; - } else root = root.right; - } - - return successor; - }; - - Tree.prototype.prev = function (d) { - var root = this._root; - var predecessor = null; - - if (d.left !== null) { - predecessor = d.left; - - while (predecessor.right) { - predecessor = predecessor.right; - } - - return predecessor; - } - - var comparator = this._comparator; - - while (root) { - var cmp = comparator(d.key, root.key); - if (cmp === 0) break;else if (cmp < 0) root = root.left;else { - predecessor = root; - root = root.right; - } - } - - return predecessor; - }; - - Tree.prototype.clear = function () { - this._root = null; - this._size = 0; - return this; - }; - - Tree.prototype.toList = function () { - return toList(this._root); - }; - /** - * Bulk-load items. Both array have to be same size - */ - - - Tree.prototype.load = function (keys, values, presort) { - if (values === void 0) { - values = []; - } - - if (presort === void 0) { - presort = false; - } - - var size = keys.length; - var comparator = this._comparator; // sort if needed - - if (presort) sort(keys, values, 0, size - 1, comparator); - - if (this._root === null) { - // empty tree - this._root = loadRecursive(keys, values, 0, size); - this._size = size; - } else { - // that re-builds the whole tree from two in-order traversals - var mergedList = mergeLists(this.toList(), createList(keys, values), comparator); - size = this._size + size; - this._root = sortedListToBST({ - head: mergedList - }, 0, size); - } - - return this; - }; - - Tree.prototype.isEmpty = function () { - return this._root === null; - }; - - Object.defineProperty(Tree.prototype, "size", { - get: function get() { - return this._size; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(Tree.prototype, "root", { - get: function get() { - return this._root; - }, - enumerable: true, - configurable: true - }); - - Tree.prototype.toString = function (printNode) { - if (printNode === void 0) { - printNode = function printNode(n) { - return String(n.key); - }; - } - - var out = []; - printRow(this._root, '', true, function (v) { - return out.push(v); - }, printNode); - return out.join(''); - }; - - Tree.prototype.update = function (key, newKey, newData) { - var comparator = this._comparator; - - var _a = split(key, this._root, comparator), - left = _a.left, - right = _a.right; - - if (comparator(key, newKey) < 0) { - right = insert(newKey, newData, right, comparator); - } else { - left = insert(newKey, newData, left, comparator); - } - - this._root = merge(left, right, comparator); - }; - - Tree.prototype.split = function (key) { - return split(key, this._root, this._comparator); - }; - - return Tree; - }(); - - function loadRecursive(keys, values, start, end) { - var size = end - start; - - if (size > 0) { - var middle = start + Math.floor(size / 2); - var key = keys[middle]; - var data = values[middle]; - var node = new Node(key, data); - node.left = loadRecursive(keys, values, start, middle); - node.right = loadRecursive(keys, values, middle + 1, end); - return node; + } else done = true; + } + } + p.next = null; // that'll work even if the tree was empty + return head.next; } - - return null; - } - - function createList(keys, values) { - var head = new Node(null, null); - var p = head; - - for (var i = 0; i < keys.length; i++) { - p = p.next = new Node(keys[i], values[i]); + function sortedListToBST(list, start, end) { + var size = end - start; + if (size > 0) { + var middle = start + Math.floor(size / 2); + var left = sortedListToBST(list, start, middle); + var root = list.head; + root.left = left; + list.head = list.head.next; + root.right = sortedListToBST(list, middle + 1, end); + return root; + } + return null; } - - p.next = null; - return head.next; - } - - function toList(root) { - var current = root; - var Q = []; - var done = false; - var head = new Node(null, null); - var p = head; - - while (!done) { - if (current) { - Q.push(current); - current = current.left; - } else { - if (Q.length > 0) { - current = p = p.next = Q.pop(); - current = current.right; - } else done = true; + function mergeLists(l1, l2, compare) { + var head = new Node(null, null); // dummy + var p = head; + var p1 = l1; + var p2 = l2; + while (p1 !== null && p2 !== null) { + if (compare(p1.key, p2.key) < 0) { + p.next = p1; + p1 = p1.next; + } else { + p.next = p2; + p2 = p2.next; + } + p = p.next; } + if (p1 !== null) { + p.next = p1; + } else if (p2 !== null) { + p.next = p2; + } + return head.next; + } + function sort(keys, values, left, right, compare) { + if (left >= right) return; + var pivot = keys[left + right >> 1]; + var i = left - 1; + var j = right + 1; + while (true) { + do i++; while (compare(keys[i], pivot) < 0); + do j--; while (compare(keys[j], pivot) > 0); + if (i >= j) break; + var tmp = keys[i]; + keys[i] = keys[j]; + keys[j] = tmp; + tmp = values[i]; + values[i] = values[j]; + values[j] = tmp; + } + sort(keys, values, left, j, compare); + sort(keys, values, j + 1, right, compare); } - p.next = null; // that'll work even if the tree was empty - - return head.next; - } - - function sortedListToBST(list, start, end) { - var size = end - start; + /** + * A bounding box has the format: + * + * { ll: { x: xmin, y: ymin }, ur: { x: xmax, y: ymax } } + * + */ - if (size > 0) { - var middle = start + Math.floor(size / 2); - var left = sortedListToBST(list, start, middle); - var root = list.head; - root.left = left; - list.head = list.head.next; - root.right = sortedListToBST(list, middle + 1, end); - return root; - } + const isInBbox = (bbox, point) => { + return bbox.ll.x <= point.x && point.x <= bbox.ur.x && bbox.ll.y <= point.y && point.y <= bbox.ur.y; + }; - return null; - } + /* Returns either null, or a bbox (aka an ordered pair of points) + * If there is only one point of overlap, a bbox with identical points + * will be returned */ + const getBboxOverlap = (b1, b2) => { + // check if the bboxes overlap at all + if (b2.ur.x < b1.ll.x || b1.ur.x < b2.ll.x || b2.ur.y < b1.ll.y || b1.ur.y < b2.ll.y) return null; - function mergeLists(l1, l2, compare) { - var head = new Node(null, null); // dummy + // find the middle two X values + const lowerX = b1.ll.x < b2.ll.x ? b2.ll.x : b1.ll.x; + const upperX = b1.ur.x < b2.ur.x ? b1.ur.x : b2.ur.x; - var p = head; - var p1 = l1; - var p2 = l2; + // find the middle two Y values + const lowerY = b1.ll.y < b2.ll.y ? b2.ll.y : b1.ll.y; + const upperY = b1.ur.y < b2.ur.y ? b1.ur.y : b2.ur.y; - while (p1 !== null && p2 !== null) { - if (compare(p1.key, p2.key) < 0) { - p.next = p1; - p1 = p1.next; - } else { - p.next = p2; - p2 = p2.next; - } + // put those middle values together to get the overlap + return { + ll: { + x: lowerX, + y: lowerY + }, + ur: { + x: upperX, + y: upperY + } + }; + }; - p = p.next; - } + /* Javascript doesn't do integer math. Everything is + * floating point with percision Number.EPSILON. + * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON + */ - if (p1 !== null) { - p.next = p1; - } else if (p2 !== null) { - p.next = p2; - } + let epsilon$1 = Number.EPSILON; - return head.next; - } - - function sort(keys, values, left, right, compare) { - if (left >= right) return; - var pivot = keys[left + right >> 1]; - var i = left - 1; - var j = right + 1; - - while (true) { - do { - i++; - } while (compare(keys[i], pivot) < 0); - - do { - j--; - } while (compare(keys[j], pivot) > 0); - - if (i >= j) break; - var tmp = keys[i]; - keys[i] = keys[j]; - keys[j] = tmp; - tmp = values[i]; - values[i] = values[j]; - values[j] = tmp; - } + // IE Polyfill + if (epsilon$1 === undefined) epsilon$1 = Math.pow(2, -52); + const EPSILON_SQ = epsilon$1 * epsilon$1; - sort(keys, values, left, j, compare); - sort(keys, values, j + 1, right, compare); - } - - /** - * A bounding box has the format: - * - * { ll: { x: xmin, y: ymin }, ur: { x: xmax, y: ymax } } - * - */ - var isInBbox = function isInBbox(bbox, point) { - return bbox.ll.x <= point.x && point.x <= bbox.ur.x && bbox.ll.y <= point.y && point.y <= bbox.ur.y; - }; - /* Returns either null, or a bbox (aka an ordered pair of points) - * If there is only one point of overlap, a bbox with identical points - * will be returned */ - - var getBboxOverlap = function getBboxOverlap(b1, b2) { - // check if the bboxes overlap at all - if (b2.ur.x < b1.ll.x || b1.ur.x < b2.ll.x || b2.ur.y < b1.ll.y || b1.ur.y < b2.ll.y) return null; // find the middle two X values - - var lowerX = b1.ll.x < b2.ll.x ? b2.ll.x : b1.ll.x; - var upperX = b1.ur.x < b2.ur.x ? b1.ur.x : b2.ur.x; // find the middle two Y values - - var lowerY = b1.ll.y < b2.ll.y ? b2.ll.y : b1.ll.y; - var upperY = b1.ur.y < b2.ur.y ? b1.ur.y : b2.ur.y; // put those middle values together to get the overlap - - return { - ll: { - x: lowerX, - y: lowerY - }, - ur: { - x: upperX, - y: upperY + /* FLP comparator */ + const cmp = (a, b) => { + // check if they're both 0 + if (-epsilon$1 < a && a < epsilon$1) { + if (-epsilon$1 < b && b < epsilon$1) { + return 0; + } } - }; - }; - - /* Javascript doesn't do integer math. Everything is - * floating point with percision Number.EPSILON. - * - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON - */ - var epsilon = Number.EPSILON; // IE Polyfill - - if (epsilon === undefined) epsilon = Math.pow(2, -52); - var EPSILON_SQ = epsilon * epsilon; - /* FLP comparator */ - - var cmp = function cmp(a, b) { - // check if they're both 0 - if (-epsilon < a && a < epsilon) { - if (-epsilon < b && b < epsilon) { + + // check if they're flp equal + const ab = a - b; + if (ab * ab < EPSILON_SQ * a * b) { return 0; } - } // check if they're flp equal - - var ab = a - b; + // normal comparison + return a < b ? -1 : 1; + }; - if (ab * ab < EPSILON_SQ * a * b) { - return 0; - } // normal comparison - - - return a < b ? -1 : 1; - }; - - /** - * This class rounds incoming values sufficiently so that - * floating points problems are, for the most part, avoided. - * - * Incoming points are have their x & y values tested against - * all previously seen x & y values. If either is 'too close' - * to a previously seen value, it's value is 'snapped' to the - * previously seen value. - * - * All points should be rounded by this class before being - * stored in any data structures in the rest of this algorithm. - */ - - var PtRounder = /*#__PURE__*/function () { - function PtRounder() { - _classCallCheck(this, PtRounder); - - this.reset(); - } + /** + * This class rounds incoming values sufficiently so that + * floating points problems are, for the most part, avoided. + * + * Incoming points are have their x & y values tested against + * all previously seen x & y values. If either is 'too close' + * to a previously seen value, it's value is 'snapped' to the + * previously seen value. + * + * All points should be rounded by this class before being + * stored in any data structures in the rest of this algorithm. + */ - _createClass(PtRounder, [{ - key: "reset", - value: function reset() { + class PtRounder { + constructor() { + this.reset(); + } + reset() { this.xRounder = new CoordRounder(); this.yRounder = new CoordRounder(); } - }, { - key: "round", - value: function round(x, y) { + round(x, y) { return { x: this.xRounder.round(x), y: this.yRounder.round(y) }; } - }]); - - return PtRounder; - }(); - - var CoordRounder = /*#__PURE__*/function () { - function CoordRounder() { - _classCallCheck(this, CoordRounder); - - this.tree = new Tree(); // preseed with 0 so we don't end up with values < Number.EPSILON - - this.round(0); - } // Note: this can rounds input values backwards or forwards. - // You might ask, why not restrict this to just rounding - // forwards? Wouldn't that allow left endpoints to always - // remain left endpoints during splitting (never change to - // right). No - it wouldn't, because we snap intersections - // to endpoints (to establish independence from the segment - // angle for t-intersections). - - - _createClass(CoordRounder, [{ - key: "round", - value: function round(coord) { - var node = this.tree.add(coord); - var prevNode = this.tree.prev(node); - + } + class CoordRounder { + constructor() { + this.tree = new Tree(); + // preseed with 0 so we don't end up with values < Number.EPSILON + this.round(0); + } + + // Note: this can rounds input values backwards or forwards. + // You might ask, why not restrict this to just rounding + // forwards? Wouldn't that allow left endpoints to always + // remain left endpoints during splitting (never change to + // right). No - it wouldn't, because we snap intersections + // to endpoints (to establish independence from the segment + // angle for t-intersections). + round(coord) { + const node = this.tree.add(coord); + const prevNode = this.tree.prev(node); if (prevNode !== null && cmp(node.key, prevNode.key) === 0) { this.tree.remove(coord); return prevNode.key; } - - var nextNode = this.tree.next(node); - + const nextNode = this.tree.next(node); if (nextNode !== null && cmp(node.key, nextNode.key) === 0) { this.tree.remove(coord); return nextNode.key; } - return coord; } - }]); - - return CoordRounder; - }(); // singleton available by import - + } - var rounder = new PtRounder(); + // singleton available by import + const rounder = new PtRounder(); + + const epsilon = 1.1102230246251565e-16; + const splitter = 134217729; + const resulterrbound = (3 + 8 * epsilon) * epsilon; + + // fast_expansion_sum_zeroelim routine from oritinal code + function sum(elen, e, flen, f, h) { + let Q, Qnew, hh, bvirt; + let enow = e[0]; + let fnow = f[0]; + let eindex = 0; + let findex = 0; + if (fnow > enow === fnow > -enow) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + let 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) { + let Q = e[0]; + for (let i = 1; i < elen; i++) Q += e[i]; + return Q; + } + function vec(n) { + return new Float64Array(n); + } - /* Cross Product of two vectors with first point at origin */ + const ccwerrboundA = (3 + 16 * epsilon) * epsilon; + const ccwerrboundB = (2 + 12 * epsilon) * epsilon; + const ccwerrboundC = (9 + 64 * epsilon) * epsilon * epsilon; + const B = vec(4); + const C1 = vec(8); + const C2 = vec(12); + const D = vec(16); + const u = vec(4); + function orient2dadapt(ax, ay, bx, by, cx, cy, detsum) { + let acxtail, acytail, bcxtail, bcytail; + let bvirt, c, ahi, alo, bhi, blo, _i, _j, _0, s1, s0, t1, t0, u3; + const acx = ax - cx; + const bcx = bx - cx; + const acy = ay - cy; + const 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; + let det = estimate(4, B); + let 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; + const 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; + const 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; + const Dlen = sum(C2len, C2, 4, u, D); + return D[Dlen - 1]; + } + function orient2d(ax, ay, bx, by, cx, cy) { + const detleft = (ay - cy) * (bx - cx); + const detright = (ax - cx) * (by - cy); + const det = detleft - detright; + const detsum = Math.abs(detleft + detright); + if (Math.abs(det) >= ccwerrboundA * detsum) return det; + return -orient2dadapt(ax, ay, bx, by, cx, cy, detsum); + } - var crossProduct = function crossProduct(a, b) { - return a.x * b.y - a.y * b.x; - }; - /* Dot Product of two vectors with first point at origin */ + /* Cross Product of two vectors with first point at origin */ + const crossProduct = (a, b) => a.x * b.y - a.y * b.x; - var dotProduct = function dotProduct(a, b) { - return a.x * b.x + a.y * b.y; - }; - /* Comparator for two vectors with same starting point */ + /* Dot Product of two vectors with first point at origin */ + const dotProduct = (a, b) => a.x * b.x + a.y * b.y; - var compareVectorAngles = function compareVectorAngles(basePt, endPt1, endPt2) { - var v1 = { - x: endPt1.x - basePt.x, - y: endPt1.y - basePt.y - }; - var v2 = { - x: endPt2.x - basePt.x, - y: endPt2.y - basePt.y - }; - var kross = crossProduct(v1, v2); - return cmp(kross, 0); - }; - var length = function length(v) { - return Math.sqrt(dotProduct(v, v)); - }; - /* Get the sine of the angle from pShared -> pAngle to pShaed -> pBase */ - - var sineOfAngle = function sineOfAngle(pShared, pBase, pAngle) { - var vBase = { - x: pBase.x - pShared.x, - y: pBase.y - pShared.y - }; - var vAngle = { - x: pAngle.x - pShared.x, - y: pAngle.y - pShared.y - }; - return crossProduct(vAngle, vBase) / length(vAngle) / length(vBase); - }; - /* Get the cosine of the angle from pShared -> pAngle to pShaed -> pBase */ - - var cosineOfAngle = function cosineOfAngle(pShared, pBase, pAngle) { - var vBase = { - x: pBase.x - pShared.x, - y: pBase.y - pShared.y + /* Comparator for two vectors with same starting point */ + const compareVectorAngles = (basePt, endPt1, endPt2) => { + const res = orient2d(basePt.x, basePt.y, endPt1.x, endPt1.y, endPt2.x, endPt2.y); + if (res > 0) return -1; + if (res < 0) return 1; + return 0; }; - var vAngle = { - x: pAngle.x - pShared.x, - y: pAngle.y - pShared.y + const length = v => Math.sqrt(dotProduct(v, v)); + + /* Get the sine of the angle from pShared -> pAngle to pShaed -> pBase */ + const sineOfAngle = (pShared, pBase, pAngle) => { + const vBase = { + x: pBase.x - pShared.x, + y: pBase.y - pShared.y + }; + const vAngle = { + x: pAngle.x - pShared.x, + y: pAngle.y - pShared.y + }; + return crossProduct(vAngle, vBase) / length(vAngle) / length(vBase); }; - return dotProduct(vAngle, vBase) / length(vAngle) / length(vBase); - }; - /* Get the x coordinate where the given line (defined by a point and vector) - * crosses the horizontal line with the given y coordiante. - * In the case of parrallel lines (including overlapping ones) returns null. */ - - var horizontalIntersection = function horizontalIntersection(pt, v, y) { - if (v.y === 0) return null; - return { - x: pt.x + v.x / v.y * (y - pt.y), - y: y + + /* Get the cosine of the angle from pShared -> pAngle to pShaed -> pBase */ + const cosineOfAngle = (pShared, pBase, pAngle) => { + const vBase = { + x: pBase.x - pShared.x, + y: pBase.y - pShared.y + }; + const vAngle = { + x: pAngle.x - pShared.x, + y: pAngle.y - pShared.y + }; + return dotProduct(vAngle, vBase) / length(vAngle) / length(vBase); }; - }; - /* Get the y coordinate where the given line (defined by a point and vector) - * crosses the vertical line with the given x coordiante. - * In the case of parrallel lines (including overlapping ones) returns null. */ - - var verticalIntersection = function verticalIntersection(pt, v, x) { - if (v.x === 0) return null; - return { - x: x, - y: pt.y + v.y / v.x * (x - pt.x) + + /* Get the x coordinate where the given line (defined by a point and vector) + * crosses the horizontal line with the given y coordiante. + * In the case of parrallel lines (including overlapping ones) returns null. */ + const horizontalIntersection = (pt, v, y) => { + if (v.y === 0) return null; + return { + x: pt.x + v.x / v.y * (y - pt.y), + y: y + }; }; - }; - /* Get the intersection of two lines, each defined by a base point and a vector. - * In the case of parrallel lines (including overlapping ones) returns null. */ - - var intersection = function intersection(pt1, v1, pt2, v2) { - // take some shortcuts for vertical and horizontal lines - // this also ensures we don't calculate an intersection and then discover - // it's actually outside the bounding box of the line - if (v1.x === 0) return verticalIntersection(pt2, v2, pt1.x); - if (v2.x === 0) return verticalIntersection(pt1, v1, pt2.x); - if (v1.y === 0) return horizontalIntersection(pt2, v2, pt1.y); - if (v2.y === 0) return horizontalIntersection(pt1, v1, pt2.y); // General case for non-overlapping segments. - // This algorithm is based on Schneider and Eberly. - // http://www.cimec.org.ar/~ncalvo/Schneider_Eberly.pdf - pg 244 - - var kross = crossProduct(v1, v2); - if (kross == 0) return null; - var ve = { - x: pt2.x - pt1.x, - y: pt2.y - pt1.y + + /* Get the y coordinate where the given line (defined by a point and vector) + * crosses the vertical line with the given x coordiante. + * In the case of parrallel lines (including overlapping ones) returns null. */ + const verticalIntersection = (pt, v, x) => { + if (v.x === 0) return null; + return { + x: x, + y: pt.y + v.y / v.x * (x - pt.x) + }; }; - var d1 = crossProduct(ve, v1) / kross; - var d2 = crossProduct(ve, v2) / kross; // take the average of the two calculations to minimize rounding error - var x1 = pt1.x + d2 * v1.x, + /* Get the intersection of two lines, each defined by a base point and a vector. + * In the case of parrallel lines (including overlapping ones) returns null. */ + const intersection$1 = (pt1, v1, pt2, v2) => { + // take some shortcuts for vertical and horizontal lines + // this also ensures we don't calculate an intersection and then discover + // it's actually outside the bounding box of the line + if (v1.x === 0) return verticalIntersection(pt2, v2, pt1.x); + if (v2.x === 0) return verticalIntersection(pt1, v1, pt2.x); + if (v1.y === 0) return horizontalIntersection(pt2, v2, pt1.y); + if (v2.y === 0) return horizontalIntersection(pt1, v1, pt2.y); + + // General case for non-overlapping segments. + // This algorithm is based on Schneider and Eberly. + // http://www.cimec.org.ar/~ncalvo/Schneider_Eberly.pdf - pg 244 + + const kross = crossProduct(v1, v2); + if (kross == 0) return null; + const ve = { + x: pt2.x - pt1.x, + y: pt2.y - pt1.y + }; + const d1 = crossProduct(ve, v1) / kross; + const d2 = crossProduct(ve, v2) / kross; + + // take the average of the two calculations to minimize rounding error + const x1 = pt1.x + d2 * v1.x, x2 = pt2.x + d1 * v2.x; - var y1 = pt1.y + d2 * v1.y, + const y1 = pt1.y + d2 * v1.y, y2 = pt2.y + d1 * v2.y; - var x = (x1 + x2) / 2; - var y = (y1 + y2) / 2; - return { - x: x, - y: y + const x = (x1 + x2) / 2; + const y = (y1 + y2) / 2; + return { + x: x, + y: y + }; }; - }; - var SweepEvent = /*#__PURE__*/function () { - _createClass(SweepEvent, null, [{ - key: "compare", + class SweepEvent { // for ordering sweep events in the sweep event queue - value: function compare(a, b) { + static compare(a, b) { // favor event with a point that the sweep line hits first - var ptCmp = SweepEvent.comparePoints(a.point, b.point); - if (ptCmp !== 0) return ptCmp; // the points are the same, so link them if needed + const ptCmp = SweepEvent.comparePoints(a.point, b.point); + if (ptCmp !== 0) return ptCmp; - if (a.point !== b.point) a.link(b); // favor right events over left + // the points are the same, so link them if needed + if (a.point !== b.point) a.link(b); - if (a.isLeft !== b.isLeft) return a.isLeft ? 1 : -1; // we have two matching left or right endpoints - // ordering of this case is the same as for their segments + // favor right events over left + if (a.isLeft !== b.isLeft) return a.isLeft ? 1 : -1; + // we have two matching left or right endpoints + // ordering of this case is the same as for their segments return Segment.compare(a.segment, b.segment); - } // for ordering points in sweep line order + } - }, { - key: "comparePoints", - value: function comparePoints(aPt, bPt) { + // for ordering points in sweep line order + static comparePoints(aPt, bPt) { if (aPt.x < bPt.x) return -1; if (aPt.x > bPt.x) return 1; if (aPt.y < bPt.y) return -1; if (aPt.y > bPt.y) return 1; return 0; - } // Warning: 'point' input will be modified and re-used (for performance) - - }]); - - function SweepEvent(point, isLeft) { - _classCallCheck(this, SweepEvent); - - if (point.events === undefined) point.events = [this];else point.events.push(this); - this.point = point; - this.isLeft = isLeft; // this.segment, this.otherSE set by factory - } + } - _createClass(SweepEvent, [{ - key: "link", - value: function link(other) { + // Warning: 'point' input will be modified and re-used (for performance) + constructor(point, isLeft) { + if (point.events === undefined) point.events = [this];else point.events.push(this); + this.point = point; + this.isLeft = isLeft; + // this.segment, this.otherSE set by factory + } + link(other) { if (other.point === this.point) { - throw new Error('Tried to link already linked events'); + throw new Error("Tried to link already linked events"); } - - var otherEvents = other.point.events; - - for (var i = 0, iMax = otherEvents.length; i < iMax; i++) { - var evt = otherEvents[i]; + const otherEvents = other.point.events; + for (let i = 0, iMax = otherEvents.length; i < iMax; i++) { + const evt = otherEvents[i]; this.point.events.push(evt); evt.point = this.point; } - this.checkForConsuming(); } + /* Do a pass over our linked events and check to see if any pair * of segments match, and should be consumed. */ - - }, { - key: "checkForConsuming", - value: function checkForConsuming() { + checkForConsuming() { // FIXME: The loops in this method run O(n^2) => no good. // Maintain little ordered sweep event trees? // Can we maintaining an ordering that avoids the need // for the re-sorting with getLeftmostComparator in geom-out? - // Compare each pair of events to see if other events also match - var numEvents = this.point.events.length; - for (var i = 0; i < numEvents; i++) { - var evt1 = this.point.events[i]; + // Compare each pair of events to see if other events also match + const numEvents = this.point.events.length; + for (let i = 0; i < numEvents; i++) { + const evt1 = this.point.events[i]; if (evt1.segment.consumedBy !== undefined) continue; - - for (var j = i + 1; j < numEvents; j++) { - var evt2 = this.point.events[j]; + for (let j = i + 1; j < numEvents; j++) { + const evt2 = this.point.events[j]; if (evt2.consumedBy !== undefined) continue; if (evt1.otherSE.point.events !== evt2.otherSE.point.events) continue; evt1.segment.consume(evt2.segment); } } } - }, { - key: "getAvailableLinkedEvents", - value: function getAvailableLinkedEvents() { + getAvailableLinkedEvents() { // point.events is always of length 2 or greater - var events = []; - - for (var i = 0, iMax = this.point.events.length; i < iMax; i++) { - var evt = this.point.events[i]; - + const events = []; + for (let i = 0, iMax = this.point.events.length; i < iMax; i++) { + const evt = this.point.events[i]; if (evt !== this && !evt.segment.ringOut && evt.segment.isInResult()) { events.push(evt); } } - return events; } + /** * Returns a comparator function for sorting linked events that will * favor the event that will give us the smallest left-side angle. @@ -1120,67 +1284,53 @@ * The comparator function has a compute cache such that it avoids * re-computing already-computed values. */ - - }, { - key: "getLeftmostComparator", - value: function getLeftmostComparator(baseEvent) { - var _this = this; - - var cache = new Map(); - - var fillCache = function fillCache(linkedEvent) { - var nextEvent = linkedEvent.otherSE; + getLeftmostComparator(baseEvent) { + const cache = new Map(); + const fillCache = linkedEvent => { + const nextEvent = linkedEvent.otherSE; cache.set(linkedEvent, { - sine: sineOfAngle(_this.point, baseEvent.point, nextEvent.point), - cosine: cosineOfAngle(_this.point, baseEvent.point, nextEvent.point) + sine: sineOfAngle(this.point, baseEvent.point, nextEvent.point), + cosine: cosineOfAngle(this.point, baseEvent.point, nextEvent.point) }); }; - - return function (a, b) { + return (a, b) => { if (!cache.has(a)) fillCache(a); if (!cache.has(b)) fillCache(b); - - var _cache$get = cache.get(a), - asine = _cache$get.sine, - acosine = _cache$get.cosine; - - var _cache$get2 = cache.get(b), - bsine = _cache$get2.sine, - bcosine = _cache$get2.cosine; // both on or above x-axis - - + const { + sine: asine, + cosine: acosine + } = cache.get(a); + const { + sine: bsine, + cosine: bcosine + } = cache.get(b); + + // both on or above x-axis if (asine >= 0 && bsine >= 0) { if (acosine < bcosine) return 1; if (acosine > bcosine) return -1; return 0; - } // both below x-axis - + } + // both below x-axis if (asine < 0 && bsine < 0) { if (acosine < bcosine) return -1; if (acosine > bcosine) return 1; return 0; - } // one above x-axis, one below - + } + // one above x-axis, one below if (bsine < asine) return -1; if (bsine > asine) return 1; return 0; }; } - }]); - - return SweepEvent; - }(); - - // segments and sweep events when all else is identical - - var segmentId = 0; - - var Segment = /*#__PURE__*/function () { - _createClass(Segment, null, [{ - key: "compare", + } + // Give segments unique ID's to get consistent sorting of + // segments and sweep events when all else is identical + let segmentId = 0; + class Segment { /* This compare() function is for ordering segments in the sweep * line tree, and does so according to the following criteria: * @@ -1194,135 +1344,157 @@ * or more of the segments are vertical) then the line to be considered * is directly on the right-more of the two left inputs. */ - value: function compare(a, b) { - var alx = a.leftSE.point.x; - var blx = b.leftSE.point.x; - var arx = a.rightSE.point.x; - var brx = b.rightSE.point.x; // check if they're even in the same vertical plane + static compare(a, b) { + const alx = a.leftSE.point.x; + const blx = b.leftSE.point.x; + const arx = a.rightSE.point.x; + const brx = b.rightSE.point.x; + // check if they're even in the same vertical plane if (brx < alx) return 1; if (arx < blx) return -1; - var aly = a.leftSE.point.y; - var bly = b.leftSE.point.y; - var ary = a.rightSE.point.y; - var bry = b.rightSE.point.y; // is left endpoint of segment B the right-more? + const aly = a.leftSE.point.y; + const bly = b.leftSE.point.y; + const ary = a.rightSE.point.y; + const bry = b.rightSE.point.y; + // is left endpoint of segment B the right-more? if (alx < blx) { // are the two segments in the same horizontal plane? if (bly < aly && bly < ary) return 1; - if (bly > aly && bly > ary) return -1; // is the B left endpoint colinear to segment A? + if (bly > aly && bly > ary) return -1; - var aCmpBLeft = a.comparePoint(b.leftSE.point); + // is the B left endpoint colinear to segment A? + const aCmpBLeft = a.comparePoint(b.leftSE.point); if (aCmpBLeft < 0) return 1; - if (aCmpBLeft > 0) return -1; // is the A right endpoint colinear to segment B ? + if (aCmpBLeft > 0) return -1; - var bCmpARight = b.comparePoint(a.rightSE.point); - if (bCmpARight !== 0) return bCmpARight; // colinear segments, consider the one with left-more - // left endpoint to be first (arbitrary?) + // is the A right endpoint colinear to segment B ? + const bCmpARight = b.comparePoint(a.rightSE.point); + if (bCmpARight !== 0) return bCmpARight; + // colinear segments, consider the one with left-more + // left endpoint to be first (arbitrary?) return -1; - } // is left endpoint of segment A the right-more? - + } + // is left endpoint of segment A the right-more? if (alx > blx) { if (aly < bly && aly < bry) return -1; - if (aly > bly && aly > bry) return 1; // is the A left endpoint colinear to segment B? + if (aly > bly && aly > bry) return 1; - var bCmpALeft = b.comparePoint(a.leftSE.point); - if (bCmpALeft !== 0) return bCmpALeft; // is the B right endpoint colinear to segment A? + // is the A left endpoint colinear to segment B? + const bCmpALeft = b.comparePoint(a.leftSE.point); + if (bCmpALeft !== 0) return bCmpALeft; - var aCmpBRight = a.comparePoint(b.rightSE.point); + // is the B right endpoint colinear to segment A? + const aCmpBRight = a.comparePoint(b.rightSE.point); if (aCmpBRight < 0) return 1; - if (aCmpBRight > 0) return -1; // colinear segments, consider the one with left-more - // left endpoint to be first (arbitrary?) + if (aCmpBRight > 0) return -1; + // colinear segments, consider the one with left-more + // left endpoint to be first (arbitrary?) return 1; - } // if we get here, the two left endpoints are in the same - // vertical plane, ie alx === blx - // consider the lower left-endpoint to come first + } + // if we get here, the two left endpoints are in the same + // vertical plane, ie alx === blx + // consider the lower left-endpoint to come first if (aly < bly) return -1; - if (aly > bly) return 1; // left endpoints are identical + if (aly > bly) return 1; + + // left endpoints are identical // check for colinearity by using the left-more right endpoint - // is the A right endpoint more left-more? + // is the A right endpoint more left-more? if (arx < brx) { - var _bCmpARight = b.comparePoint(a.rightSE.point); - - if (_bCmpARight !== 0) return _bCmpARight; - } // is the B right endpoint more left-more? - + const bCmpARight = b.comparePoint(a.rightSE.point); + if (bCmpARight !== 0) return bCmpARight; + } + // is the B right endpoint more left-more? if (arx > brx) { - var _aCmpBRight = a.comparePoint(b.rightSE.point); - - if (_aCmpBRight < 0) return 1; - if (_aCmpBRight > 0) return -1; + const aCmpBRight = a.comparePoint(b.rightSE.point); + if (aCmpBRight < 0) return 1; + if (aCmpBRight > 0) return -1; } - if (arx !== brx) { // are these two [almost] vertical segments with opposite orientation? // if so, the one with the lower right endpoint comes first - var ay = ary - aly; - var ax = arx - alx; - var by = bry - bly; - var bx = brx - blx; + const ay = ary - aly; + const ax = arx - alx; + const by = bry - bly; + const bx = brx - blx; if (ay > ax && by < bx) return 1; if (ay < ax && by > bx) return -1; - } // we have colinear segments with matching orientation - // consider the one with more left-more right endpoint to be first - + } + // we have colinear segments with matching orientation + // consider the one with more left-more right endpoint to be first if (arx > brx) return 1; - if (arx < brx) return -1; // if we get here, two two right endpoints are in the same + if (arx < brx) return -1; + + // if we get here, two two right endpoints are in the same // vertical plane, ie arx === brx - // consider the lower right-endpoint to come first + // consider the lower right-endpoint to come first if (ary < bry) return -1; - if (ary > bry) return 1; // right endpoints identical as well, so the segments are idential - // fall back on creation order as consistent tie-breaker + if (ary > bry) return 1; + // right endpoints identical as well, so the segments are idential + // fall back on creation order as consistent tie-breaker if (a.id < b.id) return -1; - if (a.id > b.id) return 1; // identical segment, ie a === b + if (a.id > b.id) return 1; + // identical segment, ie a === b return 0; } + /* Warning: a reference to ringWindings input will be stored, * and possibly will be later modified */ - - }]); - - function Segment(leftSE, rightSE, rings, windings) { - _classCallCheck(this, Segment); - - this.id = ++segmentId; - this.leftSE = leftSE; - leftSE.segment = this; - leftSE.otherSE = rightSE; - this.rightSE = rightSE; - rightSE.segment = this; - rightSE.otherSE = leftSE; - this.rings = rings; - this.windings = windings; // left unset for performance, set later in algorithm - // this.ringOut, this.consumedBy, this.prev - } - - _createClass(Segment, [{ - key: "replaceRightSE", + constructor(leftSE, rightSE, rings, windings) { + this.id = ++segmentId; + this.leftSE = leftSE; + leftSE.segment = this; + leftSE.otherSE = rightSE; + this.rightSE = rightSE; + rightSE.segment = this; + rightSE.otherSE = leftSE; + this.rings = rings; + this.windings = windings; + // left unset for performance, set later in algorithm + // this.ringOut, this.consumedBy, this.prev + } + static fromRing(pt1, pt2, ring) { + let leftPt, rightPt, winding; + + // ordering the two points according to sweep line ordering + const cmpPts = SweepEvent.comparePoints(pt1, pt2); + if (cmpPts < 0) { + leftPt = pt1; + rightPt = pt2; + winding = 1; + } else if (cmpPts > 0) { + leftPt = pt2; + rightPt = pt1; + winding = -1; + } else throw new Error(`Tried to create degenerate segment at [${pt1.x}, ${pt1.y}]`); + const leftSE = new SweepEvent(leftPt, true); + const rightSE = new SweepEvent(rightPt, false); + return new Segment(leftSE, rightSE, [ring], [winding]); + } /* When a segment is split, the rightSE is replaced with a new sweep event */ - value: function replaceRightSE(newRightSE) { + replaceRightSE(newRightSE) { this.rightSE = newRightSE; this.rightSE.segment = this; this.rightSE.otherSE = this.leftSE; this.leftSE.otherSE = this.rightSE; } - }, { - key: "bbox", - value: function bbox() { - var y1 = this.leftSE.point.y; - var y2 = this.rightSE.point.y; + bbox() { + const y1 = this.leftSE.point.y; + const y2 = this.rightSE.point.y; return { ll: { x: this.leftSE.point.x, @@ -1334,21 +1506,18 @@ } }; } - /* A vector from the left point to the right */ - }, { - key: "vector", - value: function vector() { + /* A vector from the left point to the right */ + vector() { return { x: this.rightSE.point.x - this.leftSE.point.x, y: this.rightSE.point.y - this.leftSE.point.y }; } - }, { - key: "isAnEndpoint", - value: function isAnEndpoint(pt) { + isAnEndpoint(pt) { return pt.x === this.leftSE.point.x && pt.y === this.leftSE.point.y || pt.x === this.rightSE.point.x && pt.y === this.rightSE.point.y; } + /* Compare this segment with a point. * * A point P is considered to be colinear to a segment if there @@ -1362,32 +1531,32 @@ * 0: point is colinear to segment * -1: point lies below the segment (to the right of vertical) */ - - }, { - key: "comparePoint", - value: function comparePoint(point) { + comparePoint(point) { if (this.isAnEndpoint(point)) return 0; - var lPt = this.leftSE.point; - var rPt = this.rightSE.point; - var v = this.vector(); // Exactly vertical segments. + const lPt = this.leftSE.point; + const rPt = this.rightSE.point; + const v = this.vector(); + // Exactly vertical segments. if (lPt.x === rPt.x) { if (point.x === lPt.x) return 0; return point.x < lPt.x ? 1 : -1; - } // Nearly vertical segments with an intersection. - // Check to see where a point on the line with matching Y coordinate is. + } + // Nearly vertical segments with an intersection. + // Check to see where a point on the line with matching Y coordinate is. + const yDist = (point.y - lPt.y) / v.y; + const xFromYDist = lPt.x + yDist * v.x; + if (point.x === xFromYDist) return 0; - var yDist = (point.y - lPt.y) / v.y; - var xFromYDist = lPt.x + yDist * v.x; - if (point.x === xFromYDist) return 0; // General case. + // General case. // Check to see where a point on the line with matching X coordinate is. - - var xDist = (point.x - lPt.x) / v.x; - var yFromXDist = lPt.y + xDist * v.y; + const xDist = (point.x - lPt.x) / v.x; + const yFromXDist = lPt.y + xDist * v.y; if (point.y === yFromXDist) return 0; return point.y < yFromXDist ? -1 : 1; } + /** * Given another segment, returns the first non-trivial intersection * between the two segments (in terms of sweep line ordering), if it exists. @@ -1403,78 +1572,83 @@ * If no non-trivial intersection exists, return null * Else, return null. */ - - }, { - key: "getIntersection", - value: function getIntersection(other) { + getIntersection(other) { // If bboxes don't overlap, there can't be any intersections - var tBbox = this.bbox(); - var oBbox = other.bbox(); - var bboxOverlap = getBboxOverlap(tBbox, oBbox); - if (bboxOverlap === null) return null; // We first check to see if the endpoints can be considered intersections. + const tBbox = this.bbox(); + const oBbox = other.bbox(); + const bboxOverlap = getBboxOverlap(tBbox, oBbox); + if (bboxOverlap === null) return null; + + // We first check to see if the endpoints can be considered intersections. // This will 'snap' intersections to endpoints if possible, and will // handle cases of colinearity. - var tlp = this.leftSE.point; - var trp = this.rightSE.point; - var olp = other.leftSE.point; - var orp = other.rightSE.point; // does each endpoint touch the other segment? + const tlp = this.leftSE.point; + const trp = this.rightSE.point; + const olp = other.leftSE.point; + const orp = other.rightSE.point; + + // does each endpoint touch the other segment? // note that we restrict the 'touching' definition to only allow segments // to touch endpoints that lie forward from where we are in the sweep line pass + const touchesOtherLSE = isInBbox(tBbox, olp) && this.comparePoint(olp) === 0; + const touchesThisLSE = isInBbox(oBbox, tlp) && other.comparePoint(tlp) === 0; + const touchesOtherRSE = isInBbox(tBbox, orp) && this.comparePoint(orp) === 0; + const touchesThisRSE = isInBbox(oBbox, trp) && other.comparePoint(trp) === 0; - var touchesOtherLSE = isInBbox(tBbox, olp) && this.comparePoint(olp) === 0; - var touchesThisLSE = isInBbox(oBbox, tlp) && other.comparePoint(tlp) === 0; - var touchesOtherRSE = isInBbox(tBbox, orp) && this.comparePoint(orp) === 0; - var touchesThisRSE = isInBbox(oBbox, trp) && other.comparePoint(trp) === 0; // do left endpoints match? - + // do left endpoints match? if (touchesThisLSE && touchesOtherLSE) { // these two cases are for colinear segments with matching left // endpoints, and one segment being longer than the other if (touchesThisRSE && !touchesOtherRSE) return trp; - if (!touchesThisRSE && touchesOtherRSE) return orp; // either the two segments match exactly (two trival intersections) + if (!touchesThisRSE && touchesOtherRSE) return orp; + // either the two segments match exactly (two trival intersections) // or just on their left endpoint (one trivial intersection - return null; - } // does this left endpoint matches (other doesn't) - + } + // does this left endpoint matches (other doesn't) if (touchesThisLSE) { // check for segments that just intersect on opposing endpoints if (touchesOtherRSE) { if (tlp.x === orp.x && tlp.y === orp.y) return null; - } // t-intersection on left endpoint - - + } + // t-intersection on left endpoint return tlp; - } // does other left endpoint matches (this doesn't) - + } + // does other left endpoint matches (this doesn't) if (touchesOtherLSE) { // check for segments that just intersect on opposing endpoints if (touchesThisRSE) { if (trp.x === olp.x && trp.y === olp.y) return null; - } // t-intersection on left endpoint - - + } + // t-intersection on left endpoint return olp; - } // trivial intersection on right endpoints - + } - if (touchesThisRSE && touchesOtherRSE) return null; // t-intersections on just one right endpoint + // trivial intersection on right endpoints + if (touchesThisRSE && touchesOtherRSE) return null; + // t-intersections on just one right endpoint if (touchesThisRSE) return trp; - if (touchesOtherRSE) return orp; // None of our endpoints intersect. Look for a general intersection between + if (touchesOtherRSE) return orp; + + // None of our endpoints intersect. Look for a general intersection between // infinite lines laid over the segments + const pt = intersection$1(tlp, this.vector(), olp, other.vector()); - var pt = intersection(tlp, this.vector(), olp, other.vector()); // are the segments parrallel? Note that if they were colinear with overlap, + // are the segments parrallel? Note that if they were colinear with overlap, // they would have an endpoint intersection and that case was already handled above + if (pt === null) return null; - if (pt === null) return null; // is the intersection found between the lines not on the segments? - - if (!isInBbox(bboxOverlap, pt)) return null; // round the the computed point if needed + // is the intersection found between the lines not on the segments? + if (!isInBbox(bboxOverlap, pt)) return null; + // round the the computed point if needed return rounder.round(pt.x, pt.y); } + /** * Split the given segment into multiple segments on the given points. * * Each existing segment will retain its leftSE and a new rightSE will be @@ -1487,215 +1661,180 @@ * * Warning: input array of points is modified */ - - }, { - key: "split", - value: function split(point) { - var newEvents = []; - var alreadyLinked = point.events !== undefined; - var newLeftSE = new SweepEvent(point, true); - var newRightSE = new SweepEvent(point, false); - var oldRightSE = this.rightSE; + split(point) { + const newEvents = []; + const alreadyLinked = point.events !== undefined; + const newLeftSE = new SweepEvent(point, true); + const newRightSE = new SweepEvent(point, false); + const oldRightSE = this.rightSE; this.replaceRightSE(newRightSE); newEvents.push(newRightSE); newEvents.push(newLeftSE); - var newSeg = new Segment(newLeftSE, oldRightSE, this.rings.slice(), this.windings.slice()); // when splitting a nearly vertical downward-facing segment, + const newSeg = new Segment(newLeftSE, oldRightSE, this.rings.slice(), this.windings.slice()); + + // when splitting a nearly vertical downward-facing segment, // sometimes one of the resulting new segments is vertical, in which // case its left and right events may need to be swapped - if (SweepEvent.comparePoints(newSeg.leftSE.point, newSeg.rightSE.point) > 0) { newSeg.swapEvents(); } - if (SweepEvent.comparePoints(this.leftSE.point, this.rightSE.point) > 0) { this.swapEvents(); - } // in the point we just used to create new sweep events with was already + } + + // in the point we just used to create new sweep events with was already // linked to other events, we need to check if either of the affected // segments should be consumed - - if (alreadyLinked) { newLeftSE.checkForConsuming(); newRightSE.checkForConsuming(); } - return newEvents; } - /* Swap which event is left and right */ - }, { - key: "swapEvents", - value: function swapEvents() { - var tmpEvt = this.rightSE; + /* Swap which event is left and right */ + swapEvents() { + const tmpEvt = this.rightSE; this.rightSE = this.leftSE; this.leftSE = tmpEvt; this.leftSE.isLeft = true; this.rightSE.isLeft = false; - - for (var i = 0, iMax = this.windings.length; i < iMax; i++) { + for (let i = 0, iMax = this.windings.length; i < iMax; i++) { this.windings[i] *= -1; } } + /* Consume another segment. We take their rings under our wing * and mark them as consumed. Use for perfectly overlapping segments */ - - }, { - key: "consume", - value: function consume(other) { - var consumer = this; - var consumee = other; - - while (consumer.consumedBy) { - consumer = consumer.consumedBy; - } - - while (consumee.consumedBy) { - consumee = consumee.consumedBy; - } - - var cmp = Segment.compare(consumer, consumee); + consume(other) { + let consumer = this; + let consumee = other; + while (consumer.consumedBy) consumer = consumer.consumedBy; + while (consumee.consumedBy) consumee = consumee.consumedBy; + const cmp = Segment.compare(consumer, consumee); if (cmp === 0) return; // already consumed // the winner of the consumption is the earlier segment // according to sweep line ordering - if (cmp > 0) { - var tmp = consumer; + const tmp = consumer; consumer = consumee; consumee = tmp; - } // make sure a segment doesn't consume it's prev - + } + // make sure a segment doesn't consume it's prev if (consumer.prev === consumee) { - var _tmp = consumer; + const tmp = consumer; consumer = consumee; - consumee = _tmp; + consumee = tmp; } - - for (var i = 0, iMax = consumee.rings.length; i < iMax; i++) { - var ring = consumee.rings[i]; - var winding = consumee.windings[i]; - var index = consumer.rings.indexOf(ring); - + for (let i = 0, iMax = consumee.rings.length; i < iMax; i++) { + const ring = consumee.rings[i]; + const winding = consumee.windings[i]; + const index = consumer.rings.indexOf(ring); if (index === -1) { consumer.rings.push(ring); consumer.windings.push(winding); } else consumer.windings[index] += winding; } - consumee.rings = null; consumee.windings = null; - consumee.consumedBy = consumer; // mark sweep events consumed as to maintain ordering in sweep event queue + consumee.consumedBy = consumer; + // mark sweep events consumed as to maintain ordering in sweep event queue consumee.leftSE.consumedBy = consumer.leftSE; consumee.rightSE.consumedBy = consumer.rightSE; } - /* The first segment previous segment chain that is in the result */ - }, { - key: "prevInResult", - value: function prevInResult() { + /* The first segment previous segment chain that is in the result */ + prevInResult() { if (this._prevInResult !== undefined) return this._prevInResult; if (!this.prev) this._prevInResult = null;else if (this.prev.isInResult()) this._prevInResult = this.prev;else this._prevInResult = this.prev.prevInResult(); return this._prevInResult; } - }, { - key: "beforeState", - value: function beforeState() { + beforeState() { if (this._beforeState !== undefined) return this._beforeState; if (!this.prev) this._beforeState = { rings: [], windings: [], multiPolys: [] };else { - var seg = this.prev.consumedBy || this.prev; + const seg = this.prev.consumedBy || this.prev; this._beforeState = seg.afterState(); } return this._beforeState; } - }, { - key: "afterState", - value: function afterState() { + afterState() { if (this._afterState !== undefined) return this._afterState; - var beforeState = this.beforeState(); + const beforeState = this.beforeState(); this._afterState = { rings: beforeState.rings.slice(0), windings: beforeState.windings.slice(0), multiPolys: [] }; - var ringsAfter = this._afterState.rings; - var windingsAfter = this._afterState.windings; - var mpsAfter = this._afterState.multiPolys; // calculate ringsAfter, windingsAfter - - for (var i = 0, iMax = this.rings.length; i < iMax; i++) { - var ring = this.rings[i]; - var winding = this.windings[i]; - var index = ringsAfter.indexOf(ring); - + const ringsAfter = this._afterState.rings; + const windingsAfter = this._afterState.windings; + const mpsAfter = this._afterState.multiPolys; + + // calculate ringsAfter, windingsAfter + for (let i = 0, iMax = this.rings.length; i < iMax; i++) { + const ring = this.rings[i]; + const winding = this.windings[i]; + const index = ringsAfter.indexOf(ring); if (index === -1) { ringsAfter.push(ring); windingsAfter.push(winding); } else windingsAfter[index] += winding; - } // calcualte polysAfter - - - var polysAfter = []; - var polysExclude = []; - - for (var _i = 0, _iMax = ringsAfter.length; _i < _iMax; _i++) { - if (windingsAfter[_i] === 0) continue; // non-zero rule + } - var _ring = ringsAfter[_i]; - var poly = _ring.poly; + // calcualte polysAfter + const polysAfter = []; + const polysExclude = []; + for (let i = 0, iMax = ringsAfter.length; i < iMax; i++) { + if (windingsAfter[i] === 0) continue; // non-zero rule + const ring = ringsAfter[i]; + const poly = ring.poly; if (polysExclude.indexOf(poly) !== -1) continue; - if (_ring.isExterior) polysAfter.push(poly);else { + if (ring.isExterior) polysAfter.push(poly);else { if (polysExclude.indexOf(poly) === -1) polysExclude.push(poly); - - var _index = polysAfter.indexOf(_ring.poly); - - if (_index !== -1) polysAfter.splice(_index, 1); + const index = polysAfter.indexOf(ring.poly); + if (index !== -1) polysAfter.splice(index, 1); } - } // calculate multiPolysAfter - + } - for (var _i2 = 0, _iMax2 = polysAfter.length; _i2 < _iMax2; _i2++) { - var mp = polysAfter[_i2].multiPoly; + // calculate multiPolysAfter + for (let i = 0, iMax = polysAfter.length; i < iMax; i++) { + const mp = polysAfter[i].multiPoly; if (mpsAfter.indexOf(mp) === -1) mpsAfter.push(mp); } - return this._afterState; } - /* Is this segment part of the final result? */ - }, { - key: "isInResult", - value: function isInResult() { + /* Is this segment part of the final result? */ + isInResult() { // if we've been consumed, we're not in the result if (this.consumedBy) return false; if (this._isInResult !== undefined) return this._isInResult; - var mpsBefore = this.beforeState().multiPolys; - var mpsAfter = this.afterState().multiPolys; - + const mpsBefore = this.beforeState().multiPolys; + const mpsAfter = this.afterState().multiPolys; switch (operation.type) { - case 'union': + case "union": { // UNION - included iff: // * On one side of us there is 0 poly interiors AND // * On the other side there is 1 or more. - var noBefores = mpsBefore.length === 0; - var noAfters = mpsAfter.length === 0; + const noBefores = mpsBefore.length === 0; + const noAfters = mpsAfter.length === 0; this._isInResult = noBefores !== noAfters; break; } - - case 'intersection': + case "intersection": { // INTERSECTION - included iff: // * on one side of us all multipolys are rep. with poly interiors AND // * on the other side of us, not all multipolys are repsented // with poly interiors - var least; - var most; - + let least; + let most; if (mpsBefore.length < mpsAfter.length) { least = mpsBefore.length; most = mpsAfter.length; @@ -1703,630 +1842,472 @@ least = mpsAfter.length; most = mpsBefore.length; } - this._isInResult = most === operation.numMultiPolys && least < most; break; } - - case 'xor': + case "xor": { // XOR - included iff: // * the difference between the number of multipolys represented // with poly interiors on our two sides is an odd number - var diff = Math.abs(mpsBefore.length - mpsAfter.length); + const diff = Math.abs(mpsBefore.length - mpsAfter.length); this._isInResult = diff % 2 === 1; break; } - - case 'difference': + case "difference": { // DIFFERENCE included iff: // * on exactly one side, we have just the subject - var isJustSubject = function isJustSubject(mps) { - return mps.length === 1 && mps[0].isSubject; - }; - + const isJustSubject = mps => mps.length === 1 && mps[0].isSubject; this._isInResult = isJustSubject(mpsBefore) !== isJustSubject(mpsAfter); break; } - default: - throw new Error("Unrecognized operation type found ".concat(operation.type)); + throw new Error(`Unrecognized operation type found ${operation.type}`); } - return this._isInResult; } - }], [{ - key: "fromRing", - value: function fromRing(pt1, pt2, ring) { - var leftPt, rightPt, winding; // ordering the two points according to sweep line ordering - - var cmpPts = SweepEvent.comparePoints(pt1, pt2); - - if (cmpPts < 0) { - leftPt = pt1; - rightPt = pt2; - winding = 1; - } else if (cmpPts > 0) { - leftPt = pt2; - rightPt = pt1; - winding = -1; - } else throw new Error("Tried to create degenerate segment at [".concat(pt1.x, ", ").concat(pt1.y, "]")); - - var leftSE = new SweepEvent(leftPt, true); - var rightSE = new SweepEvent(rightPt, false); - return new Segment(leftSE, rightSE, [ring], [winding]); - } - }]); - - return Segment; - }(); - - var RingIn = /*#__PURE__*/function () { - function RingIn(geomRing, poly, isExterior) { - _classCallCheck(this, RingIn); - - if (!Array.isArray(geomRing) || geomRing.length === 0) { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); - } - - this.poly = poly; - this.isExterior = isExterior; - this.segments = []; - - if (typeof geomRing[0][0] !== 'number' || typeof geomRing[0][1] !== 'number') { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); - } + } - var firstPoint = rounder.round(geomRing[0][0], geomRing[0][1]); - this.bbox = { - ll: { - x: firstPoint.x, - y: firstPoint.y - }, - ur: { - x: firstPoint.x, - y: firstPoint.y + class RingIn { + constructor(geomRing, poly, isExterior) { + if (!Array.isArray(geomRing) || geomRing.length === 0) { + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - }; - var prevPoint = firstPoint; - - for (var i = 1, iMax = geomRing.length; i < iMax; i++) { - if (typeof geomRing[i][0] !== 'number' || typeof geomRing[i][1] !== 'number') { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); + this.poly = poly; + this.isExterior = isExterior; + this.segments = []; + if (typeof geomRing[0][0] !== "number" || typeof geomRing[0][1] !== "number") { + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); + } + const firstPoint = rounder.round(geomRing[0][0], geomRing[0][1]); + this.bbox = { + ll: { + x: firstPoint.x, + y: firstPoint.y + }, + ur: { + x: firstPoint.x, + y: firstPoint.y + } + }; + let prevPoint = firstPoint; + for (let i = 1, iMax = geomRing.length; i < iMax; i++) { + if (typeof geomRing[i][0] !== "number" || typeof geomRing[i][1] !== "number") { + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); + } + let point = rounder.round(geomRing[i][0], geomRing[i][1]); + // skip repeated points + if (point.x === prevPoint.x && point.y === prevPoint.y) continue; + this.segments.push(Segment.fromRing(prevPoint, point, this)); + if (point.x < this.bbox.ll.x) this.bbox.ll.x = point.x; + if (point.y < this.bbox.ll.y) this.bbox.ll.y = point.y; + if (point.x > this.bbox.ur.x) this.bbox.ur.x = point.x; + if (point.y > this.bbox.ur.y) this.bbox.ur.y = point.y; + prevPoint = point; + } + // add segment from last to first if last is not the same as first + if (firstPoint.x !== prevPoint.x || firstPoint.y !== prevPoint.y) { + this.segments.push(Segment.fromRing(prevPoint, firstPoint, this)); } - - var point = rounder.round(geomRing[i][0], geomRing[i][1]); // skip repeated points - - if (point.x === prevPoint.x && point.y === prevPoint.y) continue; - this.segments.push(Segment.fromRing(prevPoint, point, this)); - if (point.x < this.bbox.ll.x) this.bbox.ll.x = point.x; - if (point.y < this.bbox.ll.y) this.bbox.ll.y = point.y; - if (point.x > this.bbox.ur.x) this.bbox.ur.x = point.x; - if (point.y > this.bbox.ur.y) this.bbox.ur.y = point.y; - prevPoint = point; - } // add segment from last to first if last is not the same as first - - - if (firstPoint.x !== prevPoint.x || firstPoint.y !== prevPoint.y) { - this.segments.push(Segment.fromRing(prevPoint, firstPoint, this)); } - } - - _createClass(RingIn, [{ - key: "getSweepEvents", - value: function getSweepEvents() { - var sweepEvents = []; - - for (var i = 0, iMax = this.segments.length; i < iMax; i++) { - var segment = this.segments[i]; + getSweepEvents() { + const sweepEvents = []; + for (let i = 0, iMax = this.segments.length; i < iMax; i++) { + const segment = this.segments[i]; sweepEvents.push(segment.leftSE); sweepEvents.push(segment.rightSE); } - return sweepEvents; } - }]); - - return RingIn; - }(); - var PolyIn = /*#__PURE__*/function () { - function PolyIn(geomPoly, multiPoly) { - _classCallCheck(this, PolyIn); - - if (!Array.isArray(geomPoly)) { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); - } - - this.exteriorRing = new RingIn(geomPoly[0], this, true); // copy by value - - this.bbox = { - ll: { - x: this.exteriorRing.bbox.ll.x, - y: this.exteriorRing.bbox.ll.y - }, - ur: { - x: this.exteriorRing.bbox.ur.x, - y: this.exteriorRing.bbox.ur.y + } + class PolyIn { + constructor(geomPoly, multiPoly) { + if (!Array.isArray(geomPoly)) { + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - }; - this.interiorRings = []; - - for (var i = 1, iMax = geomPoly.length; i < iMax; i++) { - var ring = new RingIn(geomPoly[i], this, false); - if (ring.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = ring.bbox.ll.x; - if (ring.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = ring.bbox.ll.y; - if (ring.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = ring.bbox.ur.x; - if (ring.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = ring.bbox.ur.y; - this.interiorRings.push(ring); + this.exteriorRing = new RingIn(geomPoly[0], this, true); + // copy by value + this.bbox = { + ll: { + x: this.exteriorRing.bbox.ll.x, + y: this.exteriorRing.bbox.ll.y + }, + ur: { + x: this.exteriorRing.bbox.ur.x, + y: this.exteriorRing.bbox.ur.y + } + }; + this.interiorRings = []; + for (let i = 1, iMax = geomPoly.length; i < iMax; i++) { + const ring = new RingIn(geomPoly[i], this, false); + if (ring.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = ring.bbox.ll.x; + if (ring.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = ring.bbox.ll.y; + if (ring.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = ring.bbox.ur.x; + if (ring.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = ring.bbox.ur.y; + this.interiorRings.push(ring); + } + this.multiPoly = multiPoly; } - - this.multiPoly = multiPoly; - } - - _createClass(PolyIn, [{ - key: "getSweepEvents", - value: function getSweepEvents() { - var sweepEvents = this.exteriorRing.getSweepEvents(); - - for (var i = 0, iMax = this.interiorRings.length; i < iMax; i++) { - var ringSweepEvents = this.interiorRings[i].getSweepEvents(); - - for (var j = 0, jMax = ringSweepEvents.length; j < jMax; j++) { + getSweepEvents() { + const sweepEvents = this.exteriorRing.getSweepEvents(); + for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) { + const ringSweepEvents = this.interiorRings[i].getSweepEvents(); + for (let j = 0, jMax = ringSweepEvents.length; j < jMax; j++) { sweepEvents.push(ringSweepEvents[j]); } } - return sweepEvents; } - }]); - - return PolyIn; - }(); - var MultiPolyIn = /*#__PURE__*/function () { - function MultiPolyIn(geom, isSubject) { - _classCallCheck(this, MultiPolyIn); - - if (!Array.isArray(geom)) { - throw new Error('Input geometry is not a valid Polygon or MultiPolygon'); - } - - try { - // if the input looks like a polygon, convert it to a multipolygon - if (typeof geom[0][0][0] === 'number') geom = [geom]; - } catch (ex) {// The input is either malformed or has empty arrays. - // In either case, it will be handled later on. - } - - this.polys = []; - this.bbox = { - ll: { - x: Number.POSITIVE_INFINITY, - y: Number.POSITIVE_INFINITY - }, - ur: { - x: Number.NEGATIVE_INFINITY, - y: Number.NEGATIVE_INFINITY + } + class MultiPolyIn { + constructor(geom, isSubject) { + if (!Array.isArray(geom)) { + throw new Error("Input geometry is not a valid Polygon or MultiPolygon"); } - }; - - for (var i = 0, iMax = geom.length; i < iMax; i++) { - var poly = new PolyIn(geom[i], this); - if (poly.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = poly.bbox.ll.x; - if (poly.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = poly.bbox.ll.y; - if (poly.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = poly.bbox.ur.x; - if (poly.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = poly.bbox.ur.y; - this.polys.push(poly); + try { + // if the input looks like a polygon, convert it to a multipolygon + if (typeof geom[0][0][0] === "number") geom = [geom]; + } catch (ex) { + // The input is either malformed or has empty arrays. + // In either case, it will be handled later on. + } + this.polys = []; + this.bbox = { + ll: { + x: Number.POSITIVE_INFINITY, + y: Number.POSITIVE_INFINITY + }, + ur: { + x: Number.NEGATIVE_INFINITY, + y: Number.NEGATIVE_INFINITY + } + }; + for (let i = 0, iMax = geom.length; i < iMax; i++) { + const poly = new PolyIn(geom[i], this); + if (poly.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = poly.bbox.ll.x; + if (poly.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = poly.bbox.ll.y; + if (poly.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = poly.bbox.ur.x; + if (poly.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = poly.bbox.ur.y; + this.polys.push(poly); + } + this.isSubject = isSubject; } - - this.isSubject = isSubject; - } - - _createClass(MultiPolyIn, [{ - key: "getSweepEvents", - value: function getSweepEvents() { - var sweepEvents = []; - - for (var i = 0, iMax = this.polys.length; i < iMax; i++) { - var polySweepEvents = this.polys[i].getSweepEvents(); - - for (var j = 0, jMax = polySweepEvents.length; j < jMax; j++) { + getSweepEvents() { + const sweepEvents = []; + for (let i = 0, iMax = this.polys.length; i < iMax; i++) { + const polySweepEvents = this.polys[i].getSweepEvents(); + for (let j = 0, jMax = polySweepEvents.length; j < jMax; j++) { sweepEvents.push(polySweepEvents[j]); } } - return sweepEvents; } - }]); - - return MultiPolyIn; - }(); - - var RingOut = /*#__PURE__*/function () { - _createClass(RingOut, null, [{ - key: "factory", + } + class RingOut { /* Given the segments from the sweep line pass, compute & return a series * of closed rings from all the segments marked to be part of the result */ - value: function factory(allSegments) { - var ringsOut = []; - - for (var i = 0, iMax = allSegments.length; i < iMax; i++) { - var segment = allSegments[i]; + static factory(allSegments) { + const ringsOut = []; + for (let i = 0, iMax = allSegments.length; i < iMax; i++) { + const segment = allSegments[i]; if (!segment.isInResult() || segment.ringOut) continue; - var prevEvent = null; - var event = segment.leftSE; - var nextEvent = segment.rightSE; - var events = [event]; - var startingPoint = event.point; - var intersectionLEs = []; - /* Walk the chain of linked events to form a closed ring */ + let prevEvent = null; + let event = segment.leftSE; + let nextEvent = segment.rightSE; + const events = [event]; + const startingPoint = event.point; + const intersectionLEs = []; + /* Walk the chain of linked events to form a closed ring */ while (true) { prevEvent = event; event = nextEvent; events.push(event); - /* Is the ring complete? */ + /* Is the ring complete? */ if (event.point === startingPoint) break; - while (true) { - var availableLEs = event.getAvailableLinkedEvents(); - /* Did we hit a dead end? This shouldn't happen. Indicates some earlier - * part of the algorithm malfunctioned... please file a bug report. */ + const availableLEs = event.getAvailableLinkedEvents(); + /* Did we hit a dead end? This shouldn't happen. + * Indicates some earlier part of the algorithm malfunctioned. */ if (availableLEs.length === 0) { - var firstPt = events[0].point; - var lastPt = events[events.length - 1].point; - throw new Error("Unable to complete output ring starting at [".concat(firstPt.x, ",") + " ".concat(firstPt.y, "]. Last matching segment found ends at") + " [".concat(lastPt.x, ", ").concat(lastPt.y, "].")); + const firstPt = events[0].point; + const lastPt = events[events.length - 1].point; + throw new Error(`Unable to complete output ring starting at [${firstPt.x},` + ` ${firstPt.y}]. Last matching segment found ends at` + ` [${lastPt.x}, ${lastPt.y}].`); } - /* Only one way to go, so cotinue on the path */ - + /* Only one way to go, so cotinue on the path */ if (availableLEs.length === 1) { nextEvent = availableLEs[0].otherSE; break; } - /* We must have an intersection. Check for a completed loop */ - - var indexLE = null; - - for (var j = 0, jMax = intersectionLEs.length; j < jMax; j++) { + /* We must have an intersection. Check for a completed loop */ + let indexLE = null; + for (let j = 0, jMax = intersectionLEs.length; j < jMax; j++) { if (intersectionLEs[j].point === event.point) { indexLE = j; break; } } /* Found a completed loop. Cut that off and make a ring */ - - if (indexLE !== null) { - var intersectionLE = intersectionLEs.splice(indexLE)[0]; - var ringEvents = events.splice(intersectionLE.index); + const intersectionLE = intersectionLEs.splice(indexLE)[0]; + const ringEvents = events.splice(intersectionLE.index); ringEvents.unshift(ringEvents[0].otherSE); ringsOut.push(new RingOut(ringEvents.reverse())); continue; } /* register the intersection */ - - intersectionLEs.push({ index: events.length, point: event.point }); /* Choose the left-most option to continue the walk */ - - var comparator = event.getLeftmostComparator(prevEvent); + const comparator = event.getLeftmostComparator(prevEvent); nextEvent = availableLEs.sort(comparator)[0].otherSE; break; } } - ringsOut.push(new RingOut(events)); } - return ringsOut; } - }]); - - function RingOut(events) { - _classCallCheck(this, RingOut); - - this.events = events; - - for (var i = 0, iMax = events.length; i < iMax; i++) { - events[i].segment.ringOut = this; + constructor(events) { + this.events = events; + for (let i = 0, iMax = events.length; i < iMax; i++) { + events[i].segment.ringOut = this; + } + this.poly = null; } - - this.poly = null; - } - - _createClass(RingOut, [{ - key: "getGeom", - value: function getGeom() { + getGeom() { // Remove superfluous points (ie extra points along a straight line), - var prevPt = this.events[0].point; - var points = [prevPt]; - - for (var i = 1, iMax = this.events.length - 1; i < iMax; i++) { - var _pt = this.events[i].point; - var _nextPt = this.events[i + 1].point; - if (compareVectorAngles(_pt, prevPt, _nextPt) === 0) continue; - points.push(_pt); - prevPt = _pt; - } // ring was all (within rounding error of angle calc) colinear points - + let prevPt = this.events[0].point; + const points = [prevPt]; + for (let i = 1, iMax = this.events.length - 1; i < iMax; i++) { + const pt = this.events[i].point; + const nextPt = this.events[i + 1].point; + if (compareVectorAngles(pt, prevPt, nextPt) === 0) continue; + points.push(pt); + prevPt = pt; + } - if (points.length === 1) return null; // check if the starting point is necessary + // ring was all (within rounding error of angle calc) colinear points + if (points.length === 1) return null; - var pt = points[0]; - var nextPt = points[1]; + // check if the starting point is necessary + const pt = points[0]; + const nextPt = points[1]; if (compareVectorAngles(pt, prevPt, nextPt) === 0) points.shift(); points.push(points[0]); - var step = this.isExteriorRing() ? 1 : -1; - var iStart = this.isExteriorRing() ? 0 : points.length - 1; - var iEnd = this.isExteriorRing() ? points.length : -1; - var orderedPoints = []; - - for (var _i = iStart; _i != iEnd; _i += step) { - orderedPoints.push([points[_i].x, points[_i].y]); - } - + const step = this.isExteriorRing() ? 1 : -1; + const iStart = this.isExteriorRing() ? 0 : points.length - 1; + const iEnd = this.isExteriorRing() ? points.length : -1; + const orderedPoints = []; + for (let i = iStart; i != iEnd; i += step) orderedPoints.push([points[i].x, points[i].y]); return orderedPoints; } - }, { - key: "isExteriorRing", - value: function isExteriorRing() { + isExteriorRing() { if (this._isExteriorRing === undefined) { - var enclosing = this.enclosingRing(); + const enclosing = this.enclosingRing(); this._isExteriorRing = enclosing ? !enclosing.isExteriorRing() : true; } - return this._isExteriorRing; } - }, { - key: "enclosingRing", - value: function enclosingRing() { + enclosingRing() { if (this._enclosingRing === undefined) { this._enclosingRing = this._calcEnclosingRing(); } - return this._enclosingRing; } - /* Returns the ring that encloses this one, if any */ - }, { - key: "_calcEnclosingRing", - value: function _calcEnclosingRing() { + /* Returns the ring that encloses this one, if any */ + _calcEnclosingRing() { // start with the ealier sweep line event so that the prevSeg // chain doesn't lead us inside of a loop of ours - var leftMostEvt = this.events[0]; - - for (var i = 1, iMax = this.events.length; i < iMax; i++) { - var evt = this.events[i]; + let leftMostEvt = this.events[0]; + for (let i = 1, iMax = this.events.length; i < iMax; i++) { + const evt = this.events[i]; if (SweepEvent.compare(leftMostEvt, evt) > 0) leftMostEvt = evt; } - - var prevSeg = leftMostEvt.segment.prevInResult(); - var prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; - + let prevSeg = leftMostEvt.segment.prevInResult(); + let prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; while (true) { // no segment found, thus no ring can enclose us - if (!prevSeg) return null; // no segments below prev segment found, thus the ring of the prev + if (!prevSeg) return null; + + // no segments below prev segment found, thus the ring of the prev // segment must loop back around and enclose us + if (!prevPrevSeg) return prevSeg.ringOut; - if (!prevPrevSeg) return prevSeg.ringOut; // if the two segments are of different rings, the ring of the prev + // if the two segments are of different rings, the ring of the prev // segment must either loop around us or the ring of the prev prev // seg, which would make us and the ring of the prev peers - if (prevPrevSeg.ringOut !== prevSeg.ringOut) { if (prevPrevSeg.ringOut.enclosingRing() !== prevSeg.ringOut) { return prevSeg.ringOut; } else return prevSeg.ringOut.enclosingRing(); - } // two segments are from the same ring, so this was a penisula - // of that ring. iterate downward, keep searching - + } + // two segments are from the same ring, so this was a penisula + // of that ring. iterate downward, keep searching prevSeg = prevPrevSeg.prevInResult(); prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null; } } - }]); - - return RingOut; - }(); - var PolyOut = /*#__PURE__*/function () { - function PolyOut(exteriorRing) { - _classCallCheck(this, PolyOut); - - this.exteriorRing = exteriorRing; - exteriorRing.poly = this; - this.interiorRings = []; } - - _createClass(PolyOut, [{ - key: "addInterior", - value: function addInterior(ring) { + class PolyOut { + constructor(exteriorRing) { + this.exteriorRing = exteriorRing; + exteriorRing.poly = this; + this.interiorRings = []; + } + addInterior(ring) { this.interiorRings.push(ring); ring.poly = this; } - }, { - key: "getGeom", - value: function getGeom() { - var geom = [this.exteriorRing.getGeom()]; // exterior ring was all (within rounding error of angle calc) colinear points - + getGeom() { + const geom = [this.exteriorRing.getGeom()]; + // exterior ring was all (within rounding error of angle calc) colinear points if (geom[0] === null) return null; - - for (var i = 0, iMax = this.interiorRings.length; i < iMax; i++) { - var ringGeom = this.interiorRings[i].getGeom(); // interior ring was all (within rounding error of angle calc) colinear points - + for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) { + const ringGeom = this.interiorRings[i].getGeom(); + // interior ring was all (within rounding error of angle calc) colinear points if (ringGeom === null) continue; geom.push(ringGeom); } - return geom; } - }]); - - return PolyOut; - }(); - var MultiPolyOut = /*#__PURE__*/function () { - function MultiPolyOut(rings) { - _classCallCheck(this, MultiPolyOut); - - this.rings = rings; - this.polys = this._composePolys(rings); } - - _createClass(MultiPolyOut, [{ - key: "getGeom", - value: function getGeom() { - var geom = []; - - for (var i = 0, iMax = this.polys.length; i < iMax; i++) { - var polyGeom = this.polys[i].getGeom(); // exterior ring was all (within rounding error of angle calc) colinear points - + class MultiPolyOut { + constructor(rings) { + this.rings = rings; + this.polys = this._composePolys(rings); + } + getGeom() { + const geom = []; + for (let i = 0, iMax = this.polys.length; i < iMax; i++) { + const polyGeom = this.polys[i].getGeom(); + // exterior ring was all (within rounding error of angle calc) colinear points if (polyGeom === null) continue; geom.push(polyGeom); } - return geom; } - }, { - key: "_composePolys", - value: function _composePolys(rings) { - var polys = []; - - for (var i = 0, iMax = rings.length; i < iMax; i++) { - var ring = rings[i]; + _composePolys(rings) { + const polys = []; + for (let i = 0, iMax = rings.length; i < iMax; i++) { + const ring = rings[i]; if (ring.poly) continue; if (ring.isExteriorRing()) polys.push(new PolyOut(ring));else { - var enclosingRing = ring.enclosingRing(); + const enclosingRing = ring.enclosingRing(); if (!enclosingRing.poly) polys.push(new PolyOut(enclosingRing)); enclosingRing.poly.addInterior(ring); } } - return polys; } - }]); - - return MultiPolyOut; - }(); - - /** - * NOTE: We must be careful not to change any segments while - * they are in the SplayTree. AFAIK, there's no way to tell - * the tree to rebalance itself - thus before splitting - * a segment that's in the tree, we remove it from the tree, - * do the split, then re-insert it. (Even though splitting a - * segment *shouldn't* change its correct position in the - * sweep line tree, the reality is because of rounding errors, - * it sometimes does.) - */ - - var SweepLine = /*#__PURE__*/function () { - function SweepLine(queue) { - var comparator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Segment.compare; + } - _classCallCheck(this, SweepLine); + /** + * NOTE: We must be careful not to change any segments while + * they are in the SplayTree. AFAIK, there's no way to tell + * the tree to rebalance itself - thus before splitting + * a segment that's in the tree, we remove it from the tree, + * do the split, then re-insert it. (Even though splitting a + * segment *shouldn't* change its correct position in the + * sweep line tree, the reality is because of rounding errors, + * it sometimes does.) + */ - this.queue = queue; - this.tree = new Tree(comparator); - this.segments = []; - } + class SweepLine { + constructor(queue) { + let comparator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Segment.compare; + this.queue = queue; + this.tree = new Tree(comparator); + this.segments = []; + } + process(event) { + const segment = event.segment; + const newEvents = []; - _createClass(SweepLine, [{ - key: "process", - value: function process(event) { - var segment = event.segment; - var newEvents = []; // if we've already been consumed by another segment, + // if we've already been consumed by another segment, // clean up our body parts and get out - if (event.consumedBy) { if (event.isLeft) this.queue.remove(event.otherSE);else this.tree.remove(segment); return newEvents; } - - var node = event.isLeft ? this.tree.insert(segment) : this.tree.find(segment); - if (!node) throw new Error("Unable to find segment #".concat(segment.id, " ") + "[".concat(segment.leftSE.point.x, ", ").concat(segment.leftSE.point.y, "] -> ") + "[".concat(segment.rightSE.point.x, ", ").concat(segment.rightSE.point.y, "] ") + 'in SweepLine tree. Please submit a bug report.'); - var prevNode = node; - var nextNode = node; - var prevSeg = undefined; - var nextSeg = undefined; // skip consumed segments still in tree - + const node = event.isLeft ? this.tree.add(segment) : this.tree.find(segment); + if (!node) throw new Error(`Unable to find segment #${segment.id} ` + `[${segment.leftSE.point.x}, ${segment.leftSE.point.y}] -> ` + `[${segment.rightSE.point.x}, ${segment.rightSE.point.y}] ` + "in SweepLine tree."); + let prevNode = node; + let nextNode = node; + let prevSeg = undefined; + let nextSeg = undefined; + + // skip consumed segments still in tree while (prevSeg === undefined) { prevNode = this.tree.prev(prevNode); if (prevNode === null) prevSeg = null;else if (prevNode.key.consumedBy === undefined) prevSeg = prevNode.key; - } // skip consumed segments still in tree - + } + // skip consumed segments still in tree while (nextSeg === undefined) { nextNode = this.tree.next(nextNode); if (nextNode === null) nextSeg = null;else if (nextNode.key.consumedBy === undefined) nextSeg = nextNode.key; } - if (event.isLeft) { // Check for intersections against the previous segment in the sweep line - var prevMySplitter = null; - + let prevMySplitter = null; if (prevSeg) { - var prevInter = prevSeg.getIntersection(segment); - + const prevInter = prevSeg.getIntersection(segment); if (prevInter !== null) { if (!segment.isAnEndpoint(prevInter)) prevMySplitter = prevInter; - if (!prevSeg.isAnEndpoint(prevInter)) { - var newEventsFromSplit = this._splitSafely(prevSeg, prevInter); - - for (var i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + const newEventsFromSplit = this._splitSafely(prevSeg, prevInter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { newEvents.push(newEventsFromSplit[i]); } } } - } // Check for intersections against the next segment in the sweep line - - - var nextMySplitter = null; + } + // Check for intersections against the next segment in the sweep line + let nextMySplitter = null; if (nextSeg) { - var nextInter = nextSeg.getIntersection(segment); - + const nextInter = nextSeg.getIntersection(segment); if (nextInter !== null) { if (!segment.isAnEndpoint(nextInter)) nextMySplitter = nextInter; - if (!nextSeg.isAnEndpoint(nextInter)) { - var _newEventsFromSplit = this._splitSafely(nextSeg, nextInter); - - for (var _i = 0, _iMax = _newEventsFromSplit.length; _i < _iMax; _i++) { - newEvents.push(_newEventsFromSplit[_i]); + const newEventsFromSplit = this._splitSafely(nextSeg, nextInter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } } } - } // For simplicity, even if we find more than one intersection we only + } + + // For simplicity, even if we find more than one intersection we only // spilt on the 'earliest' (sweep-line style) of the intersections. // The other intersection will be handled in a future process(). - - if (prevMySplitter !== null || nextMySplitter !== null) { - var mySplitter = null; + let mySplitter = null; if (prevMySplitter === null) mySplitter = nextMySplitter;else if (nextMySplitter === null) mySplitter = prevMySplitter;else { - var cmpSplitters = SweepEvent.comparePoints(prevMySplitter, nextMySplitter); + const cmpSplitters = SweepEvent.comparePoints(prevMySplitter, nextMySplitter); mySplitter = cmpSplitters <= 0 ? prevMySplitter : nextMySplitter; - } // Rounding errors can cause changes in ordering, - // so remove afected segments and right sweep events before splitting + } + // Rounding errors can cause changes in ordering, + // so remove afected segments and right sweep events before splitting this.queue.remove(segment.rightSE); newEvents.push(segment.rightSE); - - var _newEventsFromSplit2 = segment.split(mySplitter); - - for (var _i2 = 0, _iMax2 = _newEventsFromSplit2.length; _i2 < _iMax2; _i2++) { - newEvents.push(_newEventsFromSplit2[_i2]); + const newEventsFromSplit = segment.split(mySplitter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } } - if (newEvents.length > 0) { // We found some intersections, so re-do the current event to // make sure sweep line ordering is totally consistent for later @@ -2340,218 +2321,176 @@ } } else { // event.isRight + // since we're about to be removed from the sweep line, check for // intersections between our previous and next segments if (prevSeg && nextSeg) { - var inter = prevSeg.getIntersection(nextSeg); - + const inter = prevSeg.getIntersection(nextSeg); if (inter !== null) { if (!prevSeg.isAnEndpoint(inter)) { - var _newEventsFromSplit3 = this._splitSafely(prevSeg, inter); - - for (var _i3 = 0, _iMax3 = _newEventsFromSplit3.length; _i3 < _iMax3; _i3++) { - newEvents.push(_newEventsFromSplit3[_i3]); + const newEventsFromSplit = this._splitSafely(prevSeg, inter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } } - if (!nextSeg.isAnEndpoint(inter)) { - var _newEventsFromSplit4 = this._splitSafely(nextSeg, inter); - - for (var _i4 = 0, _iMax4 = _newEventsFromSplit4.length; _i4 < _iMax4; _i4++) { - newEvents.push(_newEventsFromSplit4[_i4]); + const newEventsFromSplit = this._splitSafely(nextSeg, inter); + for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) { + newEvents.push(newEventsFromSplit[i]); } } } } - this.tree.remove(segment); } - return newEvents; } + /* Safely split a segment that is currently in the datastructures * IE - a segment other than the one that is currently being processed. */ - - }, { - key: "_splitSafely", - value: function _splitSafely(seg, pt) { + _splitSafely(seg, pt) { // Rounding errors can cause changes in ordering, // so remove afected segments and right sweep events before splitting // removeNode() doesn't work, so have re-find the seg // https://github.com/w8r/splay-tree/pull/5 this.tree.remove(seg); - var rightSE = seg.rightSE; + const rightSE = seg.rightSE; this.queue.remove(rightSE); - var newEvents = seg.split(pt); - newEvents.push(rightSE); // splitting can trigger consumption - - if (seg.consumedBy === undefined) this.tree.insert(seg); + const newEvents = seg.split(pt); + newEvents.push(rightSE); + // splitting can trigger consumption + if (seg.consumedBy === undefined) this.tree.add(seg); return newEvents; } - }]); - - return SweepLine; - }(); - - var POLYGON_CLIPPING_MAX_QUEUE_SIZE = typeof process !== 'undefined' && process.env.POLYGON_CLIPPING_MAX_QUEUE_SIZE || 1000000; - var POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS = typeof process !== 'undefined' && process.env.POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS || 1000000; - var Operation = /*#__PURE__*/function () { - function Operation() { - _classCallCheck(this, Operation); } - _createClass(Operation, [{ - key: "run", - value: function run(type, geom, moreGeoms) { + // Limits on iterative processes to prevent infinite loops - usually caused by floating-point math round-off errors. + const POLYGON_CLIPPING_MAX_QUEUE_SIZE = typeof process !== "undefined" && process.env.POLYGON_CLIPPING_MAX_QUEUE_SIZE || 1000000; + const POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS = typeof process !== "undefined" && process.env.POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS || 1000000; + class Operation { + run(type, geom, moreGeoms) { operation.type = type; rounder.reset(); - /* Convert inputs to MultiPoly objects */ - - var multipolys = [new MultiPolyIn(geom, true)]; - for (var i = 0, iMax = moreGeoms.length; i < iMax; i++) { + /* Convert inputs to MultiPoly objects */ + const multipolys = [new MultiPolyIn(geom, true)]; + for (let i = 0, iMax = moreGeoms.length; i < iMax; i++) { multipolys.push(new MultiPolyIn(moreGeoms[i], false)); } - operation.numMultiPolys = multipolys.length; + /* BBox optimization for difference operation * If the bbox of a multipolygon that's part of the clipping doesn't * intersect the bbox of the subject at all, we can just drop that * multiploygon. */ - - if (operation.type === 'difference') { + if (operation.type === "difference") { // in place removal - var subject = multipolys[0]; - var _i = 1; - - while (_i < multipolys.length) { - if (getBboxOverlap(multipolys[_i].bbox, subject.bbox) !== null) _i++;else multipolys.splice(_i, 1); + const subject = multipolys[0]; + let i = 1; + while (i < multipolys.length) { + if (getBboxOverlap(multipolys[i].bbox, subject.bbox) !== null) i++;else multipolys.splice(i, 1); } } + /* BBox optimization for intersection operation * If we can find any pair of multipolygons whose bbox does not overlap, * then the result will be empty. */ - - - if (operation.type === 'intersection') { + if (operation.type === "intersection") { // TODO: this is O(n^2) in number of polygons. By sorting the bboxes, // it could be optimized to O(n * ln(n)) - for (var _i2 = 0, _iMax = multipolys.length; _i2 < _iMax; _i2++) { - var mpA = multipolys[_i2]; - - for (var j = _i2 + 1, jMax = multipolys.length; j < jMax; j++) { + for (let i = 0, iMax = multipolys.length; i < iMax; i++) { + const mpA = multipolys[i]; + for (let j = i + 1, jMax = multipolys.length; j < jMax; j++) { if (getBboxOverlap(mpA.bbox, multipolys[j].bbox) === null) return []; } } } - /* Put segment endpoints in a priority queue */ - - - var queue = new Tree(SweepEvent.compare); - - for (var _i3 = 0, _iMax2 = multipolys.length; _i3 < _iMax2; _i3++) { - var sweepEvents = multipolys[_i3].getSweepEvents(); - - for (var _j = 0, _jMax = sweepEvents.length; _j < _jMax; _j++) { - queue.insert(sweepEvents[_j]); + /* Put segment endpoints in a priority queue */ + const queue = new Tree(SweepEvent.compare); + for (let i = 0, iMax = multipolys.length; i < iMax; i++) { + const sweepEvents = multipolys[i].getSweepEvents(); + for (let j = 0, jMax = sweepEvents.length; j < jMax; j++) { + queue.insert(sweepEvents[j]); if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) { // prevents an infinite loop, an otherwise common manifestation of bugs - throw new Error('Infinite loop when putting segment endpoints in a priority queue ' + '(queue size too big). Please file a bug report.'); + throw new Error("Infinite loop when putting segment endpoints in a priority queue " + "(queue size too big)."); } } } - /* Pass the sweep line over those endpoints */ - - - var sweepLine = new SweepLine(queue); - var prevQueueSize = queue.size; - var node = queue.pop(); + /* Pass the sweep line over those endpoints */ + const sweepLine = new SweepLine(queue); + let prevQueueSize = queue.size; + let node = queue.pop(); while (node) { - var evt = node.key; - + const evt = node.key; if (queue.size === prevQueueSize) { // prevents an infinite loop, an otherwise common manifestation of bugs - var seg = evt.segment; - throw new Error("Unable to pop() ".concat(evt.isLeft ? 'left' : 'right', " SweepEvent ") + "[".concat(evt.point.x, ", ").concat(evt.point.y, "] from segment #").concat(seg.id, " ") + "[".concat(seg.leftSE.point.x, ", ").concat(seg.leftSE.point.y, "] -> ") + "[".concat(seg.rightSE.point.x, ", ").concat(seg.rightSE.point.y, "] from queue. ") + 'Please file a bug report.'); + const seg = evt.segment; + throw new Error(`Unable to pop() ${evt.isLeft ? "left" : "right"} SweepEvent ` + `[${evt.point.x}, ${evt.point.y}] from segment #${seg.id} ` + `[${seg.leftSE.point.x}, ${seg.leftSE.point.y}] -> ` + `[${seg.rightSE.point.x}, ${seg.rightSE.point.y}] from queue.`); } - if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) { // prevents an infinite loop, an otherwise common manifestation of bugs - throw new Error('Infinite loop when passing sweep line over endpoints ' + '(queue size too big). Please file a bug report.'); + throw new Error("Infinite loop when passing sweep line over endpoints " + "(queue size too big)."); } - if (sweepLine.segments.length > POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS) { // prevents an infinite loop, an otherwise common manifestation of bugs - throw new Error('Infinite loop when passing sweep line over endpoints ' + '(too many sweep line segments). Please file a bug report.'); + throw new Error("Infinite loop when passing sweep line over endpoints " + "(too many sweep line segments)."); } - - var newEvents = sweepLine.process(evt); - - for (var _i4 = 0, _iMax3 = newEvents.length; _i4 < _iMax3; _i4++) { - var _evt = newEvents[_i4]; - if (_evt.consumedBy === undefined) queue.insert(_evt); + const newEvents = sweepLine.process(evt); + for (let i = 0, iMax = newEvents.length; i < iMax; i++) { + const evt = newEvents[i]; + if (evt.consumedBy === undefined) queue.insert(evt); } - prevQueueSize = queue.size; node = queue.pop(); - } // free some memory we don't need anymore - + } + // free some memory we don't need anymore rounder.reset(); - /* Collect and compile segments we're keeping into a multipolygon */ - var ringsOut = RingOut.factory(sweepLine.segments); - var result = new MultiPolyOut(ringsOut); + /* Collect and compile segments we're keeping into a multipolygon */ + const ringsOut = RingOut.factory(sweepLine.segments); + const result = new MultiPolyOut(ringsOut); return result.getGeom(); } - }]); - - return Operation; - }(); // singleton available by import - - var operation = new Operation(); - - var union = function union(geom) { - for (var _len = arguments.length, moreGeoms = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - moreGeoms[_key - 1] = arguments[_key]; - } - - return operation.run('union', geom, moreGeoms); - }; - - var intersection$1 = function intersection(geom) { - for (var _len2 = arguments.length, moreGeoms = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - moreGeoms[_key2 - 1] = arguments[_key2]; - } - - return operation.run('intersection', geom, moreGeoms); - }; - - var xor = function xor(geom) { - for (var _len3 = arguments.length, moreGeoms = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { - moreGeoms[_key3 - 1] = arguments[_key3]; } - return operation.run('xor', geom, moreGeoms); - }; + // singleton available by import + const operation = new Operation(); - var difference = function difference(subjectGeom) { - for (var _len4 = arguments.length, clippingGeoms = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) { - clippingGeoms[_key4 - 1] = arguments[_key4]; - } - - return operation.run('difference', subjectGeom, clippingGeoms); - }; - - var index = { - union: union, - intersection: intersection$1, - xor: xor, - difference: difference - }; + const union = function (geom) { + for (var _len = arguments.length, moreGeoms = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + moreGeoms[_key - 1] = arguments[_key]; + } + return operation.run("union", geom, moreGeoms); + }; + const intersection = function (geom) { + for (var _len2 = arguments.length, moreGeoms = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + moreGeoms[_key2 - 1] = arguments[_key2]; + } + return operation.run("intersection", geom, moreGeoms); + }; + const xor = function (geom) { + for (var _len3 = arguments.length, moreGeoms = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { + moreGeoms[_key3 - 1] = arguments[_key3]; + } + return operation.run("xor", geom, moreGeoms); + }; + const difference = function (subjectGeom) { + for (var _len4 = arguments.length, clippingGeoms = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) { + clippingGeoms[_key4 - 1] = arguments[_key4]; + } + return operation.run("difference", subjectGeom, clippingGeoms); + }; + var index = { + union: union, + intersection: intersection, + xor: xor, + difference: difference + }; - return index; + return index; -}))); +})); diff --git a/dist/polygon-clipping.umd.min.js b/dist/polygon-clipping.umd.min.js index e07a1d2..f3a6f7d 100644 --- a/dist/polygon-clipping.umd.min.js +++ b/dist/polygon-clipping.umd.min.js @@ -1,10 +1,24 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).polygonClipping=e()}(this,(function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var r=0;r - * @license MIT - * @preserve - */var n=function(t,e){this.next=null,this.key=t,this.data=e,this.left=null,this.right=null};function i(t,e){return t>e?1:t0))break;if(null===e.right)break;if(r(t,e.right.key)>0){l=e.right;if(e.right=l.left,l.left=e,null===(e=l).right)break}o.right=e,o=e,e=e.right}}return o.right=e.left,s.left=e.right,e.left=i.right,e.right=i.left,e}function s(t,e,r,i){var s=new n(t,e);if(null===r)return s.left=s.right=null,s;var u=i(t,(r=o(t,r,i)).key);return u<0?(s.left=r.left,s.right=r,r.left=null):u>=0&&(s.right=r.right,s.left=r,r.right=null),s}function u(t,e,r){var n=null,i=null;if(e){var s=r((e=o(t,e,r)).key,t);0===s?(n=e.left,i=e.right):s<0?(i=e.right,e.right=null,n=e):(n=e.left,e.left=null,i=e)}return{left:n,right:i}}function l(t,e,r,n,i){if(t){n(e+(r?"└── ":"├── ")+i(t)+"\n");var o=e+(r?" ":"│ ");t.left&&l(t.left,o,!1,n,i),t.right&&l(t.right,o,!0,n,i)}}var h=function(){function t(t){void 0===t&&(t=i),this._root=null,this._size=0,this._comparator=t}return t.prototype.insert=function(t,e){return this._size++,this._root=s(t,e,this._root,this._comparator)},t.prototype.add=function(t,e){var r=new n(t,e);null===this._root&&(r.left=r.right=null,this._size++,this._root=r);var i=this._comparator,s=o(t,this._root,i),u=i(t,s.key);return 0===u?this._root=s:(u<0?(r.left=s.left,r.right=s,s.left=null):u>0&&(r.right=s.right,r.left=s,s.right=null),this._size++,this._root=r),this._root},t.prototype.remove=function(t){this._root=this._remove(t,this._root,this._comparator)},t.prototype._remove=function(t,e,r){var n;return null===e?null:0===r(t,(e=o(t,e,r)).key)?(null===e.left?n=e.right:(n=o(t,e.left,r)).right=e.right,this._size--,n):e},t.prototype.pop=function(){var t=this._root;if(t){for(;t.left;)t=t.left;return this._root=o(t.key,this._root,this._comparator),this._root=this._remove(t.key,this._root,this._comparator),{key:t.key,data:t.data}}return null},t.prototype.findStatic=function(t){for(var e=this._root,r=this._comparator;e;){var n=r(t,e.key);if(0===n)return e;e=n<0?e.left:e.right}return null},t.prototype.find=function(t){return this._root&&(this._root=o(t,this._root,this._comparator),0!==this._comparator(t,this._root.key))?null:this._root},t.prototype.contains=function(t){for(var e=this._root,r=this._comparator;e;){var n=r(t,e.key);if(0===n)return!0;e=n<0?e.left:e.right}return!1},t.prototype.forEach=function(t,e){for(var r=this._root,n=[],i=!1;!i;)null!==r?(n.push(r),r=r.left):0!==n.length?(r=n.pop(),t.call(e,r),r=r.right):i=!0;return this},t.prototype.range=function(t,e,r,n){for(var i=[],o=this._comparator,s=this._root;0!==i.length||s;)if(s)i.push(s),s=s.left;else{if(o((s=i.pop()).key,e)>0)break;if(o(s.key,t)>=0&&r.call(n,s))return this;s=s.right}return this},t.prototype.keys=function(){var t=[];return this.forEach((function(e){var r=e.key;return t.push(r)})),t},t.prototype.values=function(){var t=[];return this.forEach((function(e){var r=e.data;return t.push(r)})),t},t.prototype.min=function(){return this._root?this.minNode(this._root).key:null},t.prototype.max=function(){return this._root?this.maxNode(this._root).key:null},t.prototype.minNode=function(t){if(void 0===t&&(t=this._root),t)for(;t.left;)t=t.left;return t},t.prototype.maxNode=function(t){if(void 0===t&&(t=this._root),t)for(;t.right;)t=t.right;return t},t.prototype.at=function(t){for(var e=this._root,r=!1,n=0,i=[];!r;)if(e)i.push(e),e=e.left;else if(i.length>0){if(e=i.pop(),n===t)return e;n++,e=e.right}else r=!0;return null},t.prototype.next=function(t){var e=this._root,r=null;if(t.right){for(r=t.right;r.left;)r=r.left;return r}for(var n=this._comparator;e;){var i=n(t.key,e.key);if(0===i)break;i<0?(r=e,e=e.left):e=e.right}return r},t.prototype.prev=function(t){var e=this._root,r=null;if(null!==t.left){for(r=t.left;r.right;)r=r.right;return r}for(var n=this._comparator;e;){var i=n(t.key,e.key);if(0===i)break;i<0?e=e.left:(r=e,e=e.right)}return r},t.prototype.clear=function(){return this._root=null,this._size=0,this},t.prototype.toList=function(){return function(t){var e=t,r=[],i=!1,o=new n(null,null),s=o;for(;!i;)e?(r.push(e),e=e.left):r.length>0?e=(e=s=s.next=r.pop()).right:i=!0;return s.next=null,o.next}(this._root)},t.prototype.load=function(t,e,r){void 0===e&&(e=[]),void 0===r&&(r=!1);var i=t.length,o=this._comparator;if(r&&p(t,e,0,i-1,o),null===this._root)this._root=f(t,e,0,i),this._size=i;else{var s=function(t,e,r){var i=new n(null,null),o=i,s=t,u=e;for(;null!==s&&null!==u;)r(s.key,u.key)<0?(o.next=s,s=s.next):(o.next=u,u=u.next),o=o.next;null!==s?o.next=s:null!==u&&(o.next=u);return i.next}(this.toList(),function(t,e){for(var r=new n(null,null),i=r,o=0;o0){var s=r+Math.floor(o/2),u=t[s],l=e[s],h=new n(u,l);return h.left=f(t,e,r,s),h.right=f(t,e,s+1,i),h}return null}function a(t,e,r){var n=r-e;if(n>0){var i=e+Math.floor(n/2),o=a(t,e,i),s=t.head;return s.left=o,t.head=t.head.next,s.right=a(t,i+1,r),s}return null}function p(t,e,r,n,i){if(!(r>=n)){for(var o=t[r+n>>1],s=r-1,u=n+1;;){do{s++}while(i(t[s],o)<0);do{u--}while(i(t[u],o)>0);if(s>=u)break;var l=t[s];t[s]=t[u],t[u]=l,l=e[s],e[s]=e[u],e[u]=l}p(t,e,r,u,i),p(t,e,u+1,n,i)}}var y=function(t,e){return t.ll.x<=e.x&&e.x<=t.ur.x&&t.ll.y<=e.y&&e.y<=t.ur.y},g=function(t,e){if(e.ur.xe.x?1:t.ye.y?1:0}}]),r(e,[{key:"link",value:function(t){if(t.point===this.point)throw new Error("Tried to link already linked events");for(var e=t.point.events,r=0,n=e.length;r=0&&l>=0?sh?-1:0:o<0&&l<0?sh?1:0:lo?1:0}}}]),e}(),N=0,O=function(){function e(r,n,i,o){t(this,e),this.id=++N,this.leftSE=r,r.segment=this,r.otherSE=n,this.rightSE=n,n.segment=this,n.otherSE=r,this.rings=i,this.windings=o}return r(e,null,[{key:"compare",value:function(t,e){var r=t.leftSE.point.x,n=e.leftSE.point.x,i=t.rightSE.point.x,o=e.rightSE.point.x;if(os&&u>l)return-1;var f=t.comparePoint(e.leftSE.point);if(f<0)return 1;if(f>0)return-1;var a=e.comparePoint(t.rightSE.point);return 0!==a?a:-1}if(r>n){if(su&&s>h)return 1;var p=e.comparePoint(t.leftSE.point);if(0!==p)return p;var y=t.comparePoint(e.rightSE.point);return y<0?1:y>0?-1:1}if(su)return 1;if(io){var v=t.comparePoint(e.rightSE.point);if(v<0)return 1;if(v>0)return-1}if(i!==o){var c=l-s,x=i-r,b=h-u,m=o-n;if(c>x&&bm)return-1}return i>o?1:ih?1:t.ide.id?1:0}}]),r(e,[{key:"replaceRightSE",value:function(t){this.rightSE=t,this.rightSE.segment=this,this.rightSE.otherSE=this.leftSE,this.leftSE.otherSE=this.rightSE}},{key:"bbox",value:function(){var t=this.leftSE.point.y,e=this.rightSE.point.y;return{ll:{x:this.leftSE.point.x,y:te?t:e}}}},{key:"vector",value:function(){return{x:this.rightSE.point.x-this.leftSE.point.x,y:this.rightSE.point.y-this.leftSE.point.y}}},{key:"isAnEndpoint",value:function(t){return t.x===this.leftSE.point.x&&t.y===this.leftSE.point.y||t.x===this.rightSE.point.x&&t.y===this.rightSE.point.y}},{key:"comparePoint",value:function(t){if(this.isAnEndpoint(t))return 0;var e=this.leftSE.point,r=this.rightSE.point,n=this.vector();if(e.x===r.x)return t.x===e.x?0:t.x0&&u.swapEvents(),P.comparePoints(this.leftSE.point,this.rightSE.point)>0&&this.swapEvents(),n&&(i.checkForConsuming(),o.checkForConsuming()),r}},{key:"swapEvents",value:function(){var t=this.rightSE;this.rightSE=this.leftSE,this.leftSE=t,this.leftSE.isLeft=!0,this.rightSE.isLeft=!1;for(var e=0,r=this.windings.length;e0){var o=r;r=n,n=o}if(r.prev===n){var s=r;r=n,n=s}for(var u=0,l=n.rings.length;u0))throw new Error("Tried to create degenerate segment at [".concat(t.x,", ").concat(t.y,"]"));i=r,o=t,s=-1}return new e(new P(i,!0),new P(o,!1),[n],[s])}}]),e}(),A=function(){function e(r,n,i){if(t(this,e),!Array.isArray(r)||0===r.length)throw new Error("Input geometry is not a valid Polygon or MultiPolygon");if(this.poly=n,this.isExterior=i,this.segments=[],"number"!=typeof r[0][0]||"number"!=typeof r[0][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");var o=d.round(r[0][0],r[0][1]);this.bbox={ll:{x:o.x,y:o.y},ur:{x:o.x,y:o.y}};for(var s=o,u=1,l=r.length;uthis.bbox.ur.x&&(this.bbox.ur.x=h.x),h.y>this.bbox.ur.y&&(this.bbox.ur.y=h.y),s=h)}o.x===s.x&&o.y===s.y||this.segments.push(O.fromRing(s,o,this))}return r(e,[{key:"getSweepEvents",value:function(){for(var t=[],e=0,r=this.segments.length;ethis.bbox.ur.x&&(this.bbox.ur.x=s.bbox.ur.x),s.bbox.ur.y>this.bbox.ur.y&&(this.bbox.ur.y=s.bbox.ur.y),this.interiorRings.push(s)}this.multiPoly=n}return r(e,[{key:"getSweepEvents",value:function(){for(var t=this.exteriorRing.getSweepEvents(),e=0,r=this.interiorRings.length;ethis.bbox.ur.x&&(this.bbox.ur.x=s.bbox.ur.x),s.bbox.ur.y>this.bbox.ur.y&&(this.bbox.ur.y=s.bbox.ur.y),this.polys.push(s)}this.isSubject=n}return r(e,[{key:"getSweepEvents",value:function(){for(var t=[],e=0,r=this.polys.length;e0&&(t=n)}for(var i=t.segment.prevInResult(),o=i?i.prevInResult():null;;){if(!i)return null;if(!o)return i.ringOut;if(o.ringOut!==i.ringOut)return o.ringOut.enclosingRing()!==i.ringOut?i.ringOut:i.ringOut.enclosingRing();i=o.prevInResult(),o=i?i.prevInResult():null}}}]),e}(),M=function(){function e(r){t(this,e),this.exteriorRing=r,r.poly=this,this.interiorRings=[]}return r(e,[{key:"addInterior",value:function(t){this.interiorRings.push(t),t.poly=this}},{key:"getGeom",value:function(){var t=[this.exteriorRing.getGeom()];if(null===t[0])return null;for(var e=0,r=this.interiorRings.length;e1&&void 0!==arguments[1]?arguments[1]:O.compare;t(this,e),this.queue=r,this.tree=new h(n),this.segments=[]}return r(e,[{key:"process",value:function(t){var e=t.segment,r=[];if(t.consumedBy)return t.isLeft?this.queue.remove(t.otherSE):this.tree.remove(e),r;var n=t.isLeft?this.tree.insert(e):this.tree.find(e);if(!n)throw new Error("Unable to find segment #".concat(e.id," ")+"[".concat(e.leftSE.point.x,", ").concat(e.leftSE.point.y,"] -> ")+"[".concat(e.rightSE.point.x,", ").concat(e.rightSE.point.y,"] ")+"in SweepLine tree. Please submit a bug report.");for(var i=n,o=n,s=void 0,u=void 0;void 0===s;)null===(i=this.tree.prev(i))?s=null:void 0===i.key.consumedBy&&(s=i.key);for(;void 0===u;)null===(o=this.tree.next(o))?u=null:void 0===o.key.consumedBy&&(u=o.key);if(t.isLeft){var l=null;if(s){var h=s.getIntersection(e);if(null!==h&&(e.isAnEndpoint(h)||(l=h),!s.isAnEndpoint(h)))for(var f=this._splitSafely(s,h),a=0,p=f.length;a0?(this.tree.remove(e),r.push(t)):(this.segments.push(e),e.prev=s)}else{if(s&&u){var S=s.getIntersection(u);if(null!==S){if(!s.isAnEndpoint(S))for(var _=this._splitSafely(s,S),k=0,w=_.length;kC)throw new Error("Infinite loop when putting segment endpoints in a priority queue (queue size too big). Please file a bug report.");for(var S=new T(v),_=v.size,k=v.pop();k;){var w=k.key;if(v.size===_){var R=w.segment;throw new Error("Unable to pop() ".concat(w.isLeft?"left":"right"," SweepEvent ")+"[".concat(w.point.x,", ").concat(w.point.y,"] from segment #").concat(R.id," ")+"[".concat(R.leftSE.point.x,", ").concat(R.leftSE.point.y,"] -> ")+"[".concat(R.rightSE.point.x,", ").concat(R.rightSE.point.y,"] from queue. ")+"Please file a bug report.")}if(v.size>C)throw new Error("Infinite loop when passing sweep line over endpoints (queue size too big). Please file a bug report.");if(S.segments.length>q)throw new Error("Infinite loop when passing sweep line over endpoints (too many sweep line segments). Please file a bug report.");for(var I=S.process(w),N=0,O=I.length;N1?e-1:0),n=1;n1?e-1:0),n=1;n1?e-1:0),n=1;n1?e-1:0),n=1;n + * @license MIT + * @preserve + */ +/*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */function t(t,e){var n,r,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:l(0),throw:l(1),return:l(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function l(o){return function(l){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,r=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]e?1:t0))break;if(null===n.right)break;if(r(t,n.right.key)>0){h=n.right;if(n.right=h.left,h.left=n,null===(n=h).right)break}o.right=n,o=n,n=n.right}}return o.right=n.left,s.left=n.right,n.left=i.right,n.right=i.left,n}function i(t,n,i,o){var s=new e(t,n);if(null===i)return s.left=s.right=null,s;var l=o(t,(i=r(t,i,o)).key);return l<0?(s.left=i.left,s.right=i,i.left=null):l>=0&&(s.right=i.right,s.left=i,i.right=null),s}function o(t,e,n){var i=null,o=null;if(e){var s=n((e=r(t,e,n)).key,t);0===s?(i=e.left,o=e.right):s<0?(o=e.right,e.right=null,i=e):(i=e.left,e.left=null,o=e)}return{left:i,right:o}}function s(t,e,n,r,i){if(t){r(e+(n?"└── ":"├── ")+i(t)+"\n");var o=e+(n?" ":"│ ");t.left&&s(t.left,o,!1,r,i),t.right&&s(t.right,o,!0,r,i)}}var l=function(){function l(t){void 0===t&&(t=n),this._root=null,this._size=0,this._comparator=t}return l.prototype.insert=function(t,e){return this._size++,this._root=i(t,e,this._root,this._comparator)},l.prototype.add=function(t,n){var i=new e(t,n);null===this._root&&(i.left=i.right=null,this._size++,this._root=i);var o=this._comparator,s=r(t,this._root,o),l=o(t,s.key);return 0===l?this._root=s:(l<0?(i.left=s.left,i.right=s,s.left=null):l>0&&(i.right=s.right,i.left=s,s.right=null),this._size++,this._root=i),this._root},l.prototype.remove=function(t){this._root=this._remove(t,this._root,this._comparator)},l.prototype._remove=function(t,e,n){var i;return null===e?null:0===n(t,(e=r(t,e,n)).key)?(null===e.left?i=e.right:(i=r(t,e.left,n)).right=e.right,this._size--,i):e},l.prototype.pop=function(){var t=this._root;if(t){for(;t.left;)t=t.left;return this._root=r(t.key,this._root,this._comparator),this._root=this._remove(t.key,this._root,this._comparator),{key:t.key,data:t.data}}return null},l.prototype.findStatic=function(t){for(var e=this._root,n=this._comparator;e;){var r=n(t,e.key);if(0===r)return e;e=r<0?e.left:e.right}return null},l.prototype.find=function(t){return this._root&&(this._root=r(t,this._root,this._comparator),0!==this._comparator(t,this._root.key))?null:this._root},l.prototype.contains=function(t){for(var e=this._root,n=this._comparator;e;){var r=n(t,e.key);if(0===r)return!0;e=r<0?e.left:e.right}return!1},l.prototype.forEach=function(t,e){for(var n=this._root,r=[],i=!1;!i;)null!==n?(r.push(n),n=n.left):0!==r.length?(n=r.pop(),t.call(e,n),n=n.right):i=!0;return this},l.prototype.range=function(t,e,n,r){for(var i=[],o=this._comparator,s=this._root;0!==i.length||s;)if(s)i.push(s),s=s.left;else{if(o((s=i.pop()).key,e)>0)break;if(o(s.key,t)>=0&&n.call(r,s))return this;s=s.right}return this},l.prototype.keys=function(){var t=[];return this.forEach((function(e){var n=e.key;return t.push(n)})),t},l.prototype.values=function(){var t=[];return this.forEach((function(e){var n=e.data;return t.push(n)})),t},l.prototype.min=function(){return this._root?this.minNode(this._root).key:null},l.prototype.max=function(){return this._root?this.maxNode(this._root).key:null},l.prototype.minNode=function(t){if(void 0===t&&(t=this._root),t)for(;t.left;)t=t.left;return t},l.prototype.maxNode=function(t){if(void 0===t&&(t=this._root),t)for(;t.right;)t=t.right;return t},l.prototype.at=function(t){for(var e=this._root,n=!1,r=0,i=[];!n;)if(e)i.push(e),e=e.left;else if(i.length>0){if(e=i.pop(),r===t)return e;r++,e=e.right}else n=!0;return null},l.prototype.next=function(t){var e=this._root,n=null;if(t.right){for(n=t.right;n.left;)n=n.left;return n}for(var r=this._comparator;e;){var i=r(t.key,e.key);if(0===i)break;i<0?(n=e,e=e.left):e=e.right}return n},l.prototype.prev=function(t){var e=this._root,n=null;if(null!==t.left){for(n=t.left;n.right;)n=n.right;return n}for(var r=this._comparator;e;){var i=r(t.key,e.key);if(0===i)break;i<0?e=e.left:(n=e,e=e.right)}return n},l.prototype.clear=function(){return this._root=null,this._size=0,this},l.prototype.toList=function(){return function(t){var n=t,r=[],i=!1,o=new e(null,null),s=o;for(;!i;)n?(r.push(n),n=n.left):r.length>0?n=(n=s=s.next=r.pop()).right:i=!0;return s.next=null,o.next}(this._root)},l.prototype.load=function(t,n,r){void 0===n&&(n=[]),void 0===r&&(r=!1);var i=t.length,o=this._comparator;if(r&&f(t,n,0,i-1,o),null===this._root)this._root=h(t,n,0,i),this._size=i;else{var s=function(t,n,r){var i=new e(null,null),o=i,s=t,l=n;for(;null!==s&&null!==l;)r(s.key,l.key)<0?(o.next=s,s=s.next):(o.next=l,l=l.next),o=o.next;null!==s?o.next=s:null!==l&&(o.next=l);return i.next}(this.toList(),function(t,n){for(var r=new e(null,null),i=r,o=0;o0){var s=r+Math.floor(o/2),l=t[s],u=n[s],f=new e(l,u);return f.left=h(t,n,r,s),f.right=h(t,n,s+1,i),f}return null}function u(t,e,n){var r=n-e;if(r>0){var i=e+Math.floor(r/2),o=u(t,e,i),s=t.head;return s.left=o,t.head=t.head.next,s.right=u(t,i+1,n),s}return null}function f(t,e,n,r,i){if(!(n>=r)){for(var o=t[n+r>>1],s=n-1,l=r+1;;){do{s++}while(i(t[s],o)<0);do{l--}while(i(t[l],o)>0);if(s>=l)break;var h=t[s];t[s]=t[l],t[l]=h,h=e[s],e[s]=e[l],e[l]=h}f(t,e,n,l,i),f(t,e,l+1,r,i)}}const c=(t,e)=>t.ll.x<=e.x&&e.x<=t.ur.x&&t.ll.y<=e.y&&e.y<=t.ur.y,p=(t,e)=>{if(e.ur.x{if(-gu==f>-u?(o=u,u=e[++c]):(o=f,f=r[++p]);let g=0;if(cu==f>-u?(s=u+o,l=o-(s-u),u=e[++c]):(s=f+o,l=o-(s-f),f=r[++p]),o=s,0!==l&&(i[g++]=l);cu==f>-u?(s=o+u,h=s-o,l=o-(s-h)+(u-h),u=e[++c]):(s=o+f,h=s-o,l=o-(s-h)+(f-h),f=r[++p]),o=s,0!==l&&(i[g++]=l);for(;c=33306690738754716e-32*u?h:-function(t,e,n,r,i,o,s){let l,h,u,f,c,p,g,a,y,x,b,v,S,A,O,L,$,M;const z=t-i,B=n-i,G=e-o,T=r-o;A=z*T,p=d*z,g=p-(p-z),a=z-g,p=d*T,y=p-(p-T),x=T-y,O=a*x-(A-g*y-a*y-g*x),L=G*B,p=d*G,g=p-(p-G),a=G-g,p=d*B,y=p-(p-B),x=B-y,$=a*x-(L-g*y-a*y-g*x),b=O-$,c=O-b,k[0]=O-(b+c)+(c-$),v=A+b,c=v-A,S=A-(v-c)+(b-c),b=S-L,c=S-b,k[1]=S-(b+c)+(c-L),M=v+b,c=M-v,k[2]=v-(M-c)+(b-c),k[3]=M;let q=function(t,e){let n=e[0];for(let r=1;r=C||-q>=C)return q;if(c=t-z,l=t-(z+c)+(c-i),c=n-B,u=n-(B+c)+(c-i),c=e-G,h=e-(G+c)+(c-o),c=r-T,f=r-(T+c)+(c-o),0===l&&0===h&&0===u&&0===f)return q;if(C=w*s+m*Math.abs(q),q+=z*f+T*l-(G*u+B*h),q>=C||-q>=C)return q;A=l*T,p=d*l,g=p-(p-l),a=l-g,p=d*T,y=p-(p-T),x=T-y,O=a*x-(A-g*y-a*y-g*x),L=h*B,p=d*h,g=p-(p-h),a=h-g,p=d*B,y=p-(p-B),x=B-y,$=a*x-(L-g*y-a*y-g*x),b=O-$,c=O-b,N[0]=O-(b+c)+(c-$),v=A+b,c=v-A,S=A-(v-c)+(b-c),b=S-L,c=S-b,N[1]=S-(b+c)+(c-L),M=v+b,c=M-v,N[2]=v-(M-c)+(b-c),N[3]=M;const F=E(4,k,4,N,R);A=z*f,p=d*z,g=p-(p-z),a=z-g,p=d*f,y=p-(p-f),x=f-y,O=a*x-(A-g*y-a*y-g*x),L=G*u,p=d*G,g=p-(p-G),a=G-g,p=d*u,y=p-(p-u),x=u-y,$=a*x-(L-g*y-a*y-g*x),b=O-$,c=O-b,N[0]=O-(b+c)+(c-$),v=A+b,c=v-A,S=A-(v-c)+(b-c),b=S-L,c=S-b,N[1]=S-(b+c)+(c-L),M=v+b,c=M-v,N[2]=v-(M-c)+(b-c),N[3]=M;const j=E(F,R,4,N,I);A=l*f,p=d*l,g=p-(p-l),a=l-g,p=d*f,y=p-(p-f),x=f-y,O=a*x-(A-g*y-a*y-g*x),L=h*u,p=d*h,g=p-(p-h),a=h-g,p=d*u,y=p-(p-u),x=u-y,$=a*x-(L-g*y-a*y-g*x),b=O-$,c=O-b,N[0]=O-(b+c)+(c-$),v=A+b,c=v-A,S=A-(v-c)+(b-c),b=S-L,c=S-b,N[1]=S-(b+c)+(c-L),M=v+b,c=M-v,N[2]=v-(M-c)+(b-c),N[3]=M;const U=E(j,I,4,N,P);return P[U-1]}(t,e,n,r,i,o,u)}const O=(t,e)=>t.x*e.y-t.y*e.x,L=(t,e)=>t.x*e.x+t.y*e.y,$=(t,e,n)=>{const r=A(t.x,t.y,e.x,e.y,n.x,n.y);return r>0?-1:r<0?1:0},M=t=>Math.sqrt(L(t,t)),z=(t,e,n)=>{const r={x:e.x-t.x,y:e.y-t.y},i={x:n.x-t.x,y:n.y-t.y};return O(i,r)/M(i)/M(r)},B=(t,e,n)=>{const r={x:e.x-t.x,y:e.y-t.y},i={x:n.x-t.x,y:n.y-t.y};return L(i,r)/M(i)/M(r)},G=(t,e,n)=>0===e.y?null:{x:t.x+e.x/e.y*(n-t.y),y:n},T=(t,e,n)=>0===e.x?null:{x:n,y:t.y+e.y/e.x*(n-t.x)};class q{static compare(t,e){const n=q.comparePoints(t.point,e.point);return 0!==n?n:(t.point!==e.point&&t.link(e),t.isLeft!==e.isLeft?t.isLeft?1:-1:F.compare(t.segment,e.segment))}static comparePoints(t,e){return t.xe.x?1:t.ye.y?1:0}constructor(t,e){void 0===t.events?t.events=[this]:t.events.push(this),this.point=t,this.isLeft=e}link(t){if(t.point===this.point)throw new Error("Tried to link already linked events");const e=t.point.events;for(let t=0,n=e.length;t{const r=n.otherSE;e.set(n,{sine:z(this.point,t.point,r.point),cosine:B(this.point,t.point,r.point)})};return(t,r)=>{e.has(t)||n(t),e.has(r)||n(r);const{sine:i,cosine:o}=e.get(t),{sine:s,cosine:l}=e.get(r);return i>=0&&s>=0?ol?-1:0:i<0&&s<0?ol?1:0:si?1:0}}}let C=0;class F{static compare(t,e){const n=t.leftSE.point.x,r=e.leftSE.point.x,i=t.rightSE.point.x,o=e.rightSE.point.x;if(os&&l>h)return-1;const n=t.comparePoint(e.leftSE.point);if(n<0)return 1;if(n>0)return-1;const r=e.comparePoint(t.rightSE.point);return 0!==r?r:-1}if(n>r){if(sl&&s>u)return 1;const n=e.comparePoint(t.leftSE.point);if(0!==n)return n;const r=t.comparePoint(e.rightSE.point);return r<0?1:r>0?-1:1}if(sl)return 1;if(io){const n=t.comparePoint(e.rightSE.point);if(n<0)return 1;if(n>0)return-1}if(i!==o){const t=h-s,e=i-n,f=u-l,c=o-r;if(t>e&&fc)return-1}return i>o?1:iu?1:t.ide.id?1:0}constructor(t,e,n,r){this.id=++C,this.leftSE=t,t.segment=this,t.otherSE=e,this.rightSE=e,e.segment=this,e.otherSE=t,this.rings=n,this.windings=r}static fromRing(t,e,n){let r,i,o;const s=q.comparePoints(t,e);if(s<0)r=t,i=e,o=1;else{if(!(s>0))throw new Error(`Tried to create degenerate segment at [${t.x}, ${t.y}]`);r=e,i=t,o=-1}const l=new q(r,!0),h=new q(i,!1);return new F(l,h,[n],[o])}replaceRightSE(t){this.rightSE=t,this.rightSE.segment=this,this.rightSE.otherSE=this.leftSE,this.leftSE.otherSE=this.rightSE}bbox(){const t=this.leftSE.point.y,e=this.rightSE.point.y;return{ll:{x:this.leftSE.point.x,y:te?t:e}}}vector(){return{x:this.rightSE.point.x-this.leftSE.point.x,y:this.rightSE.point.y-this.leftSE.point.y}}isAnEndpoint(t){return t.x===this.leftSE.point.x&&t.y===this.leftSE.point.y||t.x===this.rightSE.point.x&&t.y===this.rightSE.point.y}comparePoint(t){if(this.isAnEndpoint(t))return 0;const e=this.leftSE.point,n=this.rightSE.point,r=this.vector();if(e.x===n.x)return t.x===e.x?0:t.x{if(0===e.x)return T(n,r,t.x);if(0===r.x)return T(t,e,n.x);if(0===e.y)return G(n,r,t.y);if(0===r.y)return G(t,e,n.y);const i=O(e,r);if(0==i)return null;const o={x:n.x-t.x,y:n.y-t.y},s=O(o,e)/i,l=O(o,r)/i;return{x:(t.x+l*e.x+(n.x+s*r.x))/2,y:(t.y+l*e.y+(n.y+s*r.y))/2}})(i,this.vector(),s,t.vector());return null===a?null:c(r,a)?b.round(a.x,a.y):null}split(t){const e=[],n=void 0!==t.events,r=new q(t,!0),i=new q(t,!1),o=this.rightSE;this.replaceRightSE(i),e.push(i),e.push(r);const s=new F(r,o,this.rings.slice(),this.windings.slice());return q.comparePoints(s.leftSE.point,s.rightSE.point)>0&&s.swapEvents(),q.comparePoints(this.leftSE.point,this.rightSE.point)>0&&this.swapEvents(),n&&(r.checkForConsuming(),i.checkForConsuming()),e}swapEvents(){const t=this.rightSE;this.rightSE=this.leftSE,this.leftSE=t,this.leftSE.isLeft=!0,this.rightSE.isLeft=!1;for(let t=0,e=this.windings.length;t0){const t=e;e=n,n=t}if(e.prev===n){const t=e;e=n,n=t}for(let t=0,r=n.rings.length;t1===t.length&&t[0].isSubject;this._isInResult=n(t)!==n(e);break}default:throw new Error(`Unrecognized operation type found ${H.type}`)}return this._isInResult}}class j{constructor(t,e,n){if(!Array.isArray(t)||0===t.length)throw new Error("Input geometry is not a valid Polygon or MultiPolygon");if(this.poly=e,this.isExterior=n,this.segments=[],"number"!=typeof t[0][0]||"number"!=typeof t[0][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");const r=b.round(t[0][0],t[0][1]);this.bbox={ll:{x:r.x,y:r.y},ur:{x:r.x,y:r.y}};let i=r;for(let e=1,n=t.length;ethis.bbox.ur.x&&(this.bbox.ur.x=n.x),n.y>this.bbox.ur.y&&(this.bbox.ur.y=n.y),i=n)}r.x===i.x&&r.y===i.y||this.segments.push(F.fromRing(i,r,this))}getSweepEvents(){const t=[];for(let e=0,n=this.segments.length;ethis.bbox.ur.x&&(this.bbox.ur.x=n.bbox.ur.x),n.bbox.ur.y>this.bbox.ur.y&&(this.bbox.ur.y=n.bbox.ur.y),this.interiorRings.push(n)}this.multiPoly=e}getSweepEvents(){const t=this.exteriorRing.getSweepEvents();for(let e=0,n=this.interiorRings.length;ethis.bbox.ur.x&&(this.bbox.ur.x=n.bbox.ur.x),n.bbox.ur.y>this.bbox.ur.y&&(this.bbox.ur.y=n.bbox.ur.y),this.polys.push(n)}this.isSubject=e}getSweepEvents(){const t=[];for(let e=0,n=this.polys.length;e0&&(t=n)}let e=t.segment.prevInResult(),n=e?e.prevInResult():null;for(;;){if(!e)return null;if(!n)return e.ringOut;if(n.ringOut!==e.ringOut)return n.ringOut.enclosingRing()!==e.ringOut?e.ringOut:e.ringOut.enclosingRing();e=n.prevInResult(),n=e?e.prevInResult():null}}}class X{constructor(t){this.exteriorRing=t,t.poly=this,this.interiorRings=[]}addInterior(t){this.interiorRings.push(t),t.poly=this}getGeom(){const t=[this.exteriorRing.getGeom()];if(null===t[0])return null;for(let e=0,n=this.interiorRings.length;e1&&void 0!==arguments[1]?arguments[1]:F.compare;this.queue=t,this.tree=new l(e),this.segments=[]}process(t){const e=t.segment,n=[];if(t.consumedBy)return t.isLeft?this.queue.remove(t.otherSE):this.tree.remove(e),n;const r=t.isLeft?this.tree.add(e):this.tree.find(e);if(!r)throw new Error(`Unable to find segment #${e.id} [${e.leftSE.point.x}, ${e.leftSE.point.y}] -> [${e.rightSE.point.x}, ${e.rightSE.point.y}] in SweepLine tree.`);let i,o,s=r,l=r;for(;void 0===i;)s=this.tree.prev(s),null===s?i=null:void 0===s.key.consumedBy&&(i=s.key);for(;void 0===o;)l=this.tree.next(l),null===l?o=null:void 0===l.key.consumedBy&&(o=l.key);if(t.isLeft){let r=null;if(i){const t=i.getIntersection(e);if(null!==t&&(e.isAnEndpoint(t)||(r=t),!i.isAnEndpoint(t))){const e=this._splitSafely(i,t);for(let t=0,r=e.length;t0?(this.tree.remove(e),n.push(t)):(this.segments.push(e),e.prev=i)}else{if(i&&o){const t=i.getIntersection(o);if(null!==t){if(!i.isAnEndpoint(t)){const e=this._splitSafely(i,t);for(let t=0,r=e.length;tZ)throw new Error("Infinite loop when putting segment endpoints in a priority queue (queue size too big).")}const o=new W(i);let s=i.size,h=i.pop();for(;h;){const t=h.key;if(i.size===s){const e=t.segment;throw new Error(`Unable to pop() ${t.isLeft?"left":"right"} SweepEvent [${t.point.x}, ${t.point.y}] from segment #${e.id} [${e.leftSE.point.x}, ${e.leftSE.point.y}] -> [${e.rightSE.point.x}, ${e.rightSE.point.y}] from queue.`)}if(i.size>Z)throw new Error("Infinite loop when passing sweep line over endpoints (queue size too big).");if(o.segments.length>D)throw new Error("Infinite loop when passing sweep line over endpoints (too many sweep line segments).");const e=o.process(t);for(let t=0,n=e.length;t1?e-1:0),r=1;r1?e-1:0),r=1;r1?e-1:0),r=1;r1?e-1:0),r=1;r {\n return (\n (bbox.ll.x <= point.x) &&\n (point.x <= bbox.ur.x) &&\n (bbox.ll.y <= point.y) &&\n (point.y <= bbox.ur.y)\n )\n}\n\n/* Returns either null, or a bbox (aka an ordered pair of points)\n * If there is only one point of overlap, a bbox with identical points\n * will be returned */\nexport const getBboxOverlap = (b1, b2) => {\n // check if the bboxes overlap at all\n if (\n b2.ur.x < b1.ll.x ||\n b1.ur.x < b2.ll.x ||\n b2.ur.y < b1.ll.y ||\n b1.ur.y < b2.ll.y\n ) return null\n\n // find the middle two X values\n const lowerX = b1.ll.x < b2.ll.x ? b2.ll.x : b1.ll.x\n const upperX = b1.ur.x < b2.ur.x ? b1.ur.x : b2.ur.x\n\n // find the middle two Y values\n const lowerY = b1.ll.y < b2.ll.y ? b2.ll.y : b1.ll.y\n const upperY = b1.ur.y < b2.ur.y ? b1.ur.y : b2.ur.y\n\n // put those middle values together to get the overlap\n return { ll: { x: lowerX, y: lowerY }, ur: { x: upperX, y: upperY } }\n}\n","/* Javascript doesn't do integer math. Everything is\n * floating point with percision Number.EPSILON.\n *\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON\n */\n\nlet epsilon = Number.EPSILON\n\n// IE Polyfill\nif (epsilon === undefined) epsilon = Math.pow(2, -52)\n\nconst EPSILON_SQ = epsilon * epsilon\n\n/* FLP comparator */\nexport const cmp = (a, b) => {\n // check if they're both 0\n if (-epsilon < a && a < epsilon) {\n if (-epsilon < b && b < epsilon) {\n return 0\n }\n }\n\n // check if they're flp equal\n const ab = a - b\n if (ab * ab < EPSILON_SQ * a * b) {\n return 0\n }\n\n // normal comparison\n return a < b ? -1 : 1\n}\n","import { cmp } from './flp'\nimport SplayTree from 'splaytree'\n\n/**\n * This class rounds incoming values sufficiently so that\n * floating points problems are, for the most part, avoided.\n *\n * Incoming points are have their x & y values tested against\n * all previously seen x & y values. If either is 'too close'\n * to a previously seen value, it's value is 'snapped' to the\n * previously seen value.\n *\n * All points should be rounded by this class before being\n * stored in any data structures in the rest of this algorithm.\n */\n\nclass PtRounder {\n constructor () {\n this.reset()\n }\n\n reset () {\n this.xRounder = new CoordRounder()\n this.yRounder = new CoordRounder()\n }\n\n round (x, y) {\n return {\n x: this.xRounder.round(x),\n y: this.yRounder.round(y),\n }\n }\n}\n\nclass CoordRounder {\n constructor () {\n this.tree = new SplayTree()\n // preseed with 0 so we don't end up with values < Number.EPSILON\n this.round(0)\n }\n\n // Note: this can rounds input values backwards or forwards.\n // You might ask, why not restrict this to just rounding\n // forwards? Wouldn't that allow left endpoints to always\n // remain left endpoints during splitting (never change to\n // right). No - it wouldn't, because we snap intersections\n // to endpoints (to establish independence from the segment\n // angle for t-intersections).\n round (coord) {\n const node = this.tree.add(coord)\n\n const prevNode = this.tree.prev(node)\n if (prevNode !== null && cmp(node.key, prevNode.key) === 0) {\n this.tree.remove(coord)\n return prevNode.key\n }\n\n const nextNode = this.tree.next(node)\n if (nextNode !== null && cmp(node.key, nextNode.key) === 0) {\n this.tree.remove(coord)\n return nextNode.key\n }\n\n return coord\n }\n}\n\n// singleton available by import\nconst rounder = new PtRounder()\n\nexport default rounder\n","import { cmp } from './flp'\n\n/* Cross Product of two vectors with first point at origin */\nexport const crossProduct = (a, b) => a.x * b.y - a.y * b.x\n\n/* Dot Product of two vectors with first point at origin */\nexport const dotProduct = (a, b) => a.x * b.x + a.y * b.y\n\n/* Comparator for two vectors with same starting point */\nexport const compareVectorAngles = (basePt, endPt1, endPt2) => {\n const v1 = { x: endPt1.x - basePt.x, y: endPt1.y - basePt.y }\n const v2 = { x: endPt2.x - basePt.x, y: endPt2.y - basePt.y }\n const kross = crossProduct(v1, v2)\n return cmp(kross, 0)\n}\n\nexport const length = v => Math.sqrt(dotProduct(v, v))\n\n/* Get the sine of the angle from pShared -> pAngle to pShaed -> pBase */\nexport const sineOfAngle = (pShared, pBase, pAngle) => {\n const vBase = { x: pBase.x - pShared.x, y: pBase.y - pShared.y }\n const vAngle = { x: pAngle.x - pShared.x, y: pAngle.y - pShared.y }\n return crossProduct(vAngle, vBase) / length(vAngle) / length(vBase)\n}\n\n/* Get the cosine of the angle from pShared -> pAngle to pShaed -> pBase */\nexport const cosineOfAngle = (pShared, pBase, pAngle) => {\n const vBase = { x: pBase.x - pShared.x, y: pBase.y - pShared.y }\n const vAngle = { x: pAngle.x - pShared.x, y: pAngle.y - pShared.y }\n return dotProduct(vAngle, vBase) / length(vAngle) / length(vBase)\n}\n\n/* Get the closest point on an line (defined by two points)\n * to another point. */\nexport const closestPoint = (ptA1, ptA2, ptB) => {\n if (ptA1.x === ptA2.x) return { x: ptA1.x, y: ptB.y } // vertical vector\n if (ptA1.y === ptA2.y) return { x: ptB.x, y: ptA1.y } // horizontal vector\n\n // determinne which point is further away\n // we use the further point as our base in the calculation, so that the\n // vectors are more parallel, providing more accurate dot product\n const v1 = { x: ptB.x - ptA1.x, y: ptB.y - ptA1.y }\n const v2 = { x: ptB.x - ptA2.x, y: ptB.y - ptA2.y }\n let vFar, vA, farPt\n if (dotProduct(v1, v1) > dotProduct(v2, v2)) {\n vFar = v1\n vA = { x: ptA2.x - ptA1.x, y: ptA2.y - ptA1.y }\n farPt = ptA1\n }\n else {\n vFar = v2\n vA = { x: ptA1.x - ptA2.x, y: ptA1.y - ptA2.y }\n farPt = ptA2\n }\n\n // manually test if the current point can be considered to be on the line\n // If the X coordinate was on the line, would the Y coordinate be as well?\n const xDist = (ptB.x - farPt.x) / vA.x\n if (ptB.y === farPt.y + xDist * vA.y) return ptB\n\n // If the Y coordinate was on the line, would the X coordinate be as well?\n const yDist = (ptB.y - farPt.y) / vA.y\n if (ptB.x === farPt.x + yDist * vA.x) return ptB\n\n // current point isn't exactly on line, so return closest point\n const dist = dotProduct(vA, vFar) / dotProduct(vA, vA)\n return { x: farPt.x + dist * vA.x, y: farPt.y + dist * vA.y }\n}\n\n/* Get the x coordinate where the given line (defined by a point and vector)\n * crosses the horizontal line with the given y coordiante.\n * In the case of parrallel lines (including overlapping ones) returns null. */\nexport const horizontalIntersection = (pt, v, y) => {\n if (v.y === 0) return null\n return { x: pt.x + v.x / v.y * ( y - pt.y ), y: y }\n}\n\n/* Get the y coordinate where the given line (defined by a point and vector)\n * crosses the vertical line with the given x coordiante.\n * In the case of parrallel lines (including overlapping ones) returns null. */\nexport const verticalIntersection = (pt, v, x) => {\n if (v.x === 0) return null\n return { x: x, y: pt.y + v.y / v.x * ( x - pt.x ) }\n}\n\n/* Get the intersection of two lines, each defined by a base point and a vector.\n * In the case of parrallel lines (including overlapping ones) returns null. */\nexport const intersection = (pt1, v1, pt2, v2) => {\n // take some shortcuts for vertical and horizontal lines\n // this also ensures we don't calculate an intersection and then discover\n // it's actually outside the bounding box of the line\n if (v1.x === 0) return verticalIntersection(pt2, v2, pt1.x)\n if (v2.x === 0) return verticalIntersection(pt1, v1, pt2.x)\n if (v1.y === 0) return horizontalIntersection(pt2, v2, pt1.y)\n if (v2.y === 0) return horizontalIntersection(pt1, v1, pt2.y)\n\n // General case for non-overlapping segments.\n // This algorithm is based on Schneider and Eberly.\n // http://www.cimec.org.ar/~ncalvo/Schneider_Eberly.pdf - pg 244\n\n const kross = crossProduct(v1, v2)\n if (kross == 0) return null\n\n const ve = { x: pt2.x - pt1.x, y: pt2.y - pt1.y }\n const d1 = crossProduct(ve, v1) / kross\n const d2 = crossProduct(ve, v2) / kross\n\n // take the average of the two calculations to minimize rounding error\n const x1 = pt1.x + d2 * v1.x, x2 = pt2.x + d1 * v2.x\n const y1 = pt1.y + d2 * v1.y, y2 = pt2.y + d1 * v2.y\n const x = (x1 + x2) / 2\n const y = (y1 + y2) / 2\n return { x: x, y: y }\n}\n\n/* Given a vector, return one that is perpendicular */\nexport const perpendicular = (v) => {\n return { x: -v.y, y: v.x }\n}\n","import Segment from './segment'\nimport { cosineOfAngle, sineOfAngle } from './vector'\n\nexport default class SweepEvent {\n\n // for ordering sweep events in the sweep event queue\n static compare (a, b) {\n\n // favor event with a point that the sweep line hits first\n const ptCmp = SweepEvent.comparePoints(a.point, b.point)\n if (ptCmp !== 0) return ptCmp\n\n // the points are the same, so link them if needed\n if (a.point !== b.point) a.link(b)\n\n // favor right events over left\n if (a.isLeft !== b.isLeft) return a.isLeft ? 1 : -1\n\n // we have two matching left or right endpoints\n // ordering of this case is the same as for their segments\n return Segment.compare(a.segment, b.segment)\n }\n\n // for ordering points in sweep line order\n static comparePoints (aPt, bPt) {\n if (aPt.x < bPt.x) return -1\n if (aPt.x > bPt.x) return 1\n\n if (aPt.y < bPt.y) return -1\n if (aPt.y > bPt.y) return 1\n\n return 0\n }\n\n // Warning: 'point' input will be modified and re-used (for performance)\n constructor (point, isLeft) {\n if (point.events === undefined) point.events = [this]\n else point.events.push(this)\n this.point = point\n this.isLeft = isLeft\n // this.segment, this.otherSE set by factory\n }\n\n link (other) {\n if (other.point === this.point) {\n throw new Error('Tried to link already linked events')\n }\n const otherEvents = other.point.events\n for (let i = 0, iMax = otherEvents.length; i < iMax; i++) {\n const evt = otherEvents[i]\n this.point.events.push(evt)\n evt.point = this.point\n }\n this.checkForConsuming()\n }\n\n /* Do a pass over our linked events and check to see if any pair\n * of segments match, and should be consumed. */\n checkForConsuming () {\n // FIXME: The loops in this method run O(n^2) => no good.\n // Maintain little ordered sweep event trees?\n // Can we maintaining an ordering that avoids the need\n // for the re-sorting with getLeftmostComparator in geom-out?\n\n // Compare each pair of events to see if other events also match\n const numEvents = this.point.events.length\n for (let i = 0; i < numEvents; i++) {\n const evt1 = this.point.events[i]\n if (evt1.segment.consumedBy !== undefined) continue\n for (let j = i + 1; j < numEvents; j++) {\n const evt2 = this.point.events[j]\n if (evt2.consumedBy !== undefined) continue\n if (evt1.otherSE.point.events !== evt2.otherSE.point.events) continue\n evt1.segment.consume(evt2.segment)\n }\n }\n }\n\n getAvailableLinkedEvents () {\n // point.events is always of length 2 or greater\n const events = []\n for (let i = 0, iMax = this.point.events.length; i < iMax; i++) {\n const evt = this.point.events[i]\n if (evt !== this && !evt.segment.ringOut && evt.segment.isInResult()) {\n events.push(evt)\n }\n }\n return events\n }\n\n /**\n * Returns a comparator function for sorting linked events that will\n * favor the event that will give us the smallest left-side angle.\n * All ring construction starts as low as possible heading to the right,\n * so by always turning left as sharp as possible we'll get polygons\n * without uncessary loops & holes.\n *\n * The comparator function has a compute cache such that it avoids\n * re-computing already-computed values.\n */\n getLeftmostComparator (baseEvent) {\n const cache = new Map()\n\n const fillCache = linkedEvent => {\n const nextEvent = linkedEvent.otherSE\n cache.set(linkedEvent, {\n sine: sineOfAngle(this.point, baseEvent.point, nextEvent.point),\n cosine: cosineOfAngle(this.point, baseEvent.point, nextEvent.point)\n })\n }\n\n return (a, b) => {\n if (!cache.has(a)) fillCache(a)\n if (!cache.has(b)) fillCache(b)\n\n const { sine: asine, cosine: acosine } = cache.get(a)\n const { sine: bsine, cosine: bcosine } = cache.get(b)\n\n // both on or above x-axis\n if (asine >= 0 && bsine >= 0) {\n if (acosine < bcosine) return 1\n if (acosine > bcosine) return -1\n return 0\n }\n\n // both below x-axis\n if (asine < 0 && bsine < 0) {\n if (acosine < bcosine) return -1\n if (acosine > bcosine) return 1\n return 0\n }\n\n // one above x-axis, one below\n if (bsine < asine) return -1\n if (bsine > asine) return 1\n return 0\n }\n }\n}\n","import operation from './operation'\nimport SweepEvent from './sweep-event'\nimport { isInBbox, getBboxOverlap } from './bbox'\nimport { intersection } from './vector'\nimport rounder from './rounder'\n\n// Give segments unique ID's to get consistent sorting of\n// segments and sweep events when all else is identical\nlet segmentId = 0\n\nexport default class Segment {\n\n /* This compare() function is for ordering segments in the sweep\n * line tree, and does so according to the following criteria:\n *\n * Consider the vertical line that lies an infinestimal step to the\n * right of the right-more of the two left endpoints of the input\n * segments. Imagine slowly moving a point up from negative infinity\n * in the increasing y direction. Which of the two segments will that\n * point intersect first? That segment comes 'before' the other one.\n *\n * If neither segment would be intersected by such a line, (if one\n * or more of the segments are vertical) then the line to be considered\n * is directly on the right-more of the two left inputs.\n */\n static compare (a, b) {\n\n const alx = a.leftSE.point.x\n const blx = b.leftSE.point.x\n const arx = a.rightSE.point.x\n const brx = b.rightSE.point.x\n\n // check if they're even in the same vertical plane\n if (brx < alx) return 1\n if (arx < blx) return -1\n\n const aly = a.leftSE.point.y\n const bly = b.leftSE.point.y\n const ary = a.rightSE.point.y\n const bry = b.rightSE.point.y\n\n // is left endpoint of segment B the right-more?\n if (alx < blx) {\n // are the two segments in the same horizontal plane?\n if (bly < aly && bly < ary) return 1\n if (bly > aly && bly > ary) return -1\n\n // is the B left endpoint colinear to segment A?\n const aCmpBLeft = a.comparePoint(b.leftSE.point)\n if (aCmpBLeft < 0) return 1\n if (aCmpBLeft > 0) return -1\n\n // is the A right endpoint colinear to segment B ?\n const bCmpARight = b.comparePoint(a.rightSE.point)\n if (bCmpARight !== 0) return bCmpARight\n\n // colinear segments, consider the one with left-more\n // left endpoint to be first (arbitrary?)\n return -1\n }\n\n // is left endpoint of segment A the right-more?\n if (alx > blx) {\n if (aly < bly && aly < bry) return -1\n if (aly > bly && aly > bry) return 1\n\n // is the A left endpoint colinear to segment B?\n const bCmpALeft = b.comparePoint(a.leftSE.point)\n if (bCmpALeft !== 0) return bCmpALeft\n\n // is the B right endpoint colinear to segment A?\n const aCmpBRight = a.comparePoint(b.rightSE.point)\n if (aCmpBRight < 0) return 1\n if (aCmpBRight > 0) return -1\n\n // colinear segments, consider the one with left-more\n // left endpoint to be first (arbitrary?)\n return 1\n }\n\n // if we get here, the two left endpoints are in the same\n // vertical plane, ie alx === blx\n\n // consider the lower left-endpoint to come first\n if (aly < bly) return -1\n if (aly > bly) return 1\n\n // left endpoints are identical\n // check for colinearity by using the left-more right endpoint\n\n // is the A right endpoint more left-more?\n if (arx < brx) {\n const bCmpARight = b.comparePoint(a.rightSE.point)\n if (bCmpARight !== 0) return bCmpARight\n }\n\n // is the B right endpoint more left-more?\n if (arx > brx) {\n const aCmpBRight = a.comparePoint(b.rightSE.point)\n if (aCmpBRight < 0) return 1\n if (aCmpBRight > 0) return -1\n }\n\n if (arx !== brx) {\n // are these two [almost] vertical segments with opposite orientation?\n // if so, the one with the lower right endpoint comes first\n const ay = ary - aly\n const ax = arx - alx\n const by = bry - bly\n const bx = brx - blx\n if (ay > ax && by < bx) return 1\n if (ay < ax && by > bx) return -1\n }\n\n // we have colinear segments with matching orientation\n // consider the one with more left-more right endpoint to be first\n if (arx > brx) return 1\n if (arx < brx) return -1\n\n // if we get here, two two right endpoints are in the same\n // vertical plane, ie arx === brx\n\n // consider the lower right-endpoint to come first\n if (ary < bry) return -1\n if (ary > bry) return 1\n\n // right endpoints identical as well, so the segments are idential\n // fall back on creation order as consistent tie-breaker\n if (a.id < b.id) return -1\n if (a.id > b.id) return 1\n\n // identical segment, ie a === b\n return 0\n }\n\n /* Warning: a reference to ringWindings input will be stored,\n * and possibly will be later modified */\n constructor (leftSE, rightSE, rings, windings) {\n this.id = ++segmentId\n this.leftSE = leftSE\n leftSE.segment = this\n leftSE.otherSE = rightSE\n this.rightSE = rightSE\n rightSE.segment = this\n rightSE.otherSE = leftSE\n this.rings = rings\n this.windings = windings\n // left unset for performance, set later in algorithm\n // this.ringOut, this.consumedBy, this.prev\n }\n\n static fromRing(pt1, pt2, ring) {\n let leftPt, rightPt, winding\n\n // ordering the two points according to sweep line ordering\n const cmpPts = SweepEvent.comparePoints(pt1, pt2)\n if (cmpPts < 0) {\n leftPt = pt1\n rightPt = pt2\n winding = 1\n }\n else if (cmpPts > 0) {\n leftPt = pt2\n rightPt = pt1\n winding = -1\n }\n else throw new Error(\n `Tried to create degenerate segment at [${pt1.x}, ${pt1.y}]`\n )\n\n const leftSE = new SweepEvent(leftPt, true)\n const rightSE = new SweepEvent(rightPt, false)\n return new Segment(leftSE, rightSE, [ring], [winding])\n }\n\n /* When a segment is split, the rightSE is replaced with a new sweep event */\n replaceRightSE (newRightSE) {\n this.rightSE = newRightSE\n this.rightSE.segment = this\n this.rightSE.otherSE = this.leftSE\n this.leftSE.otherSE = this.rightSE\n }\n\n bbox () {\n const y1 = this.leftSE.point.y\n const y2 = this.rightSE.point.y\n return {\n ll: { x: this.leftSE.point.x, y: y1 < y2 ? y1 : y2 },\n ur: { x: this.rightSE.point.x, y: y1 > y2 ? y1 : y2 }\n }\n }\n\n /* A vector from the left point to the right */\n vector () {\n return {\n x: this.rightSE.point.x - this.leftSE.point.x,\n y: this.rightSE.point.y - this.leftSE.point.y\n }\n }\n\n isAnEndpoint (pt) {\n return (\n (pt.x === this.leftSE.point.x && pt.y === this.leftSE.point.y) ||\n (pt.x === this.rightSE.point.x && pt.y === this.rightSE.point.y)\n )\n }\n\n /* Compare this segment with a point.\n *\n * A point P is considered to be colinear to a segment if there\n * exists a distance D such that if we travel along the segment\n * from one * endpoint towards the other a distance D, we find\n * ourselves at point P.\n *\n * Return value indicates:\n *\n * 1: point lies above the segment (to the left of vertical)\n * 0: point is colinear to segment\n * -1: point lies below the segment (to the right of vertical)\n */\n comparePoint (point) {\n if (this.isAnEndpoint(point)) return 0\n\n const lPt = this.leftSE.point\n const rPt = this.rightSE.point\n const v = this.vector()\n\n // Exactly vertical segments.\n if (lPt.x === rPt.x) {\n if (point.x === lPt.x) return 0\n return point.x < lPt.x ? 1 : -1\n }\n\n // Nearly vertical segments with an intersection.\n // Check to see where a point on the line with matching Y coordinate is.\n const yDist = (point.y - lPt.y) / v.y\n const xFromYDist = lPt.x + yDist * v.x\n if (point.x === xFromYDist) return 0\n\n // General case.\n // Check to see where a point on the line with matching X coordinate is.\n const xDist = (point.x - lPt.x) / v.x\n const yFromXDist = lPt.y + xDist * v.y\n if (point.y === yFromXDist) return 0\n return point.y < yFromXDist ? -1 : 1\n }\n\n /**\n * Given another segment, returns the first non-trivial intersection\n * between the two segments (in terms of sweep line ordering), if it exists.\n *\n * A 'non-trivial' intersection is one that will cause one or both of the\n * segments to be split(). As such, 'trivial' vs. 'non-trivial' intersection:\n *\n * * endpoint of segA with endpoint of segB --> trivial\n * * endpoint of segA with point along segB --> non-trivial\n * * endpoint of segB with point along segA --> non-trivial\n * * point along segA with point along segB --> non-trivial\n *\n * If no non-trivial intersection exists, return null\n * Else, return null.\n */\n getIntersection (other) {\n // If bboxes don't overlap, there can't be any intersections\n const tBbox = this.bbox()\n const oBbox = other.bbox()\n const bboxOverlap = getBboxOverlap(tBbox, oBbox)\n if (bboxOverlap === null) return null\n\n // We first check to see if the endpoints can be considered intersections.\n // This will 'snap' intersections to endpoints if possible, and will\n // handle cases of colinearity.\n\n const tlp = this.leftSE.point\n const trp = this.rightSE.point\n const olp = other.leftSE.point\n const orp = other.rightSE.point\n\n // does each endpoint touch the other segment?\n // note that we restrict the 'touching' definition to only allow segments\n // to touch endpoints that lie forward from where we are in the sweep line pass\n const touchesOtherLSE = isInBbox(tBbox, olp) && this.comparePoint(olp) === 0\n const touchesThisLSE = isInBbox(oBbox, tlp) && other.comparePoint(tlp) === 0\n const touchesOtherRSE = isInBbox(tBbox, orp) && this.comparePoint(orp) === 0\n const touchesThisRSE = isInBbox(oBbox, trp) && other.comparePoint(trp) === 0\n\n // do left endpoints match?\n if (touchesThisLSE && touchesOtherLSE) {\n // these two cases are for colinear segments with matching left\n // endpoints, and one segment being longer than the other\n if (touchesThisRSE && !touchesOtherRSE) return trp\n if (!touchesThisRSE && touchesOtherRSE) return orp\n // either the two segments match exactly (two trival intersections)\n // or just on their left endpoint (one trivial intersection\n return null\n }\n\n // does this left endpoint matches (other doesn't)\n if (touchesThisLSE) {\n // check for segments that just intersect on opposing endpoints\n if (touchesOtherRSE) {\n if (tlp.x === orp.x && tlp.y === orp.y) return null\n }\n // t-intersection on left endpoint\n return tlp\n }\n\n // does other left endpoint matches (this doesn't)\n if (touchesOtherLSE) {\n // check for segments that just intersect on opposing endpoints\n if (touchesThisRSE) {\n if (trp.x === olp.x && trp.y === olp.y) return null\n }\n // t-intersection on left endpoint\n return olp\n }\n\n // trivial intersection on right endpoints\n if (touchesThisRSE && touchesOtherRSE) return null\n\n // t-intersections on just one right endpoint\n if (touchesThisRSE) return trp\n if (touchesOtherRSE) return orp\n\n // None of our endpoints intersect. Look for a general intersection between\n // infinite lines laid over the segments\n const pt = intersection(tlp, this.vector(), olp, other.vector())\n\n // are the segments parrallel? Note that if they were colinear with overlap,\n // they would have an endpoint intersection and that case was already handled above\n if (pt === null) return null\n\n // is the intersection found between the lines not on the segments?\n if (!isInBbox(bboxOverlap, pt)) return null\n\n // round the the computed point if needed\n return rounder.round(pt.x, pt.y)\n }\n\n /**\n * Split the given segment into multiple segments on the given points.\n * * Each existing segment will retain its leftSE and a new rightSE will be\n * generated for it.\n * * A new segment will be generated which will adopt the original segment's\n * rightSE, and a new leftSE will be generated for it.\n * * If there are more than two points given to split on, new segments\n * in the middle will be generated with new leftSE and rightSE's.\n * * An array of the newly generated SweepEvents will be returned.\n *\n * Warning: input array of points is modified\n */\n split (point) {\n const newEvents = []\n const alreadyLinked = point.events !== undefined\n\n const newLeftSE = new SweepEvent(point, true)\n const newRightSE = new SweepEvent(point, false)\n const oldRightSE = this.rightSE\n this.replaceRightSE(newRightSE)\n newEvents.push(newRightSE)\n newEvents.push(newLeftSE)\n const newSeg = new Segment(\n newLeftSE, oldRightSE, this.rings.slice(), this.windings.slice()\n )\n\n // when splitting a nearly vertical downward-facing segment,\n // sometimes one of the resulting new segments is vertical, in which\n // case its left and right events may need to be swapped\n if (SweepEvent.comparePoints(newSeg.leftSE.point, newSeg.rightSE.point) > 0) {\n newSeg.swapEvents()\n }\n if (SweepEvent.comparePoints(this.leftSE.point, this.rightSE.point) > 0) {\n this.swapEvents()\n }\n\n // in the point we just used to create new sweep events with was already\n // linked to other events, we need to check if either of the affected\n // segments should be consumed\n if (alreadyLinked) {\n newLeftSE.checkForConsuming()\n newRightSE.checkForConsuming()\n }\n\n return newEvents\n }\n\n /* Swap which event is left and right */\n swapEvents () {\n const tmpEvt = this.rightSE\n this.rightSE = this.leftSE\n this.leftSE = tmpEvt\n this.leftSE.isLeft = true\n this.rightSE.isLeft = false\n for (let i = 0, iMax = this.windings.length; i < iMax; i++) {\n this.windings[i] *= -1\n }\n }\n\n /* Consume another segment. We take their rings under our wing\n * and mark them as consumed. Use for perfectly overlapping segments */\n consume (other) {\n let consumer = this\n let consumee = other\n while (consumer.consumedBy) consumer = consumer.consumedBy\n while (consumee.consumedBy) consumee = consumee.consumedBy\n\n const cmp = Segment.compare(consumer, consumee)\n if (cmp === 0) return // already consumed\n // the winner of the consumption is the earlier segment\n // according to sweep line ordering\n if (cmp > 0) {\n const tmp = consumer\n consumer = consumee\n consumee = tmp\n }\n\n // make sure a segment doesn't consume it's prev\n if (consumer.prev === consumee) {\n const tmp = consumer\n consumer = consumee\n consumee = tmp\n }\n\n for (let i = 0, iMax = consumee.rings.length; i < iMax; i++) {\n const ring = consumee.rings[i]\n const winding = consumee.windings[i]\n const index = consumer.rings.indexOf(ring)\n if (index === -1) {\n consumer.rings.push(ring)\n consumer.windings.push(winding)\n }\n else consumer.windings[index] += winding\n }\n consumee.rings = null\n consumee.windings = null\n consumee.consumedBy = consumer\n\n // mark sweep events consumed as to maintain ordering in sweep event queue\n consumee.leftSE.consumedBy = consumer.leftSE\n consumee.rightSE.consumedBy = consumer.rightSE\n }\n\n /* The first segment previous segment chain that is in the result */\n prevInResult () {\n if (this._prevInResult !== undefined) return this._prevInResult\n if (! this.prev) this._prevInResult = null\n else if (this.prev.isInResult()) this._prevInResult = this.prev\n else this._prevInResult = this.prev.prevInResult()\n return this._prevInResult\n }\n\n beforeState() {\n if (this._beforeState !== undefined) return this._beforeState\n if (! this.prev) this._beforeState = {\n rings: [],\n windings: [],\n multiPolys: [],\n }\n else {\n const seg = this.prev.consumedBy || this.prev\n this._beforeState = seg.afterState()\n }\n return this._beforeState\n }\n\n afterState () {\n if (this._afterState !== undefined) return this._afterState\n\n const beforeState = this.beforeState()\n this._afterState = {\n rings: beforeState.rings.slice(0),\n windings: beforeState.windings.slice(0),\n multiPolys: []\n }\n const ringsAfter = this._afterState.rings\n const windingsAfter = this._afterState.windings\n const mpsAfter = this._afterState.multiPolys\n\n // calculate ringsAfter, windingsAfter\n for (let i = 0, iMax = this.rings.length; i < iMax; i++) {\n const ring = this.rings[i]\n const winding = this.windings[i]\n const index = ringsAfter.indexOf(ring)\n if (index === -1) {\n ringsAfter.push(ring)\n windingsAfter.push(winding)\n }\n else windingsAfter[index] += winding\n }\n\n // calcualte polysAfter\n const polysAfter = []\n const polysExclude = []\n for (let i = 0, iMax = ringsAfter.length; i < iMax; i++) {\n if (windingsAfter[i] === 0) continue // non-zero rule\n const ring = ringsAfter[i]\n const poly = ring.poly\n if (polysExclude.indexOf(poly) !== -1) continue\n if (ring.isExterior) polysAfter.push(poly)\n else {\n if (polysExclude.indexOf(poly) === -1) polysExclude.push(poly)\n const index = polysAfter.indexOf(ring.poly)\n if (index !== -1) polysAfter.splice(index, 1)\n }\n }\n\n // calculate multiPolysAfter\n for (let i = 0, iMax = polysAfter.length; i < iMax; i++) {\n const mp = polysAfter[i].multiPoly\n if (mpsAfter.indexOf(mp) === -1) mpsAfter.push(mp)\n }\n\n return this._afterState\n }\n\n /* Is this segment part of the final result? */\n isInResult () {\n // if we've been consumed, we're not in the result\n if (this.consumedBy) return false\n\n if (this._isInResult !== undefined) return this._isInResult\n\n const mpsBefore = this.beforeState().multiPolys\n const mpsAfter = this.afterState().multiPolys\n\n switch (operation.type) {\n case 'union': {\n // UNION - included iff:\n // * On one side of us there is 0 poly interiors AND\n // * On the other side there is 1 or more.\n const noBefores = mpsBefore.length === 0\n const noAfters = mpsAfter.length === 0\n this._isInResult = noBefores !== noAfters\n break\n }\n\n case 'intersection': {\n // INTERSECTION - included iff:\n // * on one side of us all multipolys are rep. with poly interiors AND\n // * on the other side of us, not all multipolys are repsented\n // with poly interiors\n let least\n let most\n if (mpsBefore.length < mpsAfter.length) {\n least = mpsBefore.length\n most = mpsAfter.length\n } else {\n least = mpsAfter.length\n most = mpsBefore.length\n }\n this._isInResult = most === operation.numMultiPolys && least < most\n break\n }\n\n case 'xor': {\n // XOR - included iff:\n // * the difference between the number of multipolys represented\n // with poly interiors on our two sides is an odd number\n const diff = Math.abs(mpsBefore.length - mpsAfter.length)\n this._isInResult = diff % 2 === 1\n break\n }\n\n case 'difference': {\n // DIFFERENCE included iff:\n // * on exactly one side, we have just the subject\n const isJustSubject = mps => mps.length === 1 && mps[0].isSubject\n this._isInResult = isJustSubject(mpsBefore) !== isJustSubject(mpsAfter)\n break\n }\n\n default:\n throw new Error(`Unrecognized operation type found ${operation.type}`)\n }\n\n return this._isInResult\n }\n\n}\n","import rounder from './rounder'\nimport Segment from './segment'\n\nexport class RingIn {\n constructor (geomRing, poly, isExterior) {\n if (!Array.isArray(geomRing) || geomRing.length === 0) {\n throw new Error('Input geometry is not a valid Polygon or MultiPolygon')\n }\n\n this.poly = poly\n this.isExterior = isExterior\n this.segments = []\n\n if (typeof geomRing[0][0] !== 'number' || typeof geomRing[0][1] !== 'number') {\n throw new Error('Input geometry is not a valid Polygon or MultiPolygon')\n }\n\n const firstPoint = rounder.round(geomRing[0][0], geomRing[0][1])\n this.bbox = {\n ll: { x: firstPoint.x, y: firstPoint.y },\n ur: { x: firstPoint.x, y: firstPoint.y },\n }\n\n let prevPoint = firstPoint\n for (let i = 1, iMax = geomRing.length; i < iMax; i++) {\n if (typeof geomRing[i][0] !== 'number' || typeof geomRing[i][1] !== 'number') {\n throw new Error('Input geometry is not a valid Polygon or MultiPolygon')\n }\n let point = rounder.round(geomRing[i][0], geomRing[i][1])\n // skip repeated points\n if (point.x === prevPoint.x && point.y === prevPoint.y) continue\n this.segments.push(Segment.fromRing(prevPoint, point, this))\n if (point.x < this.bbox.ll.x) this.bbox.ll.x = point.x\n if (point.y < this.bbox.ll.y) this.bbox.ll.y = point.y\n if (point.x > this.bbox.ur.x) this.bbox.ur.x = point.x\n if (point.y > this.bbox.ur.y) this.bbox.ur.y = point.y\n prevPoint = point\n }\n // add segment from last to first if last is not the same as first\n if (firstPoint.x !== prevPoint.x || firstPoint.y !== prevPoint.y) {\n this.segments.push(Segment.fromRing(prevPoint, firstPoint, this))\n }\n }\n\n getSweepEvents () {\n const sweepEvents = []\n for (let i = 0, iMax = this.segments.length; i < iMax; i++) {\n const segment = this.segments[i]\n sweepEvents.push(segment.leftSE)\n sweepEvents.push(segment.rightSE)\n }\n return sweepEvents\n }\n}\n\nexport class PolyIn {\n constructor (geomPoly, multiPoly) {\n if (!Array.isArray(geomPoly)) {\n throw new Error('Input geometry is not a valid Polygon or MultiPolygon')\n }\n this.exteriorRing = new RingIn(geomPoly[0], this, true)\n // copy by value\n this.bbox = {\n ll: { x: this.exteriorRing.bbox.ll.x, y: this.exteriorRing.bbox.ll.y },\n ur: { x: this.exteriorRing.bbox.ur.x, y: this.exteriorRing.bbox.ur.y },\n }\n this.interiorRings = []\n for (let i = 1, iMax = geomPoly.length; i < iMax; i++) {\n const ring = new RingIn(geomPoly[i], this, false)\n if (ring.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = ring.bbox.ll.x\n if (ring.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = ring.bbox.ll.y\n if (ring.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = ring.bbox.ur.x\n if (ring.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = ring.bbox.ur.y\n this.interiorRings.push(ring)\n }\n this.multiPoly = multiPoly\n }\n\n getSweepEvents () {\n const sweepEvents = this.exteriorRing.getSweepEvents()\n for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) {\n const ringSweepEvents = this.interiorRings[i].getSweepEvents()\n for (let j = 0, jMax = ringSweepEvents.length; j < jMax; j++) {\n sweepEvents.push(ringSweepEvents[j])\n }\n }\n return sweepEvents\n }\n}\n\nexport class MultiPolyIn {\n constructor (geom, isSubject) {\n if (!Array.isArray(geom)) {\n throw new Error('Input geometry is not a valid Polygon or MultiPolygon')\n }\n\n try {\n // if the input looks like a polygon, convert it to a multipolygon\n if (typeof geom[0][0][0] === 'number') geom = [geom]\n } catch (ex) {\n // The input is either malformed or has empty arrays.\n // In either case, it will be handled later on.\n }\n\n this.polys = []\n this.bbox = {\n ll: { x: Number.POSITIVE_INFINITY, y: Number.POSITIVE_INFINITY },\n ur: { x: Number.NEGATIVE_INFINITY, y: Number.NEGATIVE_INFINITY },\n }\n for (let i = 0, iMax = geom.length; i < iMax; i++) {\n const poly = new PolyIn(geom[i], this)\n if (poly.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = poly.bbox.ll.x\n if (poly.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = poly.bbox.ll.y\n if (poly.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = poly.bbox.ur.x\n if (poly.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = poly.bbox.ur.y\n this.polys.push(poly)\n }\n this.isSubject = isSubject\n }\n\n getSweepEvents () {\n const sweepEvents = []\n for (let i = 0, iMax = this.polys.length; i < iMax; i++) {\n const polySweepEvents = this.polys[i].getSweepEvents()\n for (let j = 0, jMax = polySweepEvents.length; j < jMax; j++) {\n sweepEvents.push(polySweepEvents[j])\n }\n }\n return sweepEvents\n }\n}\n","import { compareVectorAngles } from './vector'\nimport SweepEvent from './sweep-event'\n\nexport class RingOut {\n /* Given the segments from the sweep line pass, compute & return a series\n * of closed rings from all the segments marked to be part of the result */\n static factory (allSegments) {\n const ringsOut = []\n\n for (let i = 0, iMax = allSegments.length; i < iMax; i++) {\n const segment = allSegments[i]\n if (!segment.isInResult() || segment.ringOut) continue\n\n let prevEvent = null\n let event = segment.leftSE\n let nextEvent = segment.rightSE\n const events = [event]\n\n const startingPoint = event.point\n const intersectionLEs = []\n\n /* Walk the chain of linked events to form a closed ring */\n while (true) {\n prevEvent = event\n event = nextEvent\n events.push(event)\n\n /* Is the ring complete? */\n if (event.point === startingPoint) break\n\n while (true) {\n const availableLEs = event.getAvailableLinkedEvents()\n\n /* Did we hit a dead end? This shouldn't happen. Indicates some earlier\n * part of the algorithm malfunctioned... please file a bug report. */\n if (availableLEs.length === 0) {\n const firstPt = events[0].point\n const lastPt = events[events.length - 1].point\n throw new Error(\n `Unable to complete output ring starting at [${firstPt.x},` +\n ` ${firstPt.y}]. Last matching segment found ends at` +\n ` [${lastPt.x}, ${lastPt.y}].`\n )\n }\n\n /* Only one way to go, so cotinue on the path */\n if (availableLEs.length === 1) {\n nextEvent = availableLEs[0].otherSE\n break\n }\n\n /* We must have an intersection. Check for a completed loop */\n let indexLE = null\n for (let j = 0, jMax = intersectionLEs.length; j < jMax; j++) {\n if (intersectionLEs[j].point === event.point) {\n indexLE = j\n break\n }\n }\n /* Found a completed loop. Cut that off and make a ring */\n if (indexLE !== null) {\n const intersectionLE = intersectionLEs.splice(indexLE)[0]\n const ringEvents = events.splice(intersectionLE.index)\n ringEvents.unshift(ringEvents[0].otherSE)\n ringsOut.push(new RingOut(ringEvents.reverse()))\n continue\n }\n /* register the intersection */\n intersectionLEs.push({\n index: events.length,\n point: event.point,\n })\n /* Choose the left-most option to continue the walk */\n const comparator = event.getLeftmostComparator(prevEvent)\n nextEvent = availableLEs.sort(comparator)[0].otherSE\n break\n }\n }\n\n ringsOut.push(new RingOut(events))\n }\n return ringsOut\n }\n\n constructor (events) {\n this.events = events\n for (let i = 0, iMax = events.length; i < iMax; i++) {\n events[i].segment.ringOut = this\n }\n this.poly = null\n }\n\n getGeom () {\n // Remove superfluous points (ie extra points along a straight line),\n let prevPt = this.events[0].point\n const points = [prevPt]\n for (let i = 1, iMax = this.events.length - 1; i < iMax; i++) {\n const pt = this.events[i].point\n const nextPt = this.events[i + 1].point\n if (compareVectorAngles(pt, prevPt, nextPt) === 0) continue\n points.push(pt)\n prevPt = pt\n }\n\n // ring was all (within rounding error of angle calc) colinear points\n if (points.length === 1) return null\n\n // check if the starting point is necessary\n const pt = points[0]\n const nextPt = points[1]\n if (compareVectorAngles(pt, prevPt, nextPt) === 0) points.shift()\n\n points.push(points[0])\n const step = this.isExteriorRing() ? 1 : -1\n const iStart = this.isExteriorRing() ? 0 : points.length - 1\n const iEnd = this.isExteriorRing() ? points.length : -1\n const orderedPoints = []\n for (let i = iStart; i != iEnd; i += step) orderedPoints.push([points[i].x, points[i].y])\n return orderedPoints\n }\n\n isExteriorRing () {\n if (this._isExteriorRing === undefined) {\n const enclosing = this.enclosingRing()\n this._isExteriorRing = enclosing ? ! enclosing.isExteriorRing() : true\n }\n return this._isExteriorRing\n }\n\n enclosingRing () {\n if (this._enclosingRing === undefined) {\n this._enclosingRing = this._calcEnclosingRing()\n }\n return this._enclosingRing\n }\n\n /* Returns the ring that encloses this one, if any */\n _calcEnclosingRing () {\n // start with the ealier sweep line event so that the prevSeg\n // chain doesn't lead us inside of a loop of ours\n let leftMostEvt = this.events[0]\n for (let i = 1, iMax = this.events.length; i < iMax; i++) {\n const evt = this.events[i]\n if (SweepEvent.compare(leftMostEvt, evt) > 0) leftMostEvt = evt\n }\n\n let prevSeg = leftMostEvt.segment.prevInResult()\n let prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null\n\n while (true) {\n // no segment found, thus no ring can enclose us\n if (!prevSeg) return null\n\n // no segments below prev segment found, thus the ring of the prev\n // segment must loop back around and enclose us\n if (!prevPrevSeg) return prevSeg.ringOut\n\n // if the two segments are of different rings, the ring of the prev\n // segment must either loop around us or the ring of the prev prev\n // seg, which would make us and the ring of the prev peers\n if (prevPrevSeg.ringOut !== prevSeg.ringOut) {\n if (prevPrevSeg.ringOut.enclosingRing() !== prevSeg.ringOut) {\n return prevSeg.ringOut\n } else return prevSeg.ringOut.enclosingRing()\n }\n\n // two segments are from the same ring, so this was a penisula\n // of that ring. iterate downward, keep searching\n prevSeg = prevPrevSeg.prevInResult()\n prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null\n }\n }\n}\n\nexport class PolyOut {\n constructor (exteriorRing) {\n this.exteriorRing = exteriorRing\n exteriorRing.poly = this\n this.interiorRings = []\n }\n\n addInterior (ring) {\n this.interiorRings.push(ring)\n ring.poly = this\n }\n\n getGeom () {\n const geom = [this.exteriorRing.getGeom()]\n // exterior ring was all (within rounding error of angle calc) colinear points\n if (geom[0] === null) return null\n for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) {\n const ringGeom = this.interiorRings[i].getGeom()\n // interior ring was all (within rounding error of angle calc) colinear points\n if (ringGeom === null) continue\n geom.push(ringGeom)\n }\n return geom\n }\n}\n\nexport class MultiPolyOut {\n constructor (rings) {\n this.rings = rings\n this.polys = this._composePolys(rings)\n }\n\n getGeom () {\n const geom = []\n for (let i = 0, iMax = this.polys.length; i < iMax; i++) {\n const polyGeom = this.polys[i].getGeom()\n // exterior ring was all (within rounding error of angle calc) colinear points\n if (polyGeom === null) continue\n geom.push(polyGeom)\n }\n return geom\n }\n\n _composePolys (rings) {\n const polys = []\n for (let i = 0, iMax = rings.length; i < iMax; i++) {\n const ring = rings[i]\n if (ring.poly) continue\n if (ring.isExteriorRing()) polys.push(new PolyOut(ring))\n else {\n const enclosingRing = ring.enclosingRing()\n if (!enclosingRing.poly) polys.push(new PolyOut(enclosingRing))\n enclosingRing.poly.addInterior(ring)\n }\n }\n return polys\n }\n}\n","import SplayTree from 'splaytree'\nimport Segment from './segment'\nimport SweepEvent from './sweep-event'\n\n/**\n * NOTE: We must be careful not to change any segments while\n * they are in the SplayTree. AFAIK, there's no way to tell\n * the tree to rebalance itself - thus before splitting\n * a segment that's in the tree, we remove it from the tree,\n * do the split, then re-insert it. (Even though splitting a\n * segment *shouldn't* change its correct position in the\n * sweep line tree, the reality is because of rounding errors,\n * it sometimes does.)\n */\n\nexport default class SweepLine {\n constructor (queue, comparator = Segment.compare) {\n this.queue = queue\n this.tree = new SplayTree(comparator)\n this.segments = []\n }\n\n process (event) {\n const segment = event.segment\n const newEvents = []\n\n // if we've already been consumed by another segment,\n // clean up our body parts and get out\n if (event.consumedBy) {\n if (event.isLeft) this.queue.remove(event.otherSE)\n else this.tree.remove(segment)\n return newEvents\n }\n\n const node = event.isLeft\n ? this.tree.insert(segment)\n : this.tree.find(segment)\n\n if (! node) throw new Error(\n `Unable to find segment #${segment.id} ` +\n `[${segment.leftSE.point.x}, ${segment.leftSE.point.y}] -> ` +\n `[${segment.rightSE.point.x}, ${segment.rightSE.point.y}] ` +\n 'in SweepLine tree. Please submit a bug report.'\n )\n\n let prevNode = node\n let nextNode = node\n let prevSeg = undefined\n let nextSeg = undefined\n\n // skip consumed segments still in tree\n while (prevSeg === undefined) {\n prevNode = this.tree.prev(prevNode)\n if (prevNode === null) prevSeg = null\n else if (prevNode.key.consumedBy === undefined) prevSeg = prevNode.key\n }\n\n // skip consumed segments still in tree\n while (nextSeg === undefined) {\n nextNode = this.tree.next(nextNode)\n if (nextNode === null) nextSeg = null\n else if (nextNode.key.consumedBy === undefined) nextSeg = nextNode.key\n }\n\n if (event.isLeft) {\n\n // Check for intersections against the previous segment in the sweep line\n let prevMySplitter = null\n if (prevSeg) {\n const prevInter = prevSeg.getIntersection(segment)\n if (prevInter !== null) {\n if (!segment.isAnEndpoint(prevInter)) prevMySplitter = prevInter\n if (!prevSeg.isAnEndpoint(prevInter)) {\n const newEventsFromSplit = this._splitSafely(prevSeg, prevInter)\n for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {\n newEvents.push(newEventsFromSplit[i])\n }\n }\n }\n }\n\n // Check for intersections against the next segment in the sweep line\n let nextMySplitter = null\n if (nextSeg) {\n const nextInter = nextSeg.getIntersection(segment)\n if (nextInter !== null) {\n if (!segment.isAnEndpoint(nextInter)) nextMySplitter = nextInter\n if (!nextSeg.isAnEndpoint(nextInter)) {\n const newEventsFromSplit = this._splitSafely(nextSeg, nextInter)\n for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {\n newEvents.push(newEventsFromSplit[i])\n }\n }\n }\n }\n\n // For simplicity, even if we find more than one intersection we only\n // spilt on the 'earliest' (sweep-line style) of the intersections.\n // The other intersection will be handled in a future process().\n if (prevMySplitter !== null || nextMySplitter !== null) {\n\n let mySplitter = null\n if (prevMySplitter === null) mySplitter = nextMySplitter\n else if (nextMySplitter === null) mySplitter = prevMySplitter\n else {\n const cmpSplitters = SweepEvent.comparePoints(prevMySplitter, nextMySplitter)\n mySplitter = cmpSplitters <= 0 ? prevMySplitter : nextMySplitter\n }\n\n // Rounding errors can cause changes in ordering,\n // so remove afected segments and right sweep events before splitting\n this.queue.remove(segment.rightSE)\n newEvents.push(segment.rightSE)\n\n const newEventsFromSplit = segment.split(mySplitter)\n for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {\n newEvents.push(newEventsFromSplit[i])\n }\n }\n\n if (newEvents.length > 0) {\n // We found some intersections, so re-do the current event to\n // make sure sweep line ordering is totally consistent for later\n // use with the segment 'prev' pointers\n this.tree.remove(segment)\n newEvents.push(event)\n\n } else {\n // done with left event\n this.segments.push(segment)\n segment.prev = prevSeg\n }\n\n } else {\n // event.isRight\n\n // since we're about to be removed from the sweep line, check for\n // intersections between our previous and next segments\n if (prevSeg && nextSeg) {\n const inter = prevSeg.getIntersection(nextSeg)\n if (inter !== null) {\n if (!prevSeg.isAnEndpoint(inter)) {\n const newEventsFromSplit = this._splitSafely(prevSeg, inter)\n for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {\n newEvents.push(newEventsFromSplit[i])\n }\n }\n if (!nextSeg.isAnEndpoint(inter)) {\n const newEventsFromSplit = this._splitSafely(nextSeg, inter)\n for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {\n newEvents.push(newEventsFromSplit[i])\n }\n }\n }\n }\n\n this.tree.remove(segment)\n }\n\n return newEvents\n }\n\n /* Safely split a segment that is currently in the datastructures\n * IE - a segment other than the one that is currently being processed. */\n _splitSafely(seg, pt) {\n // Rounding errors can cause changes in ordering,\n // so remove afected segments and right sweep events before splitting\n // removeNode() doesn't work, so have re-find the seg\n // https://github.com/w8r/splay-tree/pull/5\n this.tree.remove(seg)\n const rightSE = seg.rightSE\n this.queue.remove(rightSE)\n const newEvents = seg.split(pt)\n newEvents.push(rightSE)\n // splitting can trigger consumption\n if (seg.consumedBy === undefined) this.tree.insert(seg)\n return newEvents\n }\n}\n","import SplayTree from 'splaytree'\nimport { getBboxOverlap } from './bbox'\nimport * as geomIn from './geom-in'\nimport * as geomOut from './geom-out'\nimport rounder from './rounder'\nimport SweepEvent from './sweep-event'\nimport SweepLine from './sweep-line'\n\n// Limits on iterative processes to prevent infinite loops - usually caused by floating-point math round-off errors.\nconst POLYGON_CLIPPING_MAX_QUEUE_SIZE =\n (typeof process !== 'undefined' &&\n process.env.POLYGON_CLIPPING_MAX_QUEUE_SIZE) ||\n 1000000\nconst POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS =\n (typeof process !== 'undefined' &&\n process.env.POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS) ||\n 1000000\n\nexport class Operation {\n run (type, geom, moreGeoms) {\n operation.type = type\n rounder.reset()\n\n /* Convert inputs to MultiPoly objects */\n const multipolys = [new geomIn.MultiPolyIn(geom, true)]\n for (let i = 0, iMax = moreGeoms.length; i < iMax; i++) {\n multipolys.push(new geomIn.MultiPolyIn(moreGeoms[i], false))\n }\n operation.numMultiPolys = multipolys.length\n\n /* BBox optimization for difference operation\n * If the bbox of a multipolygon that's part of the clipping doesn't\n * intersect the bbox of the subject at all, we can just drop that\n * multiploygon. */\n if (operation.type === 'difference') {\n // in place removal\n const subject = multipolys[0]\n let i = 1\n while (i < multipolys.length) {\n if (getBboxOverlap(multipolys[i].bbox, subject.bbox) !== null) i++\n else multipolys.splice(i, 1)\n }\n }\n\n /* BBox optimization for intersection operation\n * If we can find any pair of multipolygons whose bbox does not overlap,\n * then the result will be empty. */\n if (operation.type === 'intersection') {\n // TODO: this is O(n^2) in number of polygons. By sorting the bboxes,\n // it could be optimized to O(n * ln(n))\n for (let i = 0, iMax = multipolys.length; i < iMax; i++) {\n const mpA = multipolys[i]\n for (let j = i + 1, jMax = multipolys.length; j < jMax; j++) {\n if (getBboxOverlap(mpA.bbox, multipolys[j].bbox) === null) return []\n }\n }\n }\n\n /* Put segment endpoints in a priority queue */\n const queue = new SplayTree(SweepEvent.compare)\n for (let i = 0, iMax = multipolys.length; i < iMax; i++) {\n const sweepEvents = multipolys[i].getSweepEvents()\n for (let j = 0, jMax = sweepEvents.length; j < jMax; j++) {\n queue.insert(sweepEvents[j])\n\n if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) {\n // prevents an infinite loop, an otherwise common manifestation of bugs\n throw new Error(\n 'Infinite loop when putting segment endpoints in a priority queue ' +\n '(queue size too big). Please file a bug report.'\n )\n }\n }\n }\n\n /* Pass the sweep line over those endpoints */\n const sweepLine = new SweepLine(queue)\n let prevQueueSize = queue.size\n let node = queue.pop()\n while (node) {\n const evt = node.key\n if (queue.size === prevQueueSize) {\n // prevents an infinite loop, an otherwise common manifestation of bugs\n const seg = evt.segment\n throw new Error(\n `Unable to pop() ${evt.isLeft ? 'left' : 'right'} SweepEvent ` +\n `[${evt.point.x}, ${evt.point.y}] from segment #${seg.id} ` +\n `[${seg.leftSE.point.x}, ${seg.leftSE.point.y}] -> ` +\n `[${seg.rightSE.point.x}, ${seg.rightSE.point.y}] from queue. ` +\n 'Please file a bug report.'\n )\n }\n\n if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) {\n // prevents an infinite loop, an otherwise common manifestation of bugs\n throw new Error(\n 'Infinite loop when passing sweep line over endpoints ' +\n '(queue size too big). Please file a bug report.'\n )\n }\n\n if (sweepLine.segments.length > POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS) {\n // prevents an infinite loop, an otherwise common manifestation of bugs\n throw new Error(\n 'Infinite loop when passing sweep line over endpoints ' +\n '(too many sweep line segments). Please file a bug report.'\n )\n }\n\n const newEvents = sweepLine.process(evt)\n for (let i = 0, iMax = newEvents.length; i < iMax; i++) {\n const evt = newEvents[i]\n if (evt.consumedBy === undefined) queue.insert(evt)\n }\n prevQueueSize = queue.size\n node = queue.pop()\n }\n\n // free some memory we don't need anymore\n rounder.reset()\n\n /* Collect and compile segments we're keeping into a multipolygon */\n const ringsOut = geomOut.RingOut.factory(sweepLine.segments)\n const result = new geomOut.MultiPolyOut(ringsOut)\n return result.getGeom()\n }\n}\n\n// singleton available by import\nconst operation = new Operation()\n\nexport default operation\n","import operation from './operation'\n\nconst union = (geom, ...moreGeoms) =>\n operation.run('union', geom, moreGeoms)\n\nconst intersection = (geom, ...moreGeoms) =>\n operation.run('intersection', geom, moreGeoms)\n\nconst xor = (geom, ...moreGeoms) =>\n operation.run('xor', geom, moreGeoms)\n\nconst difference = (subjectGeom, ...clippingGeoms) =>\n operation.run('difference', subjectGeom, clippingGeoms)\n\nexport default {\n union: union,\n intersection: intersection,\n xor: xor,\n difference: difference,\n}\n"],"names":["isInBbox","bbox","point","ll","x","ur","y","getBboxOverlap","b1","b2","lowerX","upperX","epsilon","Number","EPSILON","undefined","Math","pow","EPSILON_SQ","cmp","a","b","ab","PtRounder","reset","xRounder","CoordRounder","yRounder","this","round","tree","SplayTree","coord","node","add","prevNode","prev","key","remove","nextNode","next","rounder","crossProduct","dotProduct","compareVectorAngles","basePt","endPt1","endPt2","v1","v2","kross","length","v","sqrt","cosineOfAngle","pShared","pBase","pAngle","vBase","vAngle","horizontalIntersection","pt","verticalIntersection","SweepEvent","isLeft","events","push","ptCmp","comparePoints","link","Segment","compare","segment","aPt","bPt","other","Error","otherEvents","i","iMax","evt","checkForConsuming","numEvents","evt1","consumedBy","j","evt2","otherSE","consume","ringOut","isInResult","baseEvent","cache","Map","fillCache","linkedEvent","nextEvent","set","sine","_this","cosine","has","get","asine","acosine","bsine","bcosine","segmentId","leftSE","rightSE","rings","windings","id","alx","blx","arx","brx","aly","bly","ary","bry","aCmpBLeft","comparePoint","bCmpARight","bCmpALeft","aCmpBRight","ay","ax","by","bx","newRightSE","y1","y2","isAnEndpoint","lPt","rPt","vector","yDist","xFromYDist","xDist","yFromXDist","tBbox","oBbox","bboxOverlap","tlp","trp","olp","orp","touchesOtherLSE","touchesThisLSE","touchesOtherRSE","touchesThisRSE","pt1","pt2","ve","d1","d2","intersection","newEvents","alreadyLinked","newLeftSE","oldRightSE","replaceRightSE","newSeg","slice","swapEvents","tmpEvt","consumer","consumee","tmp","ring","winding","index","indexOf","_prevInResult","prevInResult","_beforeState","seg","afterState","multiPolys","_afterState","beforeState","ringsAfter","windingsAfter","mpsAfter","polysAfter","polysExclude","poly","isExterior","splice","mp","multiPoly","_isInResult","mpsBefore","operation","type","noBefores","noAfters","least","most","numMultiPolys","diff","abs","isJustSubject","mps","isSubject","leftPt","rightPt","cmpPts","RingIn","geomRing","Array","isArray","segments","firstPoint","prevPoint","fromRing","sweepEvents","PolyIn","geomPoly","exteriorRing","interiorRings","getSweepEvents","ringSweepEvents","jMax","MultiPolyIn","geom","ex","polys","POSITIVE_INFINITY","NEGATIVE_INFINITY","polySweepEvents","RingOut","allSegments","ringsOut","prevEvent","event","startingPoint","intersectionLEs","availableLEs","getAvailableLinkedEvents","firstPt","lastPt","indexLE","comparator","getLeftmostComparator","sort","intersectionLE","ringEvents","unshift","reverse","prevPt","points","nextPt","shift","step","isExteriorRing","iStart","iEnd","orderedPoints","_isExteriorRing","enclosing","enclosingRing","_enclosingRing","_calcEnclosingRing","leftMostEvt","prevSeg","prevPrevSeg","PolyOut","getGeom","ringGeom","MultiPolyOut","_composePolys","polyGeom","addInterior","SweepLine","queue","insert","find","nextSeg","prevMySplitter","prevInter","getIntersection","newEventsFromSplit","_splitSafely","nextMySplitter","nextInter","mySplitter","split","inter","POLYGON_CLIPPING_MAX_QUEUE_SIZE","process","env","POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS","moreGeoms","multipolys","geomIn","subject","mpA","size","sweepLine","prevQueueSize","pop","geomOut","factory","union","run","xor","difference","subjectGeom","clippingGeoms"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"polygon-clipping.umd.min.js","sources":["../src/bbox.js","../src/flp.js","../src/rounder.js","../node_modules/robust-predicates/esm/util.js","../node_modules/robust-predicates/esm/orient2d.js","../src/vector.js","../src/sweep-event.js","../src/segment.js","../src/geom-in.js","../src/geom-out.js","../src/sweep-line.js","../src/operation.js","../src/index.js"],"sourcesContent":["/**\n * A bounding box has the format:\n *\n * { ll: { x: xmin, y: ymin }, ur: { x: xmax, y: ymax } }\n *\n */\n\nexport const isInBbox = (bbox, point) => {\n return (\n bbox.ll.x <= point.x &&\n point.x <= bbox.ur.x &&\n bbox.ll.y <= point.y &&\n point.y <= bbox.ur.y\n )\n}\n\n/* Returns either null, or a bbox (aka an ordered pair of points)\n * If there is only one point of overlap, a bbox with identical points\n * will be returned */\nexport const getBboxOverlap = (b1, b2) => {\n // check if the bboxes overlap at all\n if (\n b2.ur.x < b1.ll.x ||\n b1.ur.x < b2.ll.x ||\n b2.ur.y < b1.ll.y ||\n b1.ur.y < b2.ll.y\n )\n return null\n\n // find the middle two X values\n const lowerX = b1.ll.x < b2.ll.x ? b2.ll.x : b1.ll.x\n const upperX = b1.ur.x < b2.ur.x ? b1.ur.x : b2.ur.x\n\n // find the middle two Y values\n const lowerY = b1.ll.y < b2.ll.y ? b2.ll.y : b1.ll.y\n const upperY = b1.ur.y < b2.ur.y ? b1.ur.y : b2.ur.y\n\n // put those middle values together to get the overlap\n return { ll: { x: lowerX, y: lowerY }, ur: { x: upperX, y: upperY } }\n}\n","/* Javascript doesn't do integer math. Everything is\n * floating point with percision Number.EPSILON.\n *\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON\n */\n\nlet epsilon = Number.EPSILON\n\n// IE Polyfill\nif (epsilon === undefined) epsilon = Math.pow(2, -52)\n\nconst EPSILON_SQ = epsilon * epsilon\n\n/* FLP comparator */\nexport const cmp = (a, b) => {\n // check if they're both 0\n if (-epsilon < a && a < epsilon) {\n if (-epsilon < b && b < epsilon) {\n return 0\n }\n }\n\n // check if they're flp equal\n const ab = a - b\n if (ab * ab < EPSILON_SQ * a * b) {\n return 0\n }\n\n // normal comparison\n return a < b ? -1 : 1\n}\n","import { cmp } from \"./flp\"\nimport SplayTree from \"splaytree\"\n\n/**\n * This class rounds incoming values sufficiently so that\n * floating points problems are, for the most part, avoided.\n *\n * Incoming points are have their x & y values tested against\n * all previously seen x & y values. If either is 'too close'\n * to a previously seen value, it's value is 'snapped' to the\n * previously seen value.\n *\n * All points should be rounded by this class before being\n * stored in any data structures in the rest of this algorithm.\n */\n\nclass PtRounder {\n constructor() {\n this.reset()\n }\n\n reset() {\n this.xRounder = new CoordRounder()\n this.yRounder = new CoordRounder()\n }\n\n round(x, y) {\n return {\n x: this.xRounder.round(x),\n y: this.yRounder.round(y),\n }\n }\n}\n\nclass CoordRounder {\n constructor() {\n this.tree = new SplayTree()\n // preseed with 0 so we don't end up with values < Number.EPSILON\n this.round(0)\n }\n\n // Note: this can rounds input values backwards or forwards.\n // You might ask, why not restrict this to just rounding\n // forwards? Wouldn't that allow left endpoints to always\n // remain left endpoints during splitting (never change to\n // right). No - it wouldn't, because we snap intersections\n // to endpoints (to establish independence from the segment\n // angle for t-intersections).\n round(coord) {\n const node = this.tree.add(coord)\n\n const prevNode = this.tree.prev(node)\n if (prevNode !== null && cmp(node.key, prevNode.key) === 0) {\n this.tree.remove(coord)\n return prevNode.key\n }\n\n const nextNode = this.tree.next(node)\n if (nextNode !== null && cmp(node.key, nextNode.key) === 0) {\n this.tree.remove(coord)\n return nextNode.key\n }\n\n return coord\n }\n}\n\n// singleton available by import\nconst rounder = new PtRounder()\n\nexport default rounder\n","export const epsilon = 1.1102230246251565e-16;\nexport const splitter = 134217729;\nexport const resulterrbound = (3 + 8 * epsilon) * epsilon;\n\n// fast_expansion_sum_zeroelim routine from oritinal code\nexport function sum(elen, e, flen, f, h) {\n let Q, Qnew, hh, bvirt;\n let enow = e[0];\n let fnow = f[0];\n let eindex = 0;\n let findex = 0;\n if ((fnow > enow) === (fnow > -enow)) {\n Q = enow;\n enow = e[++eindex];\n } else {\n Q = fnow;\n fnow = f[++findex];\n }\n let hindex = 0;\n if (eindex < elen && findex < flen) {\n if ((fnow > enow) === (fnow > -enow)) {\n Qnew = enow + Q;\n hh = Q - (Qnew - enow);\n enow = e[++eindex];\n } else {\n Qnew = fnow + Q;\n hh = Q - (Qnew - fnow);\n fnow = f[++findex];\n }\n Q = Qnew;\n if (hh !== 0) {\n h[hindex++] = hh;\n }\n while (eindex < elen && findex < flen) {\n if ((fnow > enow) === (fnow > -enow)) {\n Qnew = Q + enow;\n bvirt = Qnew - Q;\n hh = Q - (Qnew - bvirt) + (enow - bvirt);\n enow = e[++eindex];\n } else {\n Qnew = Q + fnow;\n bvirt = Qnew - Q;\n hh = Q - (Qnew - bvirt) + (fnow - bvirt);\n fnow = f[++findex];\n }\n Q = Qnew;\n if (hh !== 0) {\n h[hindex++] = hh;\n }\n }\n }\n while (eindex < elen) {\n Qnew = Q + enow;\n bvirt = Qnew - Q;\n hh = Q - (Qnew - bvirt) + (enow - bvirt);\n enow = e[++eindex];\n Q = Qnew;\n if (hh !== 0) {\n h[hindex++] = hh;\n }\n }\n while (findex < flen) {\n Qnew = Q + fnow;\n bvirt = Qnew - Q;\n hh = Q - (Qnew - bvirt) + (fnow - bvirt);\n fnow = f[++findex];\n Q = Qnew;\n if (hh !== 0) {\n h[hindex++] = hh;\n }\n }\n if (Q !== 0 || hindex === 0) {\n h[hindex++] = Q;\n }\n return hindex;\n}\n\nexport function sum_three(alen, a, blen, b, clen, c, tmp, out) {\n return sum(sum(alen, a, blen, b, tmp), tmp, clen, c, out);\n}\n\n// scale_expansion_zeroelim routine from oritinal code\nexport function scale(elen, e, b, h) {\n let Q, sum, hh, product1, product0;\n let bvirt, c, ahi, alo, bhi, blo;\n\n c = splitter * b;\n bhi = c - (c - b);\n blo = b - bhi;\n let enow = e[0];\n Q = enow * b;\n c = splitter * enow;\n ahi = c - (c - enow);\n alo = enow - ahi;\n hh = alo * blo - (Q - ahi * bhi - alo * bhi - ahi * blo);\n let hindex = 0;\n if (hh !== 0) {\n h[hindex++] = hh;\n }\n for (let i = 1; i < elen; i++) {\n enow = e[i];\n product1 = enow * b;\n c = splitter * enow;\n ahi = c - (c - enow);\n alo = enow - ahi;\n product0 = alo * blo - (product1 - ahi * bhi - alo * bhi - ahi * blo);\n sum = Q + product0;\n bvirt = sum - Q;\n hh = Q - (sum - bvirt) + (product0 - bvirt);\n if (hh !== 0) {\n h[hindex++] = hh;\n }\n Q = product1 + sum;\n hh = sum - (Q - product1);\n if (hh !== 0) {\n h[hindex++] = hh;\n }\n }\n if (Q !== 0 || hindex === 0) {\n h[hindex++] = Q;\n }\n return hindex;\n}\n\nexport function negate(elen, e) {\n for (let i = 0; i < elen; i++) e[i] = -e[i];\n return elen;\n}\n\nexport function estimate(elen, e) {\n let Q = e[0];\n for (let i = 1; i < elen; i++) Q += e[i];\n return Q;\n}\n\nexport function vec(n) {\n return new Float64Array(n);\n}\n","import {epsilon, splitter, resulterrbound, estimate, vec, sum} from './util.js';\n\nconst ccwerrboundA = (3 + 16 * epsilon) * epsilon;\nconst ccwerrboundB = (2 + 12 * epsilon) * epsilon;\nconst ccwerrboundC = (9 + 64 * epsilon) * epsilon * epsilon;\n\nconst B = vec(4);\nconst C1 = vec(8);\nconst C2 = vec(12);\nconst D = vec(16);\nconst u = vec(4);\n\nfunction orient2dadapt(ax, ay, bx, by, cx, cy, detsum) {\n let acxtail, acytail, bcxtail, bcytail;\n let bvirt, c, ahi, alo, bhi, blo, _i, _j, _0, s1, s0, t1, t0, u3;\n\n const acx = ax - cx;\n const bcx = bx - cx;\n const acy = ay - cy;\n const bcy = by - cy;\n\n s1 = acx * bcy;\n c = splitter * acx;\n ahi = c - (c - acx);\n alo = acx - ahi;\n c = splitter * bcy;\n bhi = c - (c - bcy);\n blo = bcy - bhi;\n s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);\n t1 = acy * bcx;\n c = splitter * acy;\n ahi = c - (c - acy);\n alo = acy - ahi;\n c = splitter * bcx;\n bhi = c - (c - bcx);\n blo = bcx - bhi;\n t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);\n _i = s0 - t0;\n bvirt = s0 - _i;\n B[0] = s0 - (_i + bvirt) + (bvirt - t0);\n _j = s1 + _i;\n bvirt = _j - s1;\n _0 = s1 - (_j - bvirt) + (_i - bvirt);\n _i = _0 - t1;\n bvirt = _0 - _i;\n B[1] = _0 - (_i + bvirt) + (bvirt - t1);\n u3 = _j + _i;\n bvirt = u3 - _j;\n B[2] = _j - (u3 - bvirt) + (_i - bvirt);\n B[3] = u3;\n\n let det = estimate(4, B);\n let errbound = ccwerrboundB * detsum;\n if (det >= errbound || -det >= errbound) {\n return det;\n }\n\n bvirt = ax - acx;\n acxtail = ax - (acx + bvirt) + (bvirt - cx);\n bvirt = bx - bcx;\n bcxtail = bx - (bcx + bvirt) + (bvirt - cx);\n bvirt = ay - acy;\n acytail = ay - (acy + bvirt) + (bvirt - cy);\n bvirt = by - bcy;\n bcytail = by - (bcy + bvirt) + (bvirt - cy);\n\n if (acxtail === 0 && acytail === 0 && bcxtail === 0 && bcytail === 0) {\n return det;\n }\n\n errbound = ccwerrboundC * detsum + resulterrbound * Math.abs(det);\n det += (acx * bcytail + bcy * acxtail) - (acy * bcxtail + bcx * acytail);\n if (det >= errbound || -det >= errbound) return det;\n\n s1 = acxtail * bcy;\n c = splitter * acxtail;\n ahi = c - (c - acxtail);\n alo = acxtail - ahi;\n c = splitter * bcy;\n bhi = c - (c - bcy);\n blo = bcy - bhi;\n s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);\n t1 = acytail * bcx;\n c = splitter * acytail;\n ahi = c - (c - acytail);\n alo = acytail - ahi;\n c = splitter * bcx;\n bhi = c - (c - bcx);\n blo = bcx - bhi;\n t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);\n _i = s0 - t0;\n bvirt = s0 - _i;\n u[0] = s0 - (_i + bvirt) + (bvirt - t0);\n _j = s1 + _i;\n bvirt = _j - s1;\n _0 = s1 - (_j - bvirt) + (_i - bvirt);\n _i = _0 - t1;\n bvirt = _0 - _i;\n u[1] = _0 - (_i + bvirt) + (bvirt - t1);\n u3 = _j + _i;\n bvirt = u3 - _j;\n u[2] = _j - (u3 - bvirt) + (_i - bvirt);\n u[3] = u3;\n const C1len = sum(4, B, 4, u, C1);\n\n s1 = acx * bcytail;\n c = splitter * acx;\n ahi = c - (c - acx);\n alo = acx - ahi;\n c = splitter * bcytail;\n bhi = c - (c - bcytail);\n blo = bcytail - bhi;\n s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);\n t1 = acy * bcxtail;\n c = splitter * acy;\n ahi = c - (c - acy);\n alo = acy - ahi;\n c = splitter * bcxtail;\n bhi = c - (c - bcxtail);\n blo = bcxtail - bhi;\n t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);\n _i = s0 - t0;\n bvirt = s0 - _i;\n u[0] = s0 - (_i + bvirt) + (bvirt - t0);\n _j = s1 + _i;\n bvirt = _j - s1;\n _0 = s1 - (_j - bvirt) + (_i - bvirt);\n _i = _0 - t1;\n bvirt = _0 - _i;\n u[1] = _0 - (_i + bvirt) + (bvirt - t1);\n u3 = _j + _i;\n bvirt = u3 - _j;\n u[2] = _j - (u3 - bvirt) + (_i - bvirt);\n u[3] = u3;\n const C2len = sum(C1len, C1, 4, u, C2);\n\n s1 = acxtail * bcytail;\n c = splitter * acxtail;\n ahi = c - (c - acxtail);\n alo = acxtail - ahi;\n c = splitter * bcytail;\n bhi = c - (c - bcytail);\n blo = bcytail - bhi;\n s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);\n t1 = acytail * bcxtail;\n c = splitter * acytail;\n ahi = c - (c - acytail);\n alo = acytail - ahi;\n c = splitter * bcxtail;\n bhi = c - (c - bcxtail);\n blo = bcxtail - bhi;\n t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);\n _i = s0 - t0;\n bvirt = s0 - _i;\n u[0] = s0 - (_i + bvirt) + (bvirt - t0);\n _j = s1 + _i;\n bvirt = _j - s1;\n _0 = s1 - (_j - bvirt) + (_i - bvirt);\n _i = _0 - t1;\n bvirt = _0 - _i;\n u[1] = _0 - (_i + bvirt) + (bvirt - t1);\n u3 = _j + _i;\n bvirt = u3 - _j;\n u[2] = _j - (u3 - bvirt) + (_i - bvirt);\n u[3] = u3;\n const Dlen = sum(C2len, C2, 4, u, D);\n\n return D[Dlen - 1];\n}\n\nexport function orient2d(ax, ay, bx, by, cx, cy) {\n const detleft = (ay - cy) * (bx - cx);\n const detright = (ax - cx) * (by - cy);\n const det = detleft - detright;\n\n const detsum = Math.abs(detleft + detright);\n if (Math.abs(det) >= ccwerrboundA * detsum) return det;\n\n return -orient2dadapt(ax, ay, bx, by, cx, cy, detsum);\n}\n\nexport function orient2dfast(ax, ay, bx, by, cx, cy) {\n return (ay - cy) * (bx - cx) - (ax - cx) * (by - cy);\n}\n","import { orient2d } from \"robust-predicates\"\n\n/* Cross Product of two vectors with first point at origin */\nexport const crossProduct = (a, b) => a.x * b.y - a.y * b.x\n\n/* Dot Product of two vectors with first point at origin */\nexport const dotProduct = (a, b) => a.x * b.x + a.y * b.y\n\n/* Comparator for two vectors with same starting point */\nexport const compareVectorAngles = (basePt, endPt1, endPt2) => {\n const res = orient2d(\n basePt.x,\n basePt.y,\n endPt1.x,\n endPt1.y,\n endPt2.x,\n endPt2.y,\n )\n if (res > 0) return -1\n if (res < 0) return 1\n return 0\n}\n\nexport const length = (v) => Math.sqrt(dotProduct(v, v))\n\n/* Get the sine of the angle from pShared -> pAngle to pShaed -> pBase */\nexport const sineOfAngle = (pShared, pBase, pAngle) => {\n const vBase = { x: pBase.x - pShared.x, y: pBase.y - pShared.y }\n const vAngle = { x: pAngle.x - pShared.x, y: pAngle.y - pShared.y }\n return crossProduct(vAngle, vBase) / length(vAngle) / length(vBase)\n}\n\n/* Get the cosine of the angle from pShared -> pAngle to pShaed -> pBase */\nexport const cosineOfAngle = (pShared, pBase, pAngle) => {\n const vBase = { x: pBase.x - pShared.x, y: pBase.y - pShared.y }\n const vAngle = { x: pAngle.x - pShared.x, y: pAngle.y - pShared.y }\n return dotProduct(vAngle, vBase) / length(vAngle) / length(vBase)\n}\n\n/* Get the closest point on an line (defined by two points)\n * to another point. */\nexport const closestPoint = (ptA1, ptA2, ptB) => {\n if (ptA1.x === ptA2.x) return { x: ptA1.x, y: ptB.y } // vertical vector\n if (ptA1.y === ptA2.y) return { x: ptB.x, y: ptA1.y } // horizontal vector\n\n // determinne which point is further away\n // we use the further point as our base in the calculation, so that the\n // vectors are more parallel, providing more accurate dot product\n const v1 = { x: ptB.x - ptA1.x, y: ptB.y - ptA1.y }\n const v2 = { x: ptB.x - ptA2.x, y: ptB.y - ptA2.y }\n let vFar, vA, farPt\n if (dotProduct(v1, v1) > dotProduct(v2, v2)) {\n vFar = v1\n vA = { x: ptA2.x - ptA1.x, y: ptA2.y - ptA1.y }\n farPt = ptA1\n } else {\n vFar = v2\n vA = { x: ptA1.x - ptA2.x, y: ptA1.y - ptA2.y }\n farPt = ptA2\n }\n\n // manually test if the current point can be considered to be on the line\n // If the X coordinate was on the line, would the Y coordinate be as well?\n const xDist = (ptB.x - farPt.x) / vA.x\n if (ptB.y === farPt.y + xDist * vA.y) return ptB\n\n // If the Y coordinate was on the line, would the X coordinate be as well?\n const yDist = (ptB.y - farPt.y) / vA.y\n if (ptB.x === farPt.x + yDist * vA.x) return ptB\n\n // current point isn't exactly on line, so return closest point\n const dist = dotProduct(vA, vFar) / dotProduct(vA, vA)\n return { x: farPt.x + dist * vA.x, y: farPt.y + dist * vA.y }\n}\n\n/* Get the x coordinate where the given line (defined by a point and vector)\n * crosses the horizontal line with the given y coordiante.\n * In the case of parrallel lines (including overlapping ones) returns null. */\nexport const horizontalIntersection = (pt, v, y) => {\n if (v.y === 0) return null\n return { x: pt.x + (v.x / v.y) * (y - pt.y), y: y }\n}\n\n/* Get the y coordinate where the given line (defined by a point and vector)\n * crosses the vertical line with the given x coordiante.\n * In the case of parrallel lines (including overlapping ones) returns null. */\nexport const verticalIntersection = (pt, v, x) => {\n if (v.x === 0) return null\n return { x: x, y: pt.y + (v.y / v.x) * (x - pt.x) }\n}\n\n/* Get the intersection of two lines, each defined by a base point and a vector.\n * In the case of parrallel lines (including overlapping ones) returns null. */\nexport const intersection = (pt1, v1, pt2, v2) => {\n // take some shortcuts for vertical and horizontal lines\n // this also ensures we don't calculate an intersection and then discover\n // it's actually outside the bounding box of the line\n if (v1.x === 0) return verticalIntersection(pt2, v2, pt1.x)\n if (v2.x === 0) return verticalIntersection(pt1, v1, pt2.x)\n if (v1.y === 0) return horizontalIntersection(pt2, v2, pt1.y)\n if (v2.y === 0) return horizontalIntersection(pt1, v1, pt2.y)\n\n // General case for non-overlapping segments.\n // This algorithm is based on Schneider and Eberly.\n // http://www.cimec.org.ar/~ncalvo/Schneider_Eberly.pdf - pg 244\n\n const kross = crossProduct(v1, v2)\n if (kross == 0) return null\n\n const ve = { x: pt2.x - pt1.x, y: pt2.y - pt1.y }\n const d1 = crossProduct(ve, v1) / kross\n const d2 = crossProduct(ve, v2) / kross\n\n // take the average of the two calculations to minimize rounding error\n const x1 = pt1.x + d2 * v1.x,\n x2 = pt2.x + d1 * v2.x\n const y1 = pt1.y + d2 * v1.y,\n y2 = pt2.y + d1 * v2.y\n const x = (x1 + x2) / 2\n const y = (y1 + y2) / 2\n return { x: x, y: y }\n}\n\n/* Given a vector, return one that is perpendicular */\nexport const perpendicular = (v) => {\n return { x: -v.y, y: v.x }\n}\n","import Segment from \"./segment\"\nimport { cosineOfAngle, sineOfAngle } from \"./vector\"\n\nexport default class SweepEvent {\n // for ordering sweep events in the sweep event queue\n static compare(a, b) {\n // favor event with a point that the sweep line hits first\n const ptCmp = SweepEvent.comparePoints(a.point, b.point)\n if (ptCmp !== 0) return ptCmp\n\n // the points are the same, so link them if needed\n if (a.point !== b.point) a.link(b)\n\n // favor right events over left\n if (a.isLeft !== b.isLeft) return a.isLeft ? 1 : -1\n\n // we have two matching left or right endpoints\n // ordering of this case is the same as for their segments\n return Segment.compare(a.segment, b.segment)\n }\n\n // for ordering points in sweep line order\n static comparePoints(aPt, bPt) {\n if (aPt.x < bPt.x) return -1\n if (aPt.x > bPt.x) return 1\n\n if (aPt.y < bPt.y) return -1\n if (aPt.y > bPt.y) return 1\n\n return 0\n }\n\n // Warning: 'point' input will be modified and re-used (for performance)\n constructor(point, isLeft) {\n if (point.events === undefined) point.events = [this]\n else point.events.push(this)\n this.point = point\n this.isLeft = isLeft\n // this.segment, this.otherSE set by factory\n }\n\n link(other) {\n if (other.point === this.point) {\n throw new Error(\"Tried to link already linked events\")\n }\n const otherEvents = other.point.events\n for (let i = 0, iMax = otherEvents.length; i < iMax; i++) {\n const evt = otherEvents[i]\n this.point.events.push(evt)\n evt.point = this.point\n }\n this.checkForConsuming()\n }\n\n /* Do a pass over our linked events and check to see if any pair\n * of segments match, and should be consumed. */\n checkForConsuming() {\n // FIXME: The loops in this method run O(n^2) => no good.\n // Maintain little ordered sweep event trees?\n // Can we maintaining an ordering that avoids the need\n // for the re-sorting with getLeftmostComparator in geom-out?\n\n // Compare each pair of events to see if other events also match\n const numEvents = this.point.events.length\n for (let i = 0; i < numEvents; i++) {\n const evt1 = this.point.events[i]\n if (evt1.segment.consumedBy !== undefined) continue\n for (let j = i + 1; j < numEvents; j++) {\n const evt2 = this.point.events[j]\n if (evt2.consumedBy !== undefined) continue\n if (evt1.otherSE.point.events !== evt2.otherSE.point.events) continue\n evt1.segment.consume(evt2.segment)\n }\n }\n }\n\n getAvailableLinkedEvents() {\n // point.events is always of length 2 or greater\n const events = []\n for (let i = 0, iMax = this.point.events.length; i < iMax; i++) {\n const evt = this.point.events[i]\n if (evt !== this && !evt.segment.ringOut && evt.segment.isInResult()) {\n events.push(evt)\n }\n }\n return events\n }\n\n /**\n * Returns a comparator function for sorting linked events that will\n * favor the event that will give us the smallest left-side angle.\n * All ring construction starts as low as possible heading to the right,\n * so by always turning left as sharp as possible we'll get polygons\n * without uncessary loops & holes.\n *\n * The comparator function has a compute cache such that it avoids\n * re-computing already-computed values.\n */\n getLeftmostComparator(baseEvent) {\n const cache = new Map()\n\n const fillCache = (linkedEvent) => {\n const nextEvent = linkedEvent.otherSE\n cache.set(linkedEvent, {\n sine: sineOfAngle(this.point, baseEvent.point, nextEvent.point),\n cosine: cosineOfAngle(this.point, baseEvent.point, nextEvent.point),\n })\n }\n\n return (a, b) => {\n if (!cache.has(a)) fillCache(a)\n if (!cache.has(b)) fillCache(b)\n\n const { sine: asine, cosine: acosine } = cache.get(a)\n const { sine: bsine, cosine: bcosine } = cache.get(b)\n\n // both on or above x-axis\n if (asine >= 0 && bsine >= 0) {\n if (acosine < bcosine) return 1\n if (acosine > bcosine) return -1\n return 0\n }\n\n // both below x-axis\n if (asine < 0 && bsine < 0) {\n if (acosine < bcosine) return -1\n if (acosine > bcosine) return 1\n return 0\n }\n\n // one above x-axis, one below\n if (bsine < asine) return -1\n if (bsine > asine) return 1\n return 0\n }\n }\n}\n","import operation from \"./operation\"\nimport SweepEvent from \"./sweep-event\"\nimport { isInBbox, getBboxOverlap } from \"./bbox\"\nimport { intersection } from \"./vector\"\nimport rounder from \"./rounder\"\n\n// Give segments unique ID's to get consistent sorting of\n// segments and sweep events when all else is identical\nlet segmentId = 0\n\nexport default class Segment {\n /* This compare() function is for ordering segments in the sweep\n * line tree, and does so according to the following criteria:\n *\n * Consider the vertical line that lies an infinestimal step to the\n * right of the right-more of the two left endpoints of the input\n * segments. Imagine slowly moving a point up from negative infinity\n * in the increasing y direction. Which of the two segments will that\n * point intersect first? That segment comes 'before' the other one.\n *\n * If neither segment would be intersected by such a line, (if one\n * or more of the segments are vertical) then the line to be considered\n * is directly on the right-more of the two left inputs.\n */\n static compare(a, b) {\n const alx = a.leftSE.point.x\n const blx = b.leftSE.point.x\n const arx = a.rightSE.point.x\n const brx = b.rightSE.point.x\n\n // check if they're even in the same vertical plane\n if (brx < alx) return 1\n if (arx < blx) return -1\n\n const aly = a.leftSE.point.y\n const bly = b.leftSE.point.y\n const ary = a.rightSE.point.y\n const bry = b.rightSE.point.y\n\n // is left endpoint of segment B the right-more?\n if (alx < blx) {\n // are the two segments in the same horizontal plane?\n if (bly < aly && bly < ary) return 1\n if (bly > aly && bly > ary) return -1\n\n // is the B left endpoint colinear to segment A?\n const aCmpBLeft = a.comparePoint(b.leftSE.point)\n if (aCmpBLeft < 0) return 1\n if (aCmpBLeft > 0) return -1\n\n // is the A right endpoint colinear to segment B ?\n const bCmpARight = b.comparePoint(a.rightSE.point)\n if (bCmpARight !== 0) return bCmpARight\n\n // colinear segments, consider the one with left-more\n // left endpoint to be first (arbitrary?)\n return -1\n }\n\n // is left endpoint of segment A the right-more?\n if (alx > blx) {\n if (aly < bly && aly < bry) return -1\n if (aly > bly && aly > bry) return 1\n\n // is the A left endpoint colinear to segment B?\n const bCmpALeft = b.comparePoint(a.leftSE.point)\n if (bCmpALeft !== 0) return bCmpALeft\n\n // is the B right endpoint colinear to segment A?\n const aCmpBRight = a.comparePoint(b.rightSE.point)\n if (aCmpBRight < 0) return 1\n if (aCmpBRight > 0) return -1\n\n // colinear segments, consider the one with left-more\n // left endpoint to be first (arbitrary?)\n return 1\n }\n\n // if we get here, the two left endpoints are in the same\n // vertical plane, ie alx === blx\n\n // consider the lower left-endpoint to come first\n if (aly < bly) return -1\n if (aly > bly) return 1\n\n // left endpoints are identical\n // check for colinearity by using the left-more right endpoint\n\n // is the A right endpoint more left-more?\n if (arx < brx) {\n const bCmpARight = b.comparePoint(a.rightSE.point)\n if (bCmpARight !== 0) return bCmpARight\n }\n\n // is the B right endpoint more left-more?\n if (arx > brx) {\n const aCmpBRight = a.comparePoint(b.rightSE.point)\n if (aCmpBRight < 0) return 1\n if (aCmpBRight > 0) return -1\n }\n\n if (arx !== brx) {\n // are these two [almost] vertical segments with opposite orientation?\n // if so, the one with the lower right endpoint comes first\n const ay = ary - aly\n const ax = arx - alx\n const by = bry - bly\n const bx = brx - blx\n if (ay > ax && by < bx) return 1\n if (ay < ax && by > bx) return -1\n }\n\n // we have colinear segments with matching orientation\n // consider the one with more left-more right endpoint to be first\n if (arx > brx) return 1\n if (arx < brx) return -1\n\n // if we get here, two two right endpoints are in the same\n // vertical plane, ie arx === brx\n\n // consider the lower right-endpoint to come first\n if (ary < bry) return -1\n if (ary > bry) return 1\n\n // right endpoints identical as well, so the segments are idential\n // fall back on creation order as consistent tie-breaker\n if (a.id < b.id) return -1\n if (a.id > b.id) return 1\n\n // identical segment, ie a === b\n return 0\n }\n\n /* Warning: a reference to ringWindings input will be stored,\n * and possibly will be later modified */\n constructor(leftSE, rightSE, rings, windings) {\n this.id = ++segmentId\n this.leftSE = leftSE\n leftSE.segment = this\n leftSE.otherSE = rightSE\n this.rightSE = rightSE\n rightSE.segment = this\n rightSE.otherSE = leftSE\n this.rings = rings\n this.windings = windings\n // left unset for performance, set later in algorithm\n // this.ringOut, this.consumedBy, this.prev\n }\n\n static fromRing(pt1, pt2, ring) {\n let leftPt, rightPt, winding\n\n // ordering the two points according to sweep line ordering\n const cmpPts = SweepEvent.comparePoints(pt1, pt2)\n if (cmpPts < 0) {\n leftPt = pt1\n rightPt = pt2\n winding = 1\n } else if (cmpPts > 0) {\n leftPt = pt2\n rightPt = pt1\n winding = -1\n } else\n throw new Error(\n `Tried to create degenerate segment at [${pt1.x}, ${pt1.y}]`,\n )\n\n const leftSE = new SweepEvent(leftPt, true)\n const rightSE = new SweepEvent(rightPt, false)\n return new Segment(leftSE, rightSE, [ring], [winding])\n }\n\n /* When a segment is split, the rightSE is replaced with a new sweep event */\n replaceRightSE(newRightSE) {\n this.rightSE = newRightSE\n this.rightSE.segment = this\n this.rightSE.otherSE = this.leftSE\n this.leftSE.otherSE = this.rightSE\n }\n\n bbox() {\n const y1 = this.leftSE.point.y\n const y2 = this.rightSE.point.y\n return {\n ll: { x: this.leftSE.point.x, y: y1 < y2 ? y1 : y2 },\n ur: { x: this.rightSE.point.x, y: y1 > y2 ? y1 : y2 },\n }\n }\n\n /* A vector from the left point to the right */\n vector() {\n return {\n x: this.rightSE.point.x - this.leftSE.point.x,\n y: this.rightSE.point.y - this.leftSE.point.y,\n }\n }\n\n isAnEndpoint(pt) {\n return (\n (pt.x === this.leftSE.point.x && pt.y === this.leftSE.point.y) ||\n (pt.x === this.rightSE.point.x && pt.y === this.rightSE.point.y)\n )\n }\n\n /* Compare this segment with a point.\n *\n * A point P is considered to be colinear to a segment if there\n * exists a distance D such that if we travel along the segment\n * from one * endpoint towards the other a distance D, we find\n * ourselves at point P.\n *\n * Return value indicates:\n *\n * 1: point lies above the segment (to the left of vertical)\n * 0: point is colinear to segment\n * -1: point lies below the segment (to the right of vertical)\n */\n comparePoint(point) {\n if (this.isAnEndpoint(point)) return 0\n\n const lPt = this.leftSE.point\n const rPt = this.rightSE.point\n const v = this.vector()\n\n // Exactly vertical segments.\n if (lPt.x === rPt.x) {\n if (point.x === lPt.x) return 0\n return point.x < lPt.x ? 1 : -1\n }\n\n // Nearly vertical segments with an intersection.\n // Check to see where a point on the line with matching Y coordinate is.\n const yDist = (point.y - lPt.y) / v.y\n const xFromYDist = lPt.x + yDist * v.x\n if (point.x === xFromYDist) return 0\n\n // General case.\n // Check to see where a point on the line with matching X coordinate is.\n const xDist = (point.x - lPt.x) / v.x\n const yFromXDist = lPt.y + xDist * v.y\n if (point.y === yFromXDist) return 0\n return point.y < yFromXDist ? -1 : 1\n }\n\n /**\n * Given another segment, returns the first non-trivial intersection\n * between the two segments (in terms of sweep line ordering), if it exists.\n *\n * A 'non-trivial' intersection is one that will cause one or both of the\n * segments to be split(). As such, 'trivial' vs. 'non-trivial' intersection:\n *\n * * endpoint of segA with endpoint of segB --> trivial\n * * endpoint of segA with point along segB --> non-trivial\n * * endpoint of segB with point along segA --> non-trivial\n * * point along segA with point along segB --> non-trivial\n *\n * If no non-trivial intersection exists, return null\n * Else, return null.\n */\n getIntersection(other) {\n // If bboxes don't overlap, there can't be any intersections\n const tBbox = this.bbox()\n const oBbox = other.bbox()\n const bboxOverlap = getBboxOverlap(tBbox, oBbox)\n if (bboxOverlap === null) return null\n\n // We first check to see if the endpoints can be considered intersections.\n // This will 'snap' intersections to endpoints if possible, and will\n // handle cases of colinearity.\n\n const tlp = this.leftSE.point\n const trp = this.rightSE.point\n const olp = other.leftSE.point\n const orp = other.rightSE.point\n\n // does each endpoint touch the other segment?\n // note that we restrict the 'touching' definition to only allow segments\n // to touch endpoints that lie forward from where we are in the sweep line pass\n const touchesOtherLSE = isInBbox(tBbox, olp) && this.comparePoint(olp) === 0\n const touchesThisLSE = isInBbox(oBbox, tlp) && other.comparePoint(tlp) === 0\n const touchesOtherRSE = isInBbox(tBbox, orp) && this.comparePoint(orp) === 0\n const touchesThisRSE = isInBbox(oBbox, trp) && other.comparePoint(trp) === 0\n\n // do left endpoints match?\n if (touchesThisLSE && touchesOtherLSE) {\n // these two cases are for colinear segments with matching left\n // endpoints, and one segment being longer than the other\n if (touchesThisRSE && !touchesOtherRSE) return trp\n if (!touchesThisRSE && touchesOtherRSE) return orp\n // either the two segments match exactly (two trival intersections)\n // or just on their left endpoint (one trivial intersection\n return null\n }\n\n // does this left endpoint matches (other doesn't)\n if (touchesThisLSE) {\n // check for segments that just intersect on opposing endpoints\n if (touchesOtherRSE) {\n if (tlp.x === orp.x && tlp.y === orp.y) return null\n }\n // t-intersection on left endpoint\n return tlp\n }\n\n // does other left endpoint matches (this doesn't)\n if (touchesOtherLSE) {\n // check for segments that just intersect on opposing endpoints\n if (touchesThisRSE) {\n if (trp.x === olp.x && trp.y === olp.y) return null\n }\n // t-intersection on left endpoint\n return olp\n }\n\n // trivial intersection on right endpoints\n if (touchesThisRSE && touchesOtherRSE) return null\n\n // t-intersections on just one right endpoint\n if (touchesThisRSE) return trp\n if (touchesOtherRSE) return orp\n\n // None of our endpoints intersect. Look for a general intersection between\n // infinite lines laid over the segments\n const pt = intersection(tlp, this.vector(), olp, other.vector())\n\n // are the segments parrallel? Note that if they were colinear with overlap,\n // they would have an endpoint intersection and that case was already handled above\n if (pt === null) return null\n\n // is the intersection found between the lines not on the segments?\n if (!isInBbox(bboxOverlap, pt)) return null\n\n // round the the computed point if needed\n return rounder.round(pt.x, pt.y)\n }\n\n /**\n * Split the given segment into multiple segments on the given points.\n * * Each existing segment will retain its leftSE and a new rightSE will be\n * generated for it.\n * * A new segment will be generated which will adopt the original segment's\n * rightSE, and a new leftSE will be generated for it.\n * * If there are more than two points given to split on, new segments\n * in the middle will be generated with new leftSE and rightSE's.\n * * An array of the newly generated SweepEvents will be returned.\n *\n * Warning: input array of points is modified\n */\n split(point) {\n const newEvents = []\n const alreadyLinked = point.events !== undefined\n\n const newLeftSE = new SweepEvent(point, true)\n const newRightSE = new SweepEvent(point, false)\n const oldRightSE = this.rightSE\n this.replaceRightSE(newRightSE)\n newEvents.push(newRightSE)\n newEvents.push(newLeftSE)\n const newSeg = new Segment(\n newLeftSE,\n oldRightSE,\n this.rings.slice(),\n this.windings.slice(),\n )\n\n // when splitting a nearly vertical downward-facing segment,\n // sometimes one of the resulting new segments is vertical, in which\n // case its left and right events may need to be swapped\n if (\n SweepEvent.comparePoints(newSeg.leftSE.point, newSeg.rightSE.point) > 0\n ) {\n newSeg.swapEvents()\n }\n if (SweepEvent.comparePoints(this.leftSE.point, this.rightSE.point) > 0) {\n this.swapEvents()\n }\n\n // in the point we just used to create new sweep events with was already\n // linked to other events, we need to check if either of the affected\n // segments should be consumed\n if (alreadyLinked) {\n newLeftSE.checkForConsuming()\n newRightSE.checkForConsuming()\n }\n\n return newEvents\n }\n\n /* Swap which event is left and right */\n swapEvents() {\n const tmpEvt = this.rightSE\n this.rightSE = this.leftSE\n this.leftSE = tmpEvt\n this.leftSE.isLeft = true\n this.rightSE.isLeft = false\n for (let i = 0, iMax = this.windings.length; i < iMax; i++) {\n this.windings[i] *= -1\n }\n }\n\n /* Consume another segment. We take their rings under our wing\n * and mark them as consumed. Use for perfectly overlapping segments */\n consume(other) {\n let consumer = this\n let consumee = other\n while (consumer.consumedBy) consumer = consumer.consumedBy\n while (consumee.consumedBy) consumee = consumee.consumedBy\n\n const cmp = Segment.compare(consumer, consumee)\n if (cmp === 0) return // already consumed\n // the winner of the consumption is the earlier segment\n // according to sweep line ordering\n if (cmp > 0) {\n const tmp = consumer\n consumer = consumee\n consumee = tmp\n }\n\n // make sure a segment doesn't consume it's prev\n if (consumer.prev === consumee) {\n const tmp = consumer\n consumer = consumee\n consumee = tmp\n }\n\n for (let i = 0, iMax = consumee.rings.length; i < iMax; i++) {\n const ring = consumee.rings[i]\n const winding = consumee.windings[i]\n const index = consumer.rings.indexOf(ring)\n if (index === -1) {\n consumer.rings.push(ring)\n consumer.windings.push(winding)\n } else consumer.windings[index] += winding\n }\n consumee.rings = null\n consumee.windings = null\n consumee.consumedBy = consumer\n\n // mark sweep events consumed as to maintain ordering in sweep event queue\n consumee.leftSE.consumedBy = consumer.leftSE\n consumee.rightSE.consumedBy = consumer.rightSE\n }\n\n /* The first segment previous segment chain that is in the result */\n prevInResult() {\n if (this._prevInResult !== undefined) return this._prevInResult\n if (!this.prev) this._prevInResult = null\n else if (this.prev.isInResult()) this._prevInResult = this.prev\n else this._prevInResult = this.prev.prevInResult()\n return this._prevInResult\n }\n\n beforeState() {\n if (this._beforeState !== undefined) return this._beforeState\n if (!this.prev)\n this._beforeState = {\n rings: [],\n windings: [],\n multiPolys: [],\n }\n else {\n const seg = this.prev.consumedBy || this.prev\n this._beforeState = seg.afterState()\n }\n return this._beforeState\n }\n\n afterState() {\n if (this._afterState !== undefined) return this._afterState\n\n const beforeState = this.beforeState()\n this._afterState = {\n rings: beforeState.rings.slice(0),\n windings: beforeState.windings.slice(0),\n multiPolys: [],\n }\n const ringsAfter = this._afterState.rings\n const windingsAfter = this._afterState.windings\n const mpsAfter = this._afterState.multiPolys\n\n // calculate ringsAfter, windingsAfter\n for (let i = 0, iMax = this.rings.length; i < iMax; i++) {\n const ring = this.rings[i]\n const winding = this.windings[i]\n const index = ringsAfter.indexOf(ring)\n if (index === -1) {\n ringsAfter.push(ring)\n windingsAfter.push(winding)\n } else windingsAfter[index] += winding\n }\n\n // calcualte polysAfter\n const polysAfter = []\n const polysExclude = []\n for (let i = 0, iMax = ringsAfter.length; i < iMax; i++) {\n if (windingsAfter[i] === 0) continue // non-zero rule\n const ring = ringsAfter[i]\n const poly = ring.poly\n if (polysExclude.indexOf(poly) !== -1) continue\n if (ring.isExterior) polysAfter.push(poly)\n else {\n if (polysExclude.indexOf(poly) === -1) polysExclude.push(poly)\n const index = polysAfter.indexOf(ring.poly)\n if (index !== -1) polysAfter.splice(index, 1)\n }\n }\n\n // calculate multiPolysAfter\n for (let i = 0, iMax = polysAfter.length; i < iMax; i++) {\n const mp = polysAfter[i].multiPoly\n if (mpsAfter.indexOf(mp) === -1) mpsAfter.push(mp)\n }\n\n return this._afterState\n }\n\n /* Is this segment part of the final result? */\n isInResult() {\n // if we've been consumed, we're not in the result\n if (this.consumedBy) return false\n\n if (this._isInResult !== undefined) return this._isInResult\n\n const mpsBefore = this.beforeState().multiPolys\n const mpsAfter = this.afterState().multiPolys\n\n switch (operation.type) {\n case \"union\": {\n // UNION - included iff:\n // * On one side of us there is 0 poly interiors AND\n // * On the other side there is 1 or more.\n const noBefores = mpsBefore.length === 0\n const noAfters = mpsAfter.length === 0\n this._isInResult = noBefores !== noAfters\n break\n }\n\n case \"intersection\": {\n // INTERSECTION - included iff:\n // * on one side of us all multipolys are rep. with poly interiors AND\n // * on the other side of us, not all multipolys are repsented\n // with poly interiors\n let least\n let most\n if (mpsBefore.length < mpsAfter.length) {\n least = mpsBefore.length\n most = mpsAfter.length\n } else {\n least = mpsAfter.length\n most = mpsBefore.length\n }\n this._isInResult = most === operation.numMultiPolys && least < most\n break\n }\n\n case \"xor\": {\n // XOR - included iff:\n // * the difference between the number of multipolys represented\n // with poly interiors on our two sides is an odd number\n const diff = Math.abs(mpsBefore.length - mpsAfter.length)\n this._isInResult = diff % 2 === 1\n break\n }\n\n case \"difference\": {\n // DIFFERENCE included iff:\n // * on exactly one side, we have just the subject\n const isJustSubject = (mps) => mps.length === 1 && mps[0].isSubject\n this._isInResult = isJustSubject(mpsBefore) !== isJustSubject(mpsAfter)\n break\n }\n\n default:\n throw new Error(`Unrecognized operation type found ${operation.type}`)\n }\n\n return this._isInResult\n }\n}\n","import rounder from \"./rounder\"\nimport Segment from \"./segment\"\n\nexport class RingIn {\n constructor(geomRing, poly, isExterior) {\n if (!Array.isArray(geomRing) || geomRing.length === 0) {\n throw new Error(\"Input geometry is not a valid Polygon or MultiPolygon\")\n }\n\n this.poly = poly\n this.isExterior = isExterior\n this.segments = []\n\n if (\n typeof geomRing[0][0] !== \"number\" ||\n typeof geomRing[0][1] !== \"number\"\n ) {\n throw new Error(\"Input geometry is not a valid Polygon or MultiPolygon\")\n }\n\n const firstPoint = rounder.round(geomRing[0][0], geomRing[0][1])\n this.bbox = {\n ll: { x: firstPoint.x, y: firstPoint.y },\n ur: { x: firstPoint.x, y: firstPoint.y },\n }\n\n let prevPoint = firstPoint\n for (let i = 1, iMax = geomRing.length; i < iMax; i++) {\n if (\n typeof geomRing[i][0] !== \"number\" ||\n typeof geomRing[i][1] !== \"number\"\n ) {\n throw new Error(\"Input geometry is not a valid Polygon or MultiPolygon\")\n }\n let point = rounder.round(geomRing[i][0], geomRing[i][1])\n // skip repeated points\n if (point.x === prevPoint.x && point.y === prevPoint.y) continue\n this.segments.push(Segment.fromRing(prevPoint, point, this))\n if (point.x < this.bbox.ll.x) this.bbox.ll.x = point.x\n if (point.y < this.bbox.ll.y) this.bbox.ll.y = point.y\n if (point.x > this.bbox.ur.x) this.bbox.ur.x = point.x\n if (point.y > this.bbox.ur.y) this.bbox.ur.y = point.y\n prevPoint = point\n }\n // add segment from last to first if last is not the same as first\n if (firstPoint.x !== prevPoint.x || firstPoint.y !== prevPoint.y) {\n this.segments.push(Segment.fromRing(prevPoint, firstPoint, this))\n }\n }\n\n getSweepEvents() {\n const sweepEvents = []\n for (let i = 0, iMax = this.segments.length; i < iMax; i++) {\n const segment = this.segments[i]\n sweepEvents.push(segment.leftSE)\n sweepEvents.push(segment.rightSE)\n }\n return sweepEvents\n }\n}\n\nexport class PolyIn {\n constructor(geomPoly, multiPoly) {\n if (!Array.isArray(geomPoly)) {\n throw new Error(\"Input geometry is not a valid Polygon or MultiPolygon\")\n }\n this.exteriorRing = new RingIn(geomPoly[0], this, true)\n // copy by value\n this.bbox = {\n ll: { x: this.exteriorRing.bbox.ll.x, y: this.exteriorRing.bbox.ll.y },\n ur: { x: this.exteriorRing.bbox.ur.x, y: this.exteriorRing.bbox.ur.y },\n }\n this.interiorRings = []\n for (let i = 1, iMax = geomPoly.length; i < iMax; i++) {\n const ring = new RingIn(geomPoly[i], this, false)\n if (ring.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = ring.bbox.ll.x\n if (ring.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = ring.bbox.ll.y\n if (ring.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = ring.bbox.ur.x\n if (ring.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = ring.bbox.ur.y\n this.interiorRings.push(ring)\n }\n this.multiPoly = multiPoly\n }\n\n getSweepEvents() {\n const sweepEvents = this.exteriorRing.getSweepEvents()\n for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) {\n const ringSweepEvents = this.interiorRings[i].getSweepEvents()\n for (let j = 0, jMax = ringSweepEvents.length; j < jMax; j++) {\n sweepEvents.push(ringSweepEvents[j])\n }\n }\n return sweepEvents\n }\n}\n\nexport class MultiPolyIn {\n constructor(geom, isSubject) {\n if (!Array.isArray(geom)) {\n throw new Error(\"Input geometry is not a valid Polygon or MultiPolygon\")\n }\n\n try {\n // if the input looks like a polygon, convert it to a multipolygon\n if (typeof geom[0][0][0] === \"number\") geom = [geom]\n } catch (ex) {\n // The input is either malformed or has empty arrays.\n // In either case, it will be handled later on.\n }\n\n this.polys = []\n this.bbox = {\n ll: { x: Number.POSITIVE_INFINITY, y: Number.POSITIVE_INFINITY },\n ur: { x: Number.NEGATIVE_INFINITY, y: Number.NEGATIVE_INFINITY },\n }\n for (let i = 0, iMax = geom.length; i < iMax; i++) {\n const poly = new PolyIn(geom[i], this)\n if (poly.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = poly.bbox.ll.x\n if (poly.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = poly.bbox.ll.y\n if (poly.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = poly.bbox.ur.x\n if (poly.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = poly.bbox.ur.y\n this.polys.push(poly)\n }\n this.isSubject = isSubject\n }\n\n getSweepEvents() {\n const sweepEvents = []\n for (let i = 0, iMax = this.polys.length; i < iMax; i++) {\n const polySweepEvents = this.polys[i].getSweepEvents()\n for (let j = 0, jMax = polySweepEvents.length; j < jMax; j++) {\n sweepEvents.push(polySweepEvents[j])\n }\n }\n return sweepEvents\n }\n}\n","import { compareVectorAngles } from \"./vector\"\nimport SweepEvent from \"./sweep-event\"\n\nexport class RingOut {\n /* Given the segments from the sweep line pass, compute & return a series\n * of closed rings from all the segments marked to be part of the result */\n static factory(allSegments) {\n const ringsOut = []\n\n for (let i = 0, iMax = allSegments.length; i < iMax; i++) {\n const segment = allSegments[i]\n if (!segment.isInResult() || segment.ringOut) continue\n\n let prevEvent = null\n let event = segment.leftSE\n let nextEvent = segment.rightSE\n const events = [event]\n\n const startingPoint = event.point\n const intersectionLEs = []\n\n /* Walk the chain of linked events to form a closed ring */\n while (true) {\n prevEvent = event\n event = nextEvent\n events.push(event)\n\n /* Is the ring complete? */\n if (event.point === startingPoint) break\n\n while (true) {\n const availableLEs = event.getAvailableLinkedEvents()\n\n /* Did we hit a dead end? This shouldn't happen.\n * Indicates some earlier part of the algorithm malfunctioned. */\n if (availableLEs.length === 0) {\n const firstPt = events[0].point\n const lastPt = events[events.length - 1].point\n throw new Error(\n `Unable to complete output ring starting at [${firstPt.x},` +\n ` ${firstPt.y}]. Last matching segment found ends at` +\n ` [${lastPt.x}, ${lastPt.y}].`,\n )\n }\n\n /* Only one way to go, so cotinue on the path */\n if (availableLEs.length === 1) {\n nextEvent = availableLEs[0].otherSE\n break\n }\n\n /* We must have an intersection. Check for a completed loop */\n let indexLE = null\n for (let j = 0, jMax = intersectionLEs.length; j < jMax; j++) {\n if (intersectionLEs[j].point === event.point) {\n indexLE = j\n break\n }\n }\n /* Found a completed loop. Cut that off and make a ring */\n if (indexLE !== null) {\n const intersectionLE = intersectionLEs.splice(indexLE)[0]\n const ringEvents = events.splice(intersectionLE.index)\n ringEvents.unshift(ringEvents[0].otherSE)\n ringsOut.push(new RingOut(ringEvents.reverse()))\n continue\n }\n /* register the intersection */\n intersectionLEs.push({\n index: events.length,\n point: event.point,\n })\n /* Choose the left-most option to continue the walk */\n const comparator = event.getLeftmostComparator(prevEvent)\n nextEvent = availableLEs.sort(comparator)[0].otherSE\n break\n }\n }\n\n ringsOut.push(new RingOut(events))\n }\n return ringsOut\n }\n\n constructor(events) {\n this.events = events\n for (let i = 0, iMax = events.length; i < iMax; i++) {\n events[i].segment.ringOut = this\n }\n this.poly = null\n }\n\n getGeom() {\n // Remove superfluous points (ie extra points along a straight line),\n let prevPt = this.events[0].point\n const points = [prevPt]\n for (let i = 1, iMax = this.events.length - 1; i < iMax; i++) {\n const pt = this.events[i].point\n const nextPt = this.events[i + 1].point\n if (compareVectorAngles(pt, prevPt, nextPt) === 0) continue\n points.push(pt)\n prevPt = pt\n }\n\n // ring was all (within rounding error of angle calc) colinear points\n if (points.length === 1) return null\n\n // check if the starting point is necessary\n const pt = points[0]\n const nextPt = points[1]\n if (compareVectorAngles(pt, prevPt, nextPt) === 0) points.shift()\n\n points.push(points[0])\n const step = this.isExteriorRing() ? 1 : -1\n const iStart = this.isExteriorRing() ? 0 : points.length - 1\n const iEnd = this.isExteriorRing() ? points.length : -1\n const orderedPoints = []\n for (let i = iStart; i != iEnd; i += step)\n orderedPoints.push([points[i].x, points[i].y])\n return orderedPoints\n }\n\n isExteriorRing() {\n if (this._isExteriorRing === undefined) {\n const enclosing = this.enclosingRing()\n this._isExteriorRing = enclosing ? !enclosing.isExteriorRing() : true\n }\n return this._isExteriorRing\n }\n\n enclosingRing() {\n if (this._enclosingRing === undefined) {\n this._enclosingRing = this._calcEnclosingRing()\n }\n return this._enclosingRing\n }\n\n /* Returns the ring that encloses this one, if any */\n _calcEnclosingRing() {\n // start with the ealier sweep line event so that the prevSeg\n // chain doesn't lead us inside of a loop of ours\n let leftMostEvt = this.events[0]\n for (let i = 1, iMax = this.events.length; i < iMax; i++) {\n const evt = this.events[i]\n if (SweepEvent.compare(leftMostEvt, evt) > 0) leftMostEvt = evt\n }\n\n let prevSeg = leftMostEvt.segment.prevInResult()\n let prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null\n\n while (true) {\n // no segment found, thus no ring can enclose us\n if (!prevSeg) return null\n\n // no segments below prev segment found, thus the ring of the prev\n // segment must loop back around and enclose us\n if (!prevPrevSeg) return prevSeg.ringOut\n\n // if the two segments are of different rings, the ring of the prev\n // segment must either loop around us or the ring of the prev prev\n // seg, which would make us and the ring of the prev peers\n if (prevPrevSeg.ringOut !== prevSeg.ringOut) {\n if (prevPrevSeg.ringOut.enclosingRing() !== prevSeg.ringOut) {\n return prevSeg.ringOut\n } else return prevSeg.ringOut.enclosingRing()\n }\n\n // two segments are from the same ring, so this was a penisula\n // of that ring. iterate downward, keep searching\n prevSeg = prevPrevSeg.prevInResult()\n prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null\n }\n }\n}\n\nexport class PolyOut {\n constructor(exteriorRing) {\n this.exteriorRing = exteriorRing\n exteriorRing.poly = this\n this.interiorRings = []\n }\n\n addInterior(ring) {\n this.interiorRings.push(ring)\n ring.poly = this\n }\n\n getGeom() {\n const geom = [this.exteriorRing.getGeom()]\n // exterior ring was all (within rounding error of angle calc) colinear points\n if (geom[0] === null) return null\n for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) {\n const ringGeom = this.interiorRings[i].getGeom()\n // interior ring was all (within rounding error of angle calc) colinear points\n if (ringGeom === null) continue\n geom.push(ringGeom)\n }\n return geom\n }\n}\n\nexport class MultiPolyOut {\n constructor(rings) {\n this.rings = rings\n this.polys = this._composePolys(rings)\n }\n\n getGeom() {\n const geom = []\n for (let i = 0, iMax = this.polys.length; i < iMax; i++) {\n const polyGeom = this.polys[i].getGeom()\n // exterior ring was all (within rounding error of angle calc) colinear points\n if (polyGeom === null) continue\n geom.push(polyGeom)\n }\n return geom\n }\n\n _composePolys(rings) {\n const polys = []\n for (let i = 0, iMax = rings.length; i < iMax; i++) {\n const ring = rings[i]\n if (ring.poly) continue\n if (ring.isExteriorRing()) polys.push(new PolyOut(ring))\n else {\n const enclosingRing = ring.enclosingRing()\n if (!enclosingRing.poly) polys.push(new PolyOut(enclosingRing))\n enclosingRing.poly.addInterior(ring)\n }\n }\n return polys\n }\n}\n","import SplayTree from \"splaytree\"\nimport Segment from \"./segment\"\nimport SweepEvent from \"./sweep-event\"\n\n/**\n * NOTE: We must be careful not to change any segments while\n * they are in the SplayTree. AFAIK, there's no way to tell\n * the tree to rebalance itself - thus before splitting\n * a segment that's in the tree, we remove it from the tree,\n * do the split, then re-insert it. (Even though splitting a\n * segment *shouldn't* change its correct position in the\n * sweep line tree, the reality is because of rounding errors,\n * it sometimes does.)\n */\n\nexport default class SweepLine {\n constructor(queue, comparator = Segment.compare) {\n this.queue = queue\n this.tree = new SplayTree(comparator)\n this.segments = []\n }\n\n process(event) {\n const segment = event.segment\n const newEvents = []\n\n // if we've already been consumed by another segment,\n // clean up our body parts and get out\n if (event.consumedBy) {\n if (event.isLeft) this.queue.remove(event.otherSE)\n else this.tree.remove(segment)\n return newEvents\n }\n\n const node = event.isLeft ? this.tree.add(segment) : this.tree.find(segment)\n\n if (!node)\n throw new Error(\n `Unable to find segment #${segment.id} ` +\n `[${segment.leftSE.point.x}, ${segment.leftSE.point.y}] -> ` +\n `[${segment.rightSE.point.x}, ${segment.rightSE.point.y}] ` +\n \"in SweepLine tree.\",\n )\n\n let prevNode = node\n let nextNode = node\n let prevSeg = undefined\n let nextSeg = undefined\n\n // skip consumed segments still in tree\n while (prevSeg === undefined) {\n prevNode = this.tree.prev(prevNode)\n if (prevNode === null) prevSeg = null\n else if (prevNode.key.consumedBy === undefined) prevSeg = prevNode.key\n }\n\n // skip consumed segments still in tree\n while (nextSeg === undefined) {\n nextNode = this.tree.next(nextNode)\n if (nextNode === null) nextSeg = null\n else if (nextNode.key.consumedBy === undefined) nextSeg = nextNode.key\n }\n\n if (event.isLeft) {\n // Check for intersections against the previous segment in the sweep line\n let prevMySplitter = null\n if (prevSeg) {\n const prevInter = prevSeg.getIntersection(segment)\n if (prevInter !== null) {\n if (!segment.isAnEndpoint(prevInter)) prevMySplitter = prevInter\n if (!prevSeg.isAnEndpoint(prevInter)) {\n const newEventsFromSplit = this._splitSafely(prevSeg, prevInter)\n for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {\n newEvents.push(newEventsFromSplit[i])\n }\n }\n }\n }\n\n // Check for intersections against the next segment in the sweep line\n let nextMySplitter = null\n if (nextSeg) {\n const nextInter = nextSeg.getIntersection(segment)\n if (nextInter !== null) {\n if (!segment.isAnEndpoint(nextInter)) nextMySplitter = nextInter\n if (!nextSeg.isAnEndpoint(nextInter)) {\n const newEventsFromSplit = this._splitSafely(nextSeg, nextInter)\n for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {\n newEvents.push(newEventsFromSplit[i])\n }\n }\n }\n }\n\n // For simplicity, even if we find more than one intersection we only\n // spilt on the 'earliest' (sweep-line style) of the intersections.\n // The other intersection will be handled in a future process().\n if (prevMySplitter !== null || nextMySplitter !== null) {\n let mySplitter = null\n if (prevMySplitter === null) mySplitter = nextMySplitter\n else if (nextMySplitter === null) mySplitter = prevMySplitter\n else {\n const cmpSplitters = SweepEvent.comparePoints(\n prevMySplitter,\n nextMySplitter,\n )\n mySplitter = cmpSplitters <= 0 ? prevMySplitter : nextMySplitter\n }\n\n // Rounding errors can cause changes in ordering,\n // so remove afected segments and right sweep events before splitting\n this.queue.remove(segment.rightSE)\n newEvents.push(segment.rightSE)\n\n const newEventsFromSplit = segment.split(mySplitter)\n for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {\n newEvents.push(newEventsFromSplit[i])\n }\n }\n\n if (newEvents.length > 0) {\n // We found some intersections, so re-do the current event to\n // make sure sweep line ordering is totally consistent for later\n // use with the segment 'prev' pointers\n this.tree.remove(segment)\n newEvents.push(event)\n } else {\n // done with left event\n this.segments.push(segment)\n segment.prev = prevSeg\n }\n } else {\n // event.isRight\n\n // since we're about to be removed from the sweep line, check for\n // intersections between our previous and next segments\n if (prevSeg && nextSeg) {\n const inter = prevSeg.getIntersection(nextSeg)\n if (inter !== null) {\n if (!prevSeg.isAnEndpoint(inter)) {\n const newEventsFromSplit = this._splitSafely(prevSeg, inter)\n for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {\n newEvents.push(newEventsFromSplit[i])\n }\n }\n if (!nextSeg.isAnEndpoint(inter)) {\n const newEventsFromSplit = this._splitSafely(nextSeg, inter)\n for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {\n newEvents.push(newEventsFromSplit[i])\n }\n }\n }\n }\n\n this.tree.remove(segment)\n }\n\n return newEvents\n }\n\n /* Safely split a segment that is currently in the datastructures\n * IE - a segment other than the one that is currently being processed. */\n _splitSafely(seg, pt) {\n // Rounding errors can cause changes in ordering,\n // so remove afected segments and right sweep events before splitting\n // removeNode() doesn't work, so have re-find the seg\n // https://github.com/w8r/splay-tree/pull/5\n this.tree.remove(seg)\n const rightSE = seg.rightSE\n this.queue.remove(rightSE)\n const newEvents = seg.split(pt)\n newEvents.push(rightSE)\n // splitting can trigger consumption\n if (seg.consumedBy === undefined) this.tree.add(seg)\n return newEvents\n }\n}\n","import SplayTree from \"splaytree\"\nimport { getBboxOverlap } from \"./bbox\"\nimport * as geomIn from \"./geom-in\"\nimport * as geomOut from \"./geom-out\"\nimport rounder from \"./rounder\"\nimport SweepEvent from \"./sweep-event\"\nimport SweepLine from \"./sweep-line\"\n\n// Limits on iterative processes to prevent infinite loops - usually caused by floating-point math round-off errors.\nconst POLYGON_CLIPPING_MAX_QUEUE_SIZE =\n (typeof process !== \"undefined\" &&\n process.env.POLYGON_CLIPPING_MAX_QUEUE_SIZE) ||\n 1000000\nconst POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS =\n (typeof process !== \"undefined\" &&\n process.env.POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS) ||\n 1000000\n\nexport class Operation {\n run(type, geom, moreGeoms) {\n operation.type = type\n rounder.reset()\n\n /* Convert inputs to MultiPoly objects */\n const multipolys = [new geomIn.MultiPolyIn(geom, true)]\n for (let i = 0, iMax = moreGeoms.length; i < iMax; i++) {\n multipolys.push(new geomIn.MultiPolyIn(moreGeoms[i], false))\n }\n operation.numMultiPolys = multipolys.length\n\n /* BBox optimization for difference operation\n * If the bbox of a multipolygon that's part of the clipping doesn't\n * intersect the bbox of the subject at all, we can just drop that\n * multiploygon. */\n if (operation.type === \"difference\") {\n // in place removal\n const subject = multipolys[0]\n let i = 1\n while (i < multipolys.length) {\n if (getBboxOverlap(multipolys[i].bbox, subject.bbox) !== null) i++\n else multipolys.splice(i, 1)\n }\n }\n\n /* BBox optimization for intersection operation\n * If we can find any pair of multipolygons whose bbox does not overlap,\n * then the result will be empty. */\n if (operation.type === \"intersection\") {\n // TODO: this is O(n^2) in number of polygons. By sorting the bboxes,\n // it could be optimized to O(n * ln(n))\n for (let i = 0, iMax = multipolys.length; i < iMax; i++) {\n const mpA = multipolys[i]\n for (let j = i + 1, jMax = multipolys.length; j < jMax; j++) {\n if (getBboxOverlap(mpA.bbox, multipolys[j].bbox) === null) return []\n }\n }\n }\n\n /* Put segment endpoints in a priority queue */\n const queue = new SplayTree(SweepEvent.compare)\n for (let i = 0, iMax = multipolys.length; i < iMax; i++) {\n const sweepEvents = multipolys[i].getSweepEvents()\n for (let j = 0, jMax = sweepEvents.length; j < jMax; j++) {\n queue.insert(sweepEvents[j])\n\n if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) {\n // prevents an infinite loop, an otherwise common manifestation of bugs\n throw new Error(\n \"Infinite loop when putting segment endpoints in a priority queue \" +\n \"(queue size too big).\",\n )\n }\n }\n }\n\n /* Pass the sweep line over those endpoints */\n const sweepLine = new SweepLine(queue)\n let prevQueueSize = queue.size\n let node = queue.pop()\n while (node) {\n const evt = node.key\n if (queue.size === prevQueueSize) {\n // prevents an infinite loop, an otherwise common manifestation of bugs\n const seg = evt.segment\n throw new Error(\n `Unable to pop() ${evt.isLeft ? \"left\" : \"right\"} SweepEvent ` +\n `[${evt.point.x}, ${evt.point.y}] from segment #${seg.id} ` +\n `[${seg.leftSE.point.x}, ${seg.leftSE.point.y}] -> ` +\n `[${seg.rightSE.point.x}, ${seg.rightSE.point.y}] from queue.`,\n )\n }\n\n if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) {\n // prevents an infinite loop, an otherwise common manifestation of bugs\n throw new Error(\n \"Infinite loop when passing sweep line over endpoints \" +\n \"(queue size too big).\",\n )\n }\n\n if (sweepLine.segments.length > POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS) {\n // prevents an infinite loop, an otherwise common manifestation of bugs\n throw new Error(\n \"Infinite loop when passing sweep line over endpoints \" +\n \"(too many sweep line segments).\",\n )\n }\n\n const newEvents = sweepLine.process(evt)\n for (let i = 0, iMax = newEvents.length; i < iMax; i++) {\n const evt = newEvents[i]\n if (evt.consumedBy === undefined) queue.insert(evt)\n }\n prevQueueSize = queue.size\n node = queue.pop()\n }\n\n // free some memory we don't need anymore\n rounder.reset()\n\n /* Collect and compile segments we're keeping into a multipolygon */\n const ringsOut = geomOut.RingOut.factory(sweepLine.segments)\n const result = new geomOut.MultiPolyOut(ringsOut)\n return result.getGeom()\n }\n}\n\n// singleton available by import\nconst operation = new Operation()\n\nexport default operation\n","import operation from \"./operation\"\n\nconst union = (geom, ...moreGeoms) => operation.run(\"union\", geom, moreGeoms)\n\nconst intersection = (geom, ...moreGeoms) =>\n operation.run(\"intersection\", geom, moreGeoms)\n\nconst xor = (geom, ...moreGeoms) => operation.run(\"xor\", geom, moreGeoms)\n\nconst difference = (subjectGeom, ...clippingGeoms) =>\n operation.run(\"difference\", subjectGeom, clippingGeoms)\n\nexport default {\n union: union,\n intersection: intersection,\n xor: xor,\n difference: difference,\n}\n"],"names":["isInBbox","bbox","point","ll","x","ur","y","getBboxOverlap","b1","b2","lowerX","upperX","epsilon","Number","EPSILON","undefined","Math","pow","EPSILON_SQ","cmp","a","b","ab","CoordRounder","constructor","this","tree","SplayTree","round","coord","node","add","prevNode","prev","key","remove","nextNode","next","rounder","reset","xRounder","yRounder","splitter","resulterrbound","sum","elen","e","flen","f","h","Q","Qnew","hh","bvirt","enow","fnow","eindex","findex","hindex","vec","n","Float64Array","ccwerrboundB","ccwerrboundC","B","C1","C2","D","u","orient2d","ax","ay","bx","by","cx","cy","detleft","detright","det","detsum","abs","acxtail","acytail","bcxtail","bcytail","c","ahi","alo","bhi","blo","_i","_j","_0","s1","s0","t1","t0","u3","acx","bcx","acy","bcy","i","estimate","errbound","C1len","C2len","Dlen","orient2dadapt","crossProduct","dotProduct","compareVectorAngles","basePt","endPt1","endPt2","res","length","v","sqrt","sineOfAngle","pShared","pBase","pAngle","vBase","vAngle","cosineOfAngle","horizontalIntersection","pt","verticalIntersection","SweepEvent","compare","ptCmp","comparePoints","link","isLeft","Segment","segment","aPt","bPt","events","push","other","Error","otherEvents","iMax","evt","checkForConsuming","numEvents","evt1","consumedBy","j","evt2","otherSE","consume","getAvailableLinkedEvents","ringOut","isInResult","getLeftmostComparator","baseEvent","cache","Map","fillCache","linkedEvent","nextEvent","set","sine","cosine","has","asine","acosine","get","bsine","bcosine","segmentId","alx","leftSE","blx","arx","rightSE","brx","aly","bly","ary","bry","aCmpBLeft","comparePoint","bCmpARight","bCmpALeft","aCmpBRight","id","rings","windings","fromRing","pt1","pt2","ring","leftPt","rightPt","winding","cmpPts","replaceRightSE","newRightSE","y1","y2","vector","isAnEndpoint","lPt","rPt","yDist","xFromYDist","xDist","yFromXDist","getIntersection","tBbox","oBbox","bboxOverlap","tlp","trp","olp","orp","touchesOtherLSE","touchesThisLSE","touchesOtherRSE","touchesThisRSE","intersection","v1","v2","kross","ve","d1","d2","split","newEvents","alreadyLinked","newLeftSE","oldRightSE","newSeg","slice","swapEvents","tmpEvt","consumer","consumee","tmp","index","indexOf","prevInResult","_prevInResult","beforeState","_beforeState","seg","afterState","multiPolys","_afterState","ringsAfter","windingsAfter","mpsAfter","polysAfter","polysExclude","poly","isExterior","splice","mp","multiPoly","_isInResult","mpsBefore","operation","type","noBefores","noAfters","least","most","numMultiPolys","diff","isJustSubject","mps","isSubject","RingIn","geomRing","Array","isArray","segments","firstPoint","prevPoint","getSweepEvents","sweepEvents","PolyIn","geomPoly","exteriorRing","interiorRings","ringSweepEvents","jMax","MultiPolyIn","geom","ex","polys","POSITIVE_INFINITY","NEGATIVE_INFINITY","polySweepEvents","RingOut","factory","allSegments","ringsOut","prevEvent","event","startingPoint","intersectionLEs","availableLEs","firstPt","lastPt","indexLE","intersectionLE","ringEvents","unshift","reverse","comparator","sort","getGeom","prevPt","points","nextPt","shift","step","isExteriorRing","iStart","iEnd","orderedPoints","_isExteriorRing","enclosing","enclosingRing","_enclosingRing","_calcEnclosingRing","leftMostEvt","prevSeg","prevPrevSeg","PolyOut","addInterior","ringGeom","MultiPolyOut","_composePolys","polyGeom","SweepLine","queue","arguments","process","find","nextSeg","prevMySplitter","prevInter","newEventsFromSplit","_splitSafely","nextMySplitter","nextInter","mySplitter","inter","POLYGON_CLIPPING_MAX_QUEUE_SIZE","env","POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS","run","moreGeoms","multipolys","geomIn","subject","mpA","insert","size","sweepLine","prevQueueSize","pop","geomOut","union","_len","_key","_len2","_key2","xor","_len3","_key3","difference","subjectGeom","_len4","clippingGeoms","_key4"],"mappings":"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"} \ No newline at end of file diff --git a/package.json b/package.json index eccea5c..2d37c7b 100644 --- a/package.json +++ b/package.json @@ -2,22 +2,23 @@ "name": "polygon-clipping", "version": "0.15.5", "description": "Apply boolean Polygon clipping operations (intersection, union, difference, xor) to your Polygons & MultiPolygons.", + "type": "module", "main": "dist/polygon-clipping.cjs.js", "module": "dist/polygon-clipping.esm.js", "browser": "dist/polygon-clipping.umd.js", "types": "dist/polygon-clipping.d.ts", "scripts": { "build": "rollup -c && cp src/polygon-clipping.d.ts dist/polygon-clipping.d.ts", - "docs:build": "cd docs && rollup -c --environment NODE_ENV:production", - "docs:watch": "cd docs && rollup -c -w", - "docs:start": "cd docs && serve .", - "docs:dev": "npm-run-all --parallel docs:start docs:watch", - "docs:lint": "cd docs && eslint --ext .js --ext .vue *config.js src/", - "lint": "eslint *.config.js src/ test/ bench/", - "test": "jest", + "docs:dev": "vite docs", + "docs:build": "vite build docs", + "docs:eslint": "cd docs && eslint --ext .js --ext .vue *config.js src/", + "lint": "npm-run-all -s lint:eslint docs:eslint lint:prettier", + "lint:eslint": "eslint .eslintrc.cjs *.config.js bench/ src/ test/", + "lint:prettier": "prettier --check ./* ./.eslintrc.cjs ./.github", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", "bench": "node bench/bench.js", - "prepublishOnly": "npm-run-all --serial lint docs:lint test build docs:build bench" + "prepublishOnly": "npm-run-all --serial lint test build docs:build bench" }, "files": [ "dist" @@ -44,29 +45,34 @@ "author": "Mike Fogel ", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.12.10", - "@babel/core": "^7.12.10", - "@babel/preset-env": "^7.12.10", - "@rollup/plugin-commonjs": "^16.0.0", + "@babel/cli": "^7.23.4", + "@babel/core": "^7.23.6", + "@babel/preset-env": "^7.23.6", + "@rollup/plugin-babel": "^5.3.1", + "@rollup/plugin-commonjs": "^21.1.0", "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^10.0.0", + "@rollup/plugin-node-resolve": "^13.3.0", "@turf/difference": "^5.1.5", "@turf/intersect": "^5.1.6", + "@turf/meta": "^6.5.0", "@turf/union": "^5.1.5", - "babel-jest": "^26.6.3", + "@vitejs/plugin-vue": "^3.2.0", + "babel-jest": "^27.5.1", "benchmark": "^2.1.4", - "eslint": "^7.15.0", - "eslint-plugin-vue": "^7.2.0", - "jest": "^26.6.3", + "eslint": "^8.56.0", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-vue": "^8.7.1", + "jest": "^27.5.1", + "leaflet": "^1.9.4", "load-json-file": "^6.2.0", - "martinez-polygon-clipping": "0.7.0", + "martinez-polygon-clipping": "^0.7.3", "npm-run-all": "^4.1.5", - "rollup": "^2.35.1", - "rollup-plugin-babel": "^4.4.0", + "prettier": "^2.8.8", + "robust-predicates": "^3.0.2", + "rollup": "^2.79.1", "rollup-plugin-terser": "^7.0.2", - "rollup-plugin-vue": "^5.1.9", - "serve": "^11.3.2", - "vue-template-compiler": "^2.6.12" + "vite": "^3.2.7", + "vue": "^3.3.12" }, "dependencies": { "splaytree": "^3.1.0"