node-red-contrib-tak-registration
Version:
A Node-RED node to register to TAK and to help wrap files as datapackages to send to TAK
1,555 lines (1,479 loc) • 89.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.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 yFromXD