| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179 |
- "use strict";
- var __create = Object.create;
- var __defProp = Object.defineProperty;
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
- var __getOwnPropNames = Object.getOwnPropertyNames;
- var __getProtoOf = Object.getPrototypeOf;
- var __hasOwnProp = Object.prototype.hasOwnProperty;
- var __export = (target, all) => {
- for (var name in all)
- __defProp(target, name, { get: all[name], enumerable: true });
- };
- var __copyProps = (to, from, except, desc) => {
- if (from && typeof from === "object" || typeof from === "function") {
- for (let key of __getOwnPropNames(from))
- if (!__hasOwnProp.call(to, key) && key !== except)
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
- }
- return to;
- };
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
- // If the importer is in node compatibility mode or this is not an ESM
- // file that has been converted to a CommonJS file using a Babel-
- // compatible transform (i.e. "__esModule" has not been set), then set
- // "default" to the CommonJS "module.exports" for node compatibility.
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
- mod
- ));
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
- // src/index.ts
- var src_exports = {};
- __export(src_exports, {
- difference: () => difference,
- intersection: () => intersection2,
- setPrecision: () => setPrecision,
- union: () => union,
- xor: () => xor
- });
- module.exports = __toCommonJS(src_exports);
- // src/geom-in.ts
- var import_bignumber2 = __toESM(require("bignumber.js"), 1);
- // src/constant.ts
- var constant_default = (x) => {
- return () => {
- return x;
- };
- };
- // src/compare.ts
- var compare_default = (eps) => {
- const almostEqual = eps ? (a, b) => b.minus(a).abs().isLessThanOrEqualTo(eps) : constant_default(false);
- return (a, b) => {
- if (almostEqual(a, b)) return 0;
- return a.comparedTo(b);
- };
- };
- // src/orient.ts
- function orient_default(eps) {
- const almostCollinear = eps ? (area2, ax, ay, cx, cy) => area2.exponentiatedBy(2).isLessThanOrEqualTo(
- cx.minus(ax).exponentiatedBy(2).plus(cy.minus(ay).exponentiatedBy(2)).times(eps)
- ) : constant_default(false);
- return (a, b, c) => {
- const ax = a.x, ay = a.y, cx = c.x, cy = c.y;
- const area2 = ay.minus(cy).times(b.x.minus(cx)).minus(ax.minus(cx).times(b.y.minus(cy)));
- if (almostCollinear(area2, ax, ay, cx, cy)) return 0;
- return area2.comparedTo(0);
- };
- }
- // src/snap.ts
- var import_bignumber = __toESM(require("bignumber.js"), 1);
- var import_splaytree_ts = require("splaytree-ts");
- // src/identity.ts
- var identity_default = (x) => {
- return x;
- };
- // src/snap.ts
- var snap_default = (eps) => {
- if (eps) {
- const xTree = new import_splaytree_ts.SplayTreeSet(compare_default(eps));
- const yTree = new import_splaytree_ts.SplayTreeSet(compare_default(eps));
- const snapCoord = (coord, tree) => {
- return tree.addAndReturn(coord);
- };
- const snap = (v) => {
- return {
- x: snapCoord(v.x, xTree),
- y: snapCoord(v.y, yTree)
- };
- };
- snap({ x: new import_bignumber.default(0), y: new import_bignumber.default(0) });
- return snap;
- }
- return identity_default;
- };
- // src/precision.ts
- var set = (eps) => {
- return {
- set: (eps2) => {
- precision = set(eps2);
- },
- reset: () => set(eps),
- compare: compare_default(eps),
- snap: snap_default(eps),
- orient: orient_default(eps)
- };
- };
- var precision = set();
- // src/bbox.ts
- var isInBbox = (bbox, point) => {
- return bbox.ll.x.isLessThanOrEqualTo(point.x) && point.x.isLessThanOrEqualTo(bbox.ur.x) && bbox.ll.y.isLessThanOrEqualTo(point.y) && point.y.isLessThanOrEqualTo(bbox.ur.y);
- };
- var getBboxOverlap = (b1, b2) => {
- if (b2.ur.x.isLessThan(b1.ll.x) || b1.ur.x.isLessThan(b2.ll.x) || b2.ur.y.isLessThan(b1.ll.y) || b1.ur.y.isLessThan(b2.ll.y))
- return null;
- const lowerX = b1.ll.x.isLessThan(b2.ll.x) ? b2.ll.x : b1.ll.x;
- const upperX = b1.ur.x.isLessThan(b2.ur.x) ? b1.ur.x : b2.ur.x;
- const lowerY = b1.ll.y.isLessThan(b2.ll.y) ? b2.ll.y : b1.ll.y;
- const upperY = b1.ur.y.isLessThan(b2.ur.y) ? b1.ur.y : b2.ur.y;
- return { ll: { x: lowerX, y: lowerY }, ur: { x: upperX, y: upperY } };
- };
- // src/operation.ts
- var import_splaytree_ts3 = require("splaytree-ts");
- // src/vector.ts
- var crossProduct = (a, b) => a.x.times(b.y).minus(a.y.times(b.x));
- var dotProduct = (a, b) => a.x.times(b.x).plus(a.y.times(b.y));
- var length = (v) => dotProduct(v, v).sqrt();
- var sineOfAngle = (pShared, pBase, pAngle) => {
- const vBase = { x: pBase.x.minus(pShared.x), y: pBase.y.minus(pShared.y) };
- const vAngle = { x: pAngle.x.minus(pShared.x), y: pAngle.y.minus(pShared.y) };
- return crossProduct(vAngle, vBase).div(length(vAngle)).div(length(vBase));
- };
- var cosineOfAngle = (pShared, pBase, pAngle) => {
- const vBase = { x: pBase.x.minus(pShared.x), y: pBase.y.minus(pShared.y) };
- const vAngle = { x: pAngle.x.minus(pShared.x), y: pAngle.y.minus(pShared.y) };
- return dotProduct(vAngle, vBase).div(length(vAngle)).div(length(vBase));
- };
- var horizontalIntersection = (pt, v, y) => {
- if (v.y.isZero()) return null;
- return { x: pt.x.plus(v.x.div(v.y).times(y.minus(pt.y))), y };
- };
- var verticalIntersection = (pt, v, x) => {
- if (v.x.isZero()) return null;
- return { x, y: pt.y.plus(v.y.div(v.x).times(x.minus(pt.x))) };
- };
- var intersection = (pt1, v1, pt2, v2) => {
- if (v1.x.isZero()) return verticalIntersection(pt2, v2, pt1.x);
- if (v2.x.isZero()) return verticalIntersection(pt1, v1, pt2.x);
- if (v1.y.isZero()) return horizontalIntersection(pt2, v2, pt1.y);
- if (v2.y.isZero()) return horizontalIntersection(pt1, v1, pt2.y);
- const kross = crossProduct(v1, v2);
- if (kross.isZero()) return null;
- const ve = { x: pt2.x.minus(pt1.x), y: pt2.y.minus(pt1.y) };
- const d1 = crossProduct(ve, v1).div(kross);
- const d2 = crossProduct(ve, v2).div(kross);
- const x1 = pt1.x.plus(d2.times(v1.x)), x2 = pt2.x.plus(d1.times(v2.x));
- const y1 = pt1.y.plus(d2.times(v1.y)), y2 = pt2.y.plus(d1.times(v2.y));
- const x = x1.plus(x2).div(2);
- const y = y1.plus(y2).div(2);
- return { x, y };
- };
- // src/sweep-event.ts
- var SweepEvent = class _SweepEvent {
- point;
- isLeft;
- segment;
- otherSE;
- consumedBy;
- // for ordering sweep events in the sweep event queue
- static compare(a, b) {
- const ptCmp = _SweepEvent.comparePoints(a.point, b.point);
- if (ptCmp !== 0) return ptCmp;
- if (a.point !== b.point) a.link(b);
- if (a.isLeft !== b.isLeft) return a.isLeft ? 1 : -1;
- return Segment.compare(a.segment, b.segment);
- }
- // for ordering points in sweep line order
- static comparePoints(aPt, bPt) {
- if (aPt.x.isLessThan(bPt.x)) return -1;
- if (aPt.x.isGreaterThan(bPt.x)) return 1;
- if (aPt.y.isLessThan(bPt.y)) return -1;
- if (aPt.y.isGreaterThan(bPt.y)) return 1;
- return 0;
- }
- // Warning: 'point' input will be modified and re-used (for performance)
- constructor(point, isLeft) {
- if (point.events === void 0) point.events = [this];
- else point.events.push(this);
- this.point = point;
- this.isLeft = isLeft;
- }
- 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();
- }
- /* Do a pass over our linked events and check to see if any pair
- * of segments match, and should be consumed. */
- checkForConsuming() {
- const numEvents = this.point.events.length;
- for (let i = 0; i < numEvents; i++) {
- const evt1 = this.point.events[i];
- if (evt1.segment.consumedBy !== void 0) continue;
- for (let j = i + 1; j < numEvents; j++) {
- const evt2 = this.point.events[j];
- if (evt2.consumedBy !== void 0) continue;
- if (evt1.otherSE.point.events !== evt2.otherSE.point.events) continue;
- evt1.segment.consume(evt2.segment);
- }
- }
- }
- getAvailableLinkedEvents() {
- 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.
- * 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 = /* @__PURE__ */ 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);
- if (asine.isGreaterThanOrEqualTo(0) && bsine.isGreaterThanOrEqualTo(0)) {
- if (acosine.isLessThan(bcosine)) return 1;
- if (acosine.isGreaterThan(bcosine)) return -1;
- return 0;
- }
- if (asine.isLessThan(0) && bsine.isLessThan(0)) {
- if (acosine.isLessThan(bcosine)) return -1;
- if (acosine.isGreaterThan(bcosine)) return 1;
- return 0;
- }
- if (bsine.isLessThan(asine)) return -1;
- if (bsine.isGreaterThan(asine)) return 1;
- return 0;
- };
- }
- };
- // src/geom-out.ts
- var RingOut = class _RingOut {
- events;
- poly;
- _isExteriorRing;
- _enclosingRing;
- /* 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 = [];
- while (true) {
- prevEvent = event;
- event = nextEvent;
- events.push(event);
- if (event.point === startingPoint) break;
- while (true) {
- const availableLEs = event.getAvailableLinkedEvents();
- 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}].`
- );
- }
- if (availableLEs.length === 1) {
- nextEvent = availableLEs[0].otherSE;
- break;
- }
- let indexLE = null;
- for (let j = 0, jMax = intersectionLEs.length; j < jMax; j++) {
- if (intersectionLEs[j].point === event.point) {
- indexLE = j;
- break;
- }
- }
- 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;
- }
- intersectionLEs.push({
- index: events.length,
- point: event.point
- });
- const comparator = event.getLeftmostComparator(prevEvent);
- nextEvent = availableLEs.sort(comparator)[0].otherSE;
- break;
- }
- }
- ringsOut.push(new _RingOut(events));
- }
- return ringsOut;
- }
- constructor(events) {
- this.events = events;
- for (let i = 0, iMax = events.length; i < iMax; i++) {
- events[i].segment.ringOut = this;
- }
- this.poly = null;
- }
- getGeom() {
- let prevPt = this.events[0].point;
- const points = [prevPt];
- for (let i = 1, iMax = this.events.length - 1; i < iMax; i++) {
- const pt2 = this.events[i].point;
- const nextPt2 = this.events[i + 1].point;
- if (precision.orient(pt2, prevPt, nextPt2) === 0) continue;
- points.push(pt2);
- prevPt = pt2;
- }
- if (points.length === 1) return null;
- const pt = points[0];
- const nextPt = points[1];
- if (precision.orient(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.toNumber(), points[i].y.toNumber()]);
- return orderedPoints;
- }
- isExteriorRing() {
- if (this._isExteriorRing === void 0) {
- const enclosing = this.enclosingRing();
- this._isExteriorRing = enclosing ? !enclosing.isExteriorRing() : true;
- }
- return this._isExteriorRing;
- }
- enclosingRing() {
- if (this._enclosingRing === void 0) {
- this._enclosingRing = this._calcEnclosingRing();
- }
- return this._enclosingRing;
- }
- /* Returns the ring that encloses this one, if any */
- _calcEnclosingRing() {
- 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;
- }
- let prevSeg = leftMostEvt.segment.prevInResult();
- let prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null;
- while (true) {
- if (!prevSeg) return null;
- if (!prevPrevSeg) return prevSeg.ringOut;
- if (prevPrevSeg.ringOut !== prevSeg.ringOut) {
- if (prevPrevSeg.ringOut?.enclosingRing() !== prevSeg.ringOut) {
- return prevSeg.ringOut;
- } else return prevSeg.ringOut?.enclosingRing();
- }
- prevSeg = prevPrevSeg.prevInResult();
- prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null;
- }
- }
- };
- var PolyOut = class {
- exteriorRing;
- interiorRings;
- constructor(exteriorRing) {
- this.exteriorRing = exteriorRing;
- exteriorRing.poly = this;
- this.interiorRings = [];
- }
- addInterior(ring) {
- this.interiorRings.push(ring);
- ring.poly = this;
- }
- getGeom() {
- const geom0 = this.exteriorRing.getGeom();
- if (geom0 === null) return null;
- const geom = [geom0];
- for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) {
- const ringGeom = this.interiorRings[i].getGeom();
- if (ringGeom === null) continue;
- geom.push(ringGeom);
- }
- return geom;
- }
- };
- var MultiPolyOut = class {
- rings;
- polys;
- 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();
- if (polyGeom === null) continue;
- geom.push(polyGeom);
- }
- 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;
- }
- };
- // src/sweep-line.ts
- var import_splaytree_ts2 = require("splaytree-ts");
- var SweepLine = class {
- queue;
- tree;
- segments;
- constructor(queue, comparator = Segment.compare) {
- this.queue = queue;
- this.tree = new import_splaytree_ts2.SplayTreeSet(comparator);
- this.segments = [];
- }
- process(event) {
- const segment = event.segment;
- const newEvents = [];
- if (event.consumedBy) {
- if (event.isLeft) this.queue.delete(event.otherSE);
- else this.tree.delete(segment);
- return newEvents;
- }
- if (event.isLeft) this.tree.add(segment);
- let prevSeg = segment;
- let nextSeg = segment;
- do {
- prevSeg = this.tree.lastBefore(prevSeg);
- } while (prevSeg != null && prevSeg.consumedBy != void 0);
- do {
- nextSeg = this.tree.firstAfter(nextSeg);
- } while (nextSeg != null && nextSeg.consumedBy != void 0);
- if (event.isLeft) {
- 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]);
- }
- }
- }
- }
- 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]);
- }
- }
- }
- }
- 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;
- }
- this.queue.delete(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) {
- this.tree.delete(segment);
- newEvents.push(event);
- } else {
- this.segments.push(segment);
- segment.prev = prevSeg;
- }
- } else {
- 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)) {
- const newEventsFromSplit = this._splitSafely(nextSeg, inter);
- for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {
- newEvents.push(newEventsFromSplit[i]);
- }
- }
- }
- }
- this.tree.delete(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. */
- _splitSafely(seg, pt) {
- this.tree.delete(seg);
- const rightSE = seg.rightSE;
- this.queue.delete(rightSE);
- const newEvents = seg.split(pt);
- newEvents.push(rightSE);
- if (seg.consumedBy === void 0) this.tree.add(seg);
- return newEvents;
- }
- };
- // src/operation.ts
- var Operation = class {
- type;
- numMultiPolys;
- run(type, geom, moreGeoms) {
- operation.type = type;
- 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;
- if (operation.type === "difference") {
- 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);
- }
- }
- if (operation.type === "intersection") {
- 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 [];
- }
- }
- }
- const queue = new import_splaytree_ts3.SplayTreeSet(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.add(sweepEvents[j]);
- }
- }
- const sweepLine = new SweepLine(queue);
- let evt = null;
- if (queue.size != 0) {
- evt = queue.first();
- queue.delete(evt);
- }
- while (evt) {
- const newEvents = sweepLine.process(evt);
- for (let i = 0, iMax = newEvents.length; i < iMax; i++) {
- const evt2 = newEvents[i];
- if (evt2.consumedBy === void 0) queue.add(evt2);
- }
- if (queue.size != 0) {
- evt = queue.first();
- queue.delete(evt);
- } else {
- evt = null;
- }
- }
- precision.reset();
- const ringsOut = RingOut.factory(sweepLine.segments);
- const result = new MultiPolyOut(ringsOut);
- return result.getGeom();
- }
- };
- var operation = new Operation();
- var operation_default = operation;
- // src/segment.ts
- var segmentId = 0;
- var Segment = class _Segment {
- id;
- leftSE;
- rightSE;
- rings;
- windings;
- ringOut;
- consumedBy;
- prev;
- _prevInResult;
- _beforeState;
- _afterState;
- _isInResult;
- /* 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;
- if (brx.isLessThan(alx)) return 1;
- if (arx.isLessThan(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;
- if (alx.isLessThan(blx)) {
- if (bly.isLessThan(aly) && bly.isLessThan(ary)) return 1;
- if (bly.isGreaterThan(aly) && bly.isGreaterThan(ary)) return -1;
- const aCmpBLeft = a.comparePoint(b.leftSE.point);
- if (aCmpBLeft < 0) return 1;
- if (aCmpBLeft > 0) return -1;
- const bCmpARight = b.comparePoint(a.rightSE.point);
- if (bCmpARight !== 0) return bCmpARight;
- return -1;
- }
- if (alx.isGreaterThan(blx)) {
- if (aly.isLessThan(bly) && aly.isLessThan(bry)) return -1;
- if (aly.isGreaterThan(bly) && aly.isGreaterThan(bry)) return 1;
- const bCmpALeft = b.comparePoint(a.leftSE.point);
- if (bCmpALeft !== 0) return bCmpALeft;
- const aCmpBRight = a.comparePoint(b.rightSE.point);
- if (aCmpBRight < 0) return 1;
- if (aCmpBRight > 0) return -1;
- return 1;
- }
- if (aly.isLessThan(bly)) return -1;
- if (aly.isGreaterThan(bly)) return 1;
- if (arx.isLessThan(brx)) {
- const bCmpARight = b.comparePoint(a.rightSE.point);
- if (bCmpARight !== 0) return bCmpARight;
- }
- if (arx.isGreaterThan(brx)) {
- const aCmpBRight = a.comparePoint(b.rightSE.point);
- if (aCmpBRight < 0) return 1;
- if (aCmpBRight > 0) return -1;
- }
- if (!arx.eq(brx)) {
- const ay = ary.minus(aly);
- const ax = arx.minus(alx);
- const by = bry.minus(bly);
- const bx = brx.minus(blx);
- if (ay.isGreaterThan(ax) && by.isLessThan(bx)) return 1;
- if (ay.isLessThan(ax) && by.isGreaterThan(bx)) return -1;
- }
- if (arx.isGreaterThan(brx)) return 1;
- if (arx.isLessThan(brx)) return -1;
- if (ary.isLessThan(bry)) return -1;
- if (ary.isGreaterThan(bry)) return 1;
- if (a.id < b.id) return -1;
- if (a.id > b.id) return 1;
- 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;
- leftSE.otherSE = rightSE;
- this.rightSE = rightSE;
- rightSE.segment = this;
- rightSE.otherSE = leftSE;
- this.rings = rings;
- this.windings = windings;
- }
- static fromRing(pt1, pt2, ring) {
- let leftPt, rightPt, winding;
- 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 */
- 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.isLessThan(y2) ? y1 : y2 },
- ur: { x: this.rightSE.point.x, y: y1.isGreaterThan(y2) ? y1 : y2 }
- };
- }
- /* A vector from the left point to the right */
- vector() {
- return {
- x: this.rightSE.point.x.minus(this.leftSE.point.x),
- y: this.rightSE.point.y.minus(this.leftSE.point.y)
- };
- }
- isAnEndpoint(pt) {
- return pt.x.eq(this.leftSE.point.x) && pt.y.eq(this.leftSE.point.y) || pt.x.eq(this.rightSE.point.x) && pt.y.eq(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) {
- return precision.orient(this.leftSE.point, point, this.rightSE.point);
- }
- /**
- * 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) {
- const tBbox = this.bbox();
- const oBbox = other.bbox();
- const bboxOverlap = getBboxOverlap(tBbox, oBbox);
- if (bboxOverlap === null) return null;
- const tlp = this.leftSE.point;
- const trp = this.rightSE.point;
- const olp = other.leftSE.point;
- const orp = other.rightSE.point;
- 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;
- if (touchesThisLSE && touchesOtherLSE) {
- if (touchesThisRSE && !touchesOtherRSE) return trp;
- if (!touchesThisRSE && touchesOtherRSE) return orp;
- return null;
- }
- if (touchesThisLSE) {
- if (touchesOtherRSE) {
- if (tlp.x.eq(orp.x) && tlp.y.eq(orp.y)) return null;
- }
- return tlp;
- }
- if (touchesOtherLSE) {
- if (touchesThisRSE) {
- if (trp.x.eq(olp.x) && trp.y.eq(olp.y)) return null;
- }
- return olp;
- }
- if (touchesThisRSE && touchesOtherRSE) return null;
- if (touchesThisRSE) return trp;
- if (touchesOtherRSE) return orp;
- const pt = intersection(tlp, this.vector(), olp, other.vector());
- if (pt === null) return null;
- if (!isInBbox(bboxOverlap, pt)) return null;
- return precision.snap(pt);
- }
- /**
- * 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 !== void 0;
- 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()
- );
- if (SweepEvent.comparePoints(newSeg.leftSE.point, newSeg.rightSE.point) > 0) {
- newSeg.swapEvents();
- }
- if (SweepEvent.comparePoints(this.leftSE.point, this.rightSE.point) > 0) {
- this.swapEvents();
- }
- if (alreadyLinked) {
- newLeftSE.checkForConsuming();
- newRightSE.checkForConsuming();
- }
- return newEvents;
- }
- /* 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;
- }
- }
- /* 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;
- if (cmp > 0) {
- const tmp = consumer;
- consumer = consumee;
- consumee = tmp;
- }
- 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;
- consumee.leftSE.consumedBy = consumer.leftSE;
- consumee.rightSE.consumedBy = consumer.rightSE;
- }
- /* The first segment previous segment chain that is in the result */
- prevInResult() {
- if (this._prevInResult !== void 0) 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 !== void 0) 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 !== void 0) 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;
- 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;
- }
- const polysAfter = [];
- const polysExclude = [];
- for (let i = 0, iMax = ringsAfter.length; i < iMax; i++) {
- if (windingsAfter[i] === 0) continue;
- 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);
- }
- }
- 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? */
- isInResult() {
- if (this.consumedBy) return false;
- if (this._isInResult !== void 0) return this._isInResult;
- const mpsBefore = this.beforeState().multiPolys;
- const mpsAfter = this.afterState().multiPolys;
- switch (operation_default.type) {
- case "union": {
- const noBefores = mpsBefore.length === 0;
- const noAfters = mpsAfter.length === 0;
- this._isInResult = noBefores !== noAfters;
- break;
- }
- case "intersection": {
- 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_default.numMultiPolys && least < most;
- break;
- }
- case "xor": {
- const diff = Math.abs(mpsBefore.length - mpsAfter.length);
- this._isInResult = diff % 2 === 1;
- break;
- }
- case "difference": {
- const isJustSubject = (mps) => mps.length === 1 && mps[0].isSubject;
- this._isInResult = isJustSubject(mpsBefore) !== isJustSubject(mpsAfter);
- break;
- }
- }
- return this._isInResult;
- }
- };
- // src/geom-in.ts
- var RingIn = class {
- poly;
- isExterior;
- segments;
- bbox;
- constructor(geomRing, poly, isExterior) {
- 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");
- }
- const firstPoint = precision.snap({ x: new import_bignumber2.default(geomRing[0][0]), y: new import_bignumber2.default(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");
- }
- const point = precision.snap({ x: new import_bignumber2.default(geomRing[i][0]), y: new import_bignumber2.default(geomRing[i][1]) });
- if (point.x.eq(prevPoint.x) && point.y.eq(prevPoint.y)) continue;
- this.segments.push(Segment.fromRing(prevPoint, point, this));
- if (point.x.isLessThan(this.bbox.ll.x)) this.bbox.ll.x = point.x;
- if (point.y.isLessThan(this.bbox.ll.y)) this.bbox.ll.y = point.y;
- if (point.x.isGreaterThan(this.bbox.ur.x)) this.bbox.ur.x = point.x;
- if (point.y.isGreaterThan(this.bbox.ur.y)) this.bbox.ur.y = point.y;
- prevPoint = point;
- }
- if (!firstPoint.x.eq(prevPoint.x) || !firstPoint.y.eq(prevPoint.y)) {
- this.segments.push(Segment.fromRing(prevPoint, firstPoint, this));
- }
- }
- 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;
- }
- };
- var PolyIn = class {
- multiPoly;
- exteriorRing;
- interiorRings;
- bbox;
- constructor(geomPoly, multiPoly) {
- if (!Array.isArray(geomPoly)) {
- throw new Error("Input geometry is not a valid Polygon or MultiPolygon");
- }
- this.exteriorRing = new RingIn(geomPoly[0], this, true);
- 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.isLessThan(this.bbox.ll.x)) this.bbox.ll.x = ring.bbox.ll.x;
- if (ring.bbox.ll.y.isLessThan(this.bbox.ll.y)) this.bbox.ll.y = ring.bbox.ll.y;
- if (ring.bbox.ur.x.isGreaterThan(this.bbox.ur.x)) this.bbox.ur.x = ring.bbox.ur.x;
- if (ring.bbox.ur.y.isGreaterThan(this.bbox.ur.y)) this.bbox.ur.y = ring.bbox.ur.y;
- this.interiorRings.push(ring);
- }
- this.multiPoly = multiPoly;
- }
- 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;
- }
- };
- var MultiPolyIn = class {
- isSubject;
- polys;
- bbox;
- constructor(geom, isSubject) {
- if (!Array.isArray(geom)) {
- throw new Error("Input geometry is not a valid Polygon or MultiPolygon");
- }
- try {
- if (typeof geom[0][0][0] === "number") geom = [geom];
- } catch (ex) {
- }
- this.polys = [];
- this.bbox = {
- ll: { x: new import_bignumber2.default(Number.POSITIVE_INFINITY), y: new import_bignumber2.default(Number.POSITIVE_INFINITY) },
- ur: { x: new import_bignumber2.default(Number.NEGATIVE_INFINITY), y: new import_bignumber2.default(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.isLessThan(this.bbox.ll.x)) this.bbox.ll.x = poly.bbox.ll.x;
- if (poly.bbox.ll.y.isLessThan(this.bbox.ll.y)) this.bbox.ll.y = poly.bbox.ll.y;
- if (poly.bbox.ur.x.isGreaterThan(this.bbox.ur.x)) this.bbox.ur.x = poly.bbox.ur.x;
- if (poly.bbox.ur.y.isGreaterThan(this.bbox.ur.y)) this.bbox.ur.y = poly.bbox.ur.y;
- this.polys.push(poly);
- }
- this.isSubject = isSubject;
- }
- 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;
- }
- };
- // src/index.ts
- var union = (geom, ...moreGeoms) => operation_default.run("union", geom, moreGeoms);
- var intersection2 = (geom, ...moreGeoms) => operation_default.run("intersection", geom, moreGeoms);
- var xor = (geom, ...moreGeoms) => operation_default.run("xor", geom, moreGeoms);
- var difference = (geom, ...moreGeoms) => operation_default.run("difference", geom, moreGeoms);
- var setPrecision = precision.set;
- // Annotate the CommonJS export names for ESM import in node:
- 0 && (module.exports = {
- difference,
- intersection,
- setPrecision,
- union,
- xor
- });
- //# sourceMappingURL=index.cjs.map
|