289 lines
9.9 KiB
JavaScript
289 lines
9.9 KiB
JavaScript
// Inlined from
|
|
// https://github.com/rowanwins/sweepline-intersections
|
|
// Needed to go through babel to transform ES6 classes
|
|
var commonjsGlobal = typeof globalThis !== "undefined"
|
|
? globalThis
|
|
: typeof window !== "undefined"
|
|
? window
|
|
: typeof global !== "undefined"
|
|
? global
|
|
: typeof self !== "undefined"
|
|
? self
|
|
: {};
|
|
function createCommonjsModule(fn, module) {
|
|
return (module = { exports: {} }), fn(module, module.exports), module.exports;
|
|
}
|
|
var tinyqueue = createCommonjsModule(function (module) {
|
|
(function (global, factory) {
|
|
module.exports = factory();
|
|
})(commonjsGlobal, function () {
|
|
var TinyQueue = function TinyQueue(data, compare) {
|
|
if (data === void 0)
|
|
data = [];
|
|
if (compare === void 0)
|
|
compare = defaultCompare;
|
|
this.data = data;
|
|
this.length = this.data.length;
|
|
this.compare = compare;
|
|
if (this.length > 0) {
|
|
for (var i = (this.length >> 1) - 1; i >= 0; i--) {
|
|
this._down(i);
|
|
}
|
|
}
|
|
};
|
|
TinyQueue.prototype.push = function push(item) {
|
|
this.data.push(item);
|
|
this.length++;
|
|
this._up(this.length - 1);
|
|
};
|
|
TinyQueue.prototype.pop = function pop() {
|
|
if (this.length === 0) {
|
|
return undefined;
|
|
}
|
|
var top = this.data[0];
|
|
var bottom = this.data.pop();
|
|
this.length--;
|
|
if (this.length > 0) {
|
|
this.data[0] = bottom;
|
|
this._down(0);
|
|
}
|
|
return top;
|
|
};
|
|
TinyQueue.prototype.peek = function peek() {
|
|
return this.data[0];
|
|
};
|
|
TinyQueue.prototype._up = function _up(pos) {
|
|
var ref = this;
|
|
var data = ref.data;
|
|
var compare = ref.compare;
|
|
var item = data[pos];
|
|
while (pos > 0) {
|
|
var parent = (pos - 1) >> 1;
|
|
var current = data[parent];
|
|
if (compare(item, current) >= 0) {
|
|
break;
|
|
}
|
|
data[pos] = current;
|
|
pos = parent;
|
|
}
|
|
data[pos] = item;
|
|
};
|
|
TinyQueue.prototype._down = function _down(pos) {
|
|
var ref = this;
|
|
var data = ref.data;
|
|
var compare = ref.compare;
|
|
var halfLength = this.length >> 1;
|
|
var item = data[pos];
|
|
while (pos < halfLength) {
|
|
var left = (pos << 1) + 1;
|
|
var best = data[left];
|
|
var right = left + 1;
|
|
if (right < this.length && compare(data[right], best) < 0) {
|
|
left = right;
|
|
best = data[right];
|
|
}
|
|
if (compare(best, item) >= 0) {
|
|
break;
|
|
}
|
|
data[pos] = best;
|
|
pos = left;
|
|
}
|
|
data[pos] = item;
|
|
};
|
|
function defaultCompare(a, b) {
|
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
}
|
|
return TinyQueue;
|
|
});
|
|
});
|
|
function checkWhichEventIsLeft(e1, e2) {
|
|
if (e1.p.x > e2.p.x)
|
|
return 1;
|
|
if (e1.p.x < e2.p.x)
|
|
return -1;
|
|
if (e1.p.y !== e2.p.y)
|
|
return e1.p.y > e2.p.y ? 1 : -1;
|
|
return 1;
|
|
}
|
|
function checkWhichSegmentHasRightEndpointFirst(seg1, seg2) {
|
|
if (seg1.rightSweepEvent.p.x > seg2.rightSweepEvent.p.x)
|
|
return 1;
|
|
if (seg1.rightSweepEvent.p.x < seg2.rightSweepEvent.p.x)
|
|
return -1;
|
|
if (seg1.rightSweepEvent.p.y !== seg2.rightSweepEvent.p.y)
|
|
return seg1.rightSweepEvent.p.y < seg2.rightSweepEvent.p.y ? 1 : -1;
|
|
return 1;
|
|
}
|
|
function _classCallCheck(instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
}
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor)
|
|
descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps)
|
|
_defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps)
|
|
_defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
var Event = /*#__PURE__*/ (function () {
|
|
function Event(p, featureId, ringId, eventId) {
|
|
_classCallCheck(this, Event);
|
|
this.p = {
|
|
x: p[0],
|
|
y: p[1],
|
|
};
|
|
this.featureId = featureId;
|
|
this.ringId = ringId;
|
|
this.eventId = eventId;
|
|
this.otherEvent = null;
|
|
this.isLeftEndpoint = null;
|
|
}
|
|
_createClass(Event, [
|
|
{
|
|
key: "isSamePoint",
|
|
value: function isSamePoint(eventToCheck) {
|
|
return this.p.x === eventToCheck.p.x && this.p.y === eventToCheck.p.y;
|
|
},
|
|
},
|
|
]);
|
|
return Event;
|
|
})();
|
|
function fillEventQueue(geojson, eventQueue) {
|
|
if (geojson.type === "FeatureCollection") {
|
|
var features = geojson.features;
|
|
for (var i = 0; i < features.length; i++) {
|
|
processFeature(features[i], eventQueue);
|
|
}
|
|
}
|
|
else {
|
|
processFeature(geojson, eventQueue);
|
|
}
|
|
}
|
|
var featureId = 0;
|
|
var ringId = 0;
|
|
var eventId = 0;
|
|
function processFeature(featureOrGeometry, eventQueue) {
|
|
var geom = featureOrGeometry.type === "Feature"
|
|
? featureOrGeometry.geometry
|
|
: featureOrGeometry;
|
|
var coords = geom.coordinates; // standardise the input
|
|
if (geom.type === "Polygon" || geom.type === "MultiLineString")
|
|
coords = [coords];
|
|
if (geom.type === "LineString")
|
|
coords = [[coords]];
|
|
for (var i = 0; i < coords.length; i++) {
|
|
for (var ii = 0; ii < coords[i].length; ii++) {
|
|
var currentP = coords[i][ii][0];
|
|
var nextP = null;
|
|
ringId = ringId + 1;
|
|
for (var iii = 0; iii < coords[i][ii].length - 1; iii++) {
|
|
nextP = coords[i][ii][iii + 1];
|
|
var e1 = new Event(currentP, featureId, ringId, eventId);
|
|
var e2 = new Event(nextP, featureId, ringId, eventId + 1);
|
|
e1.otherEvent = e2;
|
|
e2.otherEvent = e1;
|
|
if (checkWhichEventIsLeft(e1, e2) > 0) {
|
|
e2.isLeftEndpoint = true;
|
|
e1.isLeftEndpoint = false;
|
|
}
|
|
else {
|
|
e1.isLeftEndpoint = true;
|
|
e2.isLeftEndpoint = false;
|
|
}
|
|
eventQueue.push(e1);
|
|
eventQueue.push(e2);
|
|
currentP = nextP;
|
|
eventId = eventId + 1;
|
|
}
|
|
}
|
|
}
|
|
featureId = featureId + 1;
|
|
}
|
|
var Segment = function Segment(event) {
|
|
_classCallCheck(this, Segment);
|
|
this.leftSweepEvent = event;
|
|
this.rightSweepEvent = event.otherEvent;
|
|
};
|
|
function testSegmentIntersect(seg1, seg2) {
|
|
if (seg1 === null || seg2 === null)
|
|
return false;
|
|
if (seg1.leftSweepEvent.ringId === seg2.leftSweepEvent.ringId &&
|
|
(seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
|
|
seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
|
|
seg1.rightSweepEvent.isSamePoint(seg2.rightSweepEvent) ||
|
|
seg1.leftSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
|
|
seg1.leftSweepEvent.isSamePoint(seg2.rightSweepEvent)))
|
|
return false;
|
|
var x1 = seg1.leftSweepEvent.p.x;
|
|
var y1 = seg1.leftSweepEvent.p.y;
|
|
var x2 = seg1.rightSweepEvent.p.x;
|
|
var y2 = seg1.rightSweepEvent.p.y;
|
|
var x3 = seg2.leftSweepEvent.p.x;
|
|
var y3 = seg2.leftSweepEvent.p.y;
|
|
var x4 = seg2.rightSweepEvent.p.x;
|
|
var y4 = seg2.rightSweepEvent.p.y;
|
|
var denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
|
|
var numeA = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
|
|
var numeB = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
|
|
if (denom === 0) {
|
|
if (numeA === 0 && numeB === 0)
|
|
return false;
|
|
return false;
|
|
}
|
|
var uA = numeA / denom;
|
|
var uB = numeB / denom;
|
|
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
|
|
var x = x1 + uA * (x2 - x1);
|
|
var y = y1 + uA * (y2 - y1);
|
|
return [x, y];
|
|
}
|
|
return false;
|
|
}
|
|
function runCheck(eventQueue, ignoreSelfIntersections) {
|
|
ignoreSelfIntersections = ignoreSelfIntersections
|
|
? ignoreSelfIntersections
|
|
: false;
|
|
var intersectionPoints = [];
|
|
var outQueue = new tinyqueue([], checkWhichSegmentHasRightEndpointFirst);
|
|
while (eventQueue.length) {
|
|
var event = eventQueue.pop();
|
|
if (event.isLeftEndpoint) {
|
|
// debugEventAndSegments(event.p, outQueue.data)
|
|
var segment = new Segment(event);
|
|
for (var i = 0; i < outQueue.data.length; i++) {
|
|
var otherSeg = outQueue.data[i];
|
|
if (ignoreSelfIntersections) {
|
|
if (otherSeg.leftSweepEvent.featureId === event.featureId)
|
|
continue;
|
|
}
|
|
var intersection = testSegmentIntersect(segment, otherSeg);
|
|
if (intersection !== false)
|
|
intersectionPoints.push(intersection);
|
|
}
|
|
outQueue.push(segment);
|
|
}
|
|
else if (event.isLeftEndpoint === false) {
|
|
outQueue.pop(); // const seg = outQueue.pop()
|
|
// debugRemovingSegment(event.p, seg)
|
|
}
|
|
}
|
|
return intersectionPoints;
|
|
}
|
|
function sweeplineIntersections(geojson, ignoreSelfIntersections) {
|
|
var eventQueue = new tinyqueue([], checkWhichEventIsLeft);
|
|
fillEventQueue(geojson, eventQueue);
|
|
return runCheck(eventQueue, ignoreSelfIntersections);
|
|
}
|
|
export default sweeplineIntersections;
|