sweepline-intersections.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Inlined from
  2. // https://github.com/rowanwins/sweepline-intersections
  3. // Needed to go through babel to transform ES6 classes
  4. var commonjsGlobal = typeof globalThis !== "undefined"
  5. ? globalThis
  6. : typeof window !== "undefined"
  7. ? window
  8. : typeof global !== "undefined"
  9. ? global
  10. : typeof self !== "undefined"
  11. ? self
  12. : {};
  13. function createCommonjsModule(fn, module) {
  14. return (module = { exports: {} }), fn(module, module.exports), module.exports;
  15. }
  16. var tinyqueue = createCommonjsModule(function (module) {
  17. (function (global, factory) {
  18. module.exports = factory();
  19. })(commonjsGlobal, function () {
  20. var TinyQueue = function TinyQueue(data, compare) {
  21. if (data === void 0)
  22. data = [];
  23. if (compare === void 0)
  24. compare = defaultCompare;
  25. this.data = data;
  26. this.length = this.data.length;
  27. this.compare = compare;
  28. if (this.length > 0) {
  29. for (var i = (this.length >> 1) - 1; i >= 0; i--) {
  30. this._down(i);
  31. }
  32. }
  33. };
  34. TinyQueue.prototype.push = function push(item) {
  35. this.data.push(item);
  36. this.length++;
  37. this._up(this.length - 1);
  38. };
  39. TinyQueue.prototype.pop = function pop() {
  40. if (this.length === 0) {
  41. return undefined;
  42. }
  43. var top = this.data[0];
  44. var bottom = this.data.pop();
  45. this.length--;
  46. if (this.length > 0) {
  47. this.data[0] = bottom;
  48. this._down(0);
  49. }
  50. return top;
  51. };
  52. TinyQueue.prototype.peek = function peek() {
  53. return this.data[0];
  54. };
  55. TinyQueue.prototype._up = function _up(pos) {
  56. var ref = this;
  57. var data = ref.data;
  58. var compare = ref.compare;
  59. var item = data[pos];
  60. while (pos > 0) {
  61. var parent = (pos - 1) >> 1;
  62. var current = data[parent];
  63. if (compare(item, current) >= 0) {
  64. break;
  65. }
  66. data[pos] = current;
  67. pos = parent;
  68. }
  69. data[pos] = item;
  70. };
  71. TinyQueue.prototype._down = function _down(pos) {
  72. var ref = this;
  73. var data = ref.data;
  74. var compare = ref.compare;
  75. var halfLength = this.length >> 1;
  76. var item = data[pos];
  77. while (pos < halfLength) {
  78. var left = (pos << 1) + 1;
  79. var best = data[left];
  80. var right = left + 1;
  81. if (right < this.length && compare(data[right], best) < 0) {
  82. left = right;
  83. best = data[right];
  84. }
  85. if (compare(best, item) >= 0) {
  86. break;
  87. }
  88. data[pos] = best;
  89. pos = left;
  90. }
  91. data[pos] = item;
  92. };
  93. function defaultCompare(a, b) {
  94. return a < b ? -1 : a > b ? 1 : 0;
  95. }
  96. return TinyQueue;
  97. });
  98. });
  99. function checkWhichEventIsLeft(e1, e2) {
  100. if (e1.p.x > e2.p.x)
  101. return 1;
  102. if (e1.p.x < e2.p.x)
  103. return -1;
  104. if (e1.p.y !== e2.p.y)
  105. return e1.p.y > e2.p.y ? 1 : -1;
  106. return 1;
  107. }
  108. function checkWhichSegmentHasRightEndpointFirst(seg1, seg2) {
  109. if (seg1.rightSweepEvent.p.x > seg2.rightSweepEvent.p.x)
  110. return 1;
  111. if (seg1.rightSweepEvent.p.x < seg2.rightSweepEvent.p.x)
  112. return -1;
  113. if (seg1.rightSweepEvent.p.y !== seg2.rightSweepEvent.p.y)
  114. return seg1.rightSweepEvent.p.y < seg2.rightSweepEvent.p.y ? 1 : -1;
  115. return 1;
  116. }
  117. function _classCallCheck(instance, Constructor) {
  118. if (!(instance instanceof Constructor)) {
  119. throw new TypeError("Cannot call a class as a function");
  120. }
  121. }
  122. function _defineProperties(target, props) {
  123. for (var i = 0; i < props.length; i++) {
  124. var descriptor = props[i];
  125. descriptor.enumerable = descriptor.enumerable || false;
  126. descriptor.configurable = true;
  127. if ("value" in descriptor)
  128. descriptor.writable = true;
  129. Object.defineProperty(target, descriptor.key, descriptor);
  130. }
  131. }
  132. function _createClass(Constructor, protoProps, staticProps) {
  133. if (protoProps)
  134. _defineProperties(Constructor.prototype, protoProps);
  135. if (staticProps)
  136. _defineProperties(Constructor, staticProps);
  137. return Constructor;
  138. }
  139. var Event = /*#__PURE__*/ (function () {
  140. function Event(p, featureId, ringId, eventId) {
  141. _classCallCheck(this, Event);
  142. this.p = {
  143. x: p[0],
  144. y: p[1],
  145. };
  146. this.featureId = featureId;
  147. this.ringId = ringId;
  148. this.eventId = eventId;
  149. this.otherEvent = null;
  150. this.isLeftEndpoint = null;
  151. }
  152. _createClass(Event, [
  153. {
  154. key: "isSamePoint",
  155. value: function isSamePoint(eventToCheck) {
  156. return this.p.x === eventToCheck.p.x && this.p.y === eventToCheck.p.y;
  157. },
  158. },
  159. ]);
  160. return Event;
  161. })();
  162. function fillEventQueue(geojson, eventQueue) {
  163. if (geojson.type === "FeatureCollection") {
  164. var features = geojson.features;
  165. for (var i = 0; i < features.length; i++) {
  166. processFeature(features[i], eventQueue);
  167. }
  168. }
  169. else {
  170. processFeature(geojson, eventQueue);
  171. }
  172. }
  173. var featureId = 0;
  174. var ringId = 0;
  175. var eventId = 0;
  176. function processFeature(featureOrGeometry, eventQueue) {
  177. var geom = featureOrGeometry.type === "Feature"
  178. ? featureOrGeometry.geometry
  179. : featureOrGeometry;
  180. var coords = geom.coordinates; // standardise the input
  181. if (geom.type === "Polygon" || geom.type === "MultiLineString")
  182. coords = [coords];
  183. if (geom.type === "LineString")
  184. coords = [[coords]];
  185. for (var i = 0; i < coords.length; i++) {
  186. for (var ii = 0; ii < coords[i].length; ii++) {
  187. var currentP = coords[i][ii][0];
  188. var nextP = null;
  189. ringId = ringId + 1;
  190. for (var iii = 0; iii < coords[i][ii].length - 1; iii++) {
  191. nextP = coords[i][ii][iii + 1];
  192. var e1 = new Event(currentP, featureId, ringId, eventId);
  193. var e2 = new Event(nextP, featureId, ringId, eventId + 1);
  194. e1.otherEvent = e2;
  195. e2.otherEvent = e1;
  196. if (checkWhichEventIsLeft(e1, e2) > 0) {
  197. e2.isLeftEndpoint = true;
  198. e1.isLeftEndpoint = false;
  199. }
  200. else {
  201. e1.isLeftEndpoint = true;
  202. e2.isLeftEndpoint = false;
  203. }
  204. eventQueue.push(e1);
  205. eventQueue.push(e2);
  206. currentP = nextP;
  207. eventId = eventId + 1;
  208. }
  209. }
  210. }
  211. featureId = featureId + 1;
  212. }
  213. var Segment = function Segment(event) {
  214. _classCallCheck(this, Segment);
  215. this.leftSweepEvent = event;
  216. this.rightSweepEvent = event.otherEvent;
  217. };
  218. function testSegmentIntersect(seg1, seg2) {
  219. if (seg1 === null || seg2 === null)
  220. return false;
  221. if (seg1.leftSweepEvent.ringId === seg2.leftSweepEvent.ringId &&
  222. (seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
  223. seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
  224. seg1.rightSweepEvent.isSamePoint(seg2.rightSweepEvent) ||
  225. seg1.leftSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
  226. seg1.leftSweepEvent.isSamePoint(seg2.rightSweepEvent)))
  227. return false;
  228. var x1 = seg1.leftSweepEvent.p.x;
  229. var y1 = seg1.leftSweepEvent.p.y;
  230. var x2 = seg1.rightSweepEvent.p.x;
  231. var y2 = seg1.rightSweepEvent.p.y;
  232. var x3 = seg2.leftSweepEvent.p.x;
  233. var y3 = seg2.leftSweepEvent.p.y;
  234. var x4 = seg2.rightSweepEvent.p.x;
  235. var y4 = seg2.rightSweepEvent.p.y;
  236. var denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
  237. var numeA = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
  238. var numeB = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
  239. if (denom === 0) {
  240. if (numeA === 0 && numeB === 0)
  241. return false;
  242. return false;
  243. }
  244. var uA = numeA / denom;
  245. var uB = numeB / denom;
  246. if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
  247. var x = x1 + uA * (x2 - x1);
  248. var y = y1 + uA * (y2 - y1);
  249. return [x, y];
  250. }
  251. return false;
  252. }
  253. function runCheck(eventQueue, ignoreSelfIntersections) {
  254. ignoreSelfIntersections = ignoreSelfIntersections
  255. ? ignoreSelfIntersections
  256. : false;
  257. var intersectionPoints = [];
  258. var outQueue = new tinyqueue([], checkWhichSegmentHasRightEndpointFirst);
  259. while (eventQueue.length) {
  260. var event = eventQueue.pop();
  261. if (event.isLeftEndpoint) {
  262. // debugEventAndSegments(event.p, outQueue.data)
  263. var segment = new Segment(event);
  264. for (var i = 0; i < outQueue.data.length; i++) {
  265. var otherSeg = outQueue.data[i];
  266. if (ignoreSelfIntersections) {
  267. if (otherSeg.leftSweepEvent.featureId === event.featureId)
  268. continue;
  269. }
  270. var intersection = testSegmentIntersect(segment, otherSeg);
  271. if (intersection !== false)
  272. intersectionPoints.push(intersection);
  273. }
  274. outQueue.push(segment);
  275. }
  276. else if (event.isLeftEndpoint === false) {
  277. outQueue.pop(); // const seg = outQueue.pop()
  278. // debugRemovingSegment(event.p, seg)
  279. }
  280. }
  281. return intersectionPoints;
  282. }
  283. function sweeplineIntersections(geojson, ignoreSelfIntersections) {
  284. var eventQueue = new tinyqueue([], checkWhichEventIsLeft);
  285. fillEventQueue(geojson, eventQueue);
  286. return runCheck(eventQueue, ignoreSelfIntersections);
  287. }
  288. export default sweeplineIntersections;