| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496 |
- (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';
- /**
- * splaytree v3.1.2
- * Fast Splay tree for Node and browser
- *
- * @author Alexander Milevski <info@w8r.name>
- * @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 __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]);
- };
- }
- 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
- };
- }
- }
- 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 <sleator@cs.cmu.edu> 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);
- 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;
- }
- }
- 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;
- }
- this._root = null;
- this._size = 0;
- this._comparator = comparator;
- }
- /**
- * 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) {
- 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;
- }
- }
- 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
- });
- Object.defineProperty(Tree.prototype, "root", {
- get: function () {
- return this._root;
- },
- enumerable: true,
- configurable: true
- });
- 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 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 = p = p.next = Q.pop();
- current = current.right;
- } else done = true;
- }
- }
- p.next = null; // that'll work even if the tree was empty
- return head.next;
- }
- 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;
- }
- 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);
- }
- /**
- * A bounding box has the format:
- *
- * { ll: { x: xmin, y: ymin }, ur: { x: xmax, y: ymax } }
- *
- */
- 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 */
- 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
- 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;
- // 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,
- y: lowerY
- },
- ur: {
- x: upperX,
- y: upperY
- }
- };
- };
- /* 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
- */
- let epsilon$1 = Number.EPSILON;
- // IE Polyfill
- if (epsilon$1 === undefined) epsilon$1 = Math.pow(2, -52);
- const EPSILON_SQ = epsilon$1 * epsilon$1;
- /* 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;
- }
- }
- // check if they're flp equal
- const ab = a - b;
- 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.
- */
- class PtRounder {
- constructor() {
- this.reset();
- }
- 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 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;
- }
- const nextNode = this.tree.next(node);
- if (nextNode !== null && cmp(node.key, nextNode.key) === 0) {
- this.tree.remove(coord);
- return nextNode.key;
- }
- return coord;
- }
- }
- // 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);
- }
- 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);
- }
- /* Cross Product of two vectors with first point at origin */
- const crossProduct = (a, b) => 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;
- /* 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;
- };
- 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);
- };
- /* 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 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 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)
- };
- };
- /* 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;
- 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
- };
- };
- 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);
- // 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
- 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)
- 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();
- }
- /* 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);
- }
- }
- }
- 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);
- }
- }
- 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 = 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;
- }
- // 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;
- 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;
- }
- // 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;
- // 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 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) {
- 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;
- }
- // 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
- // 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;
- 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 */
- 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
- }
- };
- }
- /* 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;
- }
- // 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;
- }
- /**
- * 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;
- }
- // 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
- const pt = intersection$1(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
- */
- 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();
- }
- 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
- 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; // 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;
- }
- // 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;
- // 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 */
- 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;
- }
- // 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);
- }
- }
- // 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? */
- 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");
- }
- 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));
- }
- }
- 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;
- }
- }
- class PolyIn {
- 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);
- // 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;
- }
- 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;
- }
- }
- class MultiPolyIn {
- constructor(geom, isSubject) {
- 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
- }
- };
- 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;
- }
- 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;
- }
- }
- 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) {
- 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;
- }
- /* 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) {
- 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;
- }
- constructor(events) {
- this.events = events;
- for (let i = 0, iMax = events.length; i < iMax; i++) {
- events[i].segment.ringOut = this;
- }
- this.poly = null;
- }
- 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;
- }
- // 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;
- }
- return this._isExteriorRing;
- }
- enclosingRing() {
- if (this._enclosingRing === undefined) {
- this._enclosingRing = this._calcEnclosingRing();
- }
- 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;
- }
- 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();
- }
- // 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;
- }
- }
- }
- class PolyOut {
- constructor(exteriorRing) {
- this.exteriorRing = exteriorRing;
- exteriorRing.poly = this;
- this.interiorRings = [];
- }
- 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 geom;
- }
- }
- 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;
- }
- _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;
- }
- }
- /**
- * 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.)
- */
- 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 = [];
- // 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;
- }
- // 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
- 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) {
- 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;
- }
- // 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 {
- // 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)) {
- 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. */
- _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;
- }
- }
- // 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);
- }
- }
- /* 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 [];
- }
- }
- }
- /* 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).");
- }
- }
- }
- /* 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();
- }
- // free some memory we don't need anymore
- rounder.reset();
- /* 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();
- 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;
- }));
|