sweeplineIntersections.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.sweeplineIntersections = factory());
  5. }(this, (function () { 'use strict';
  6. var TinyQueue = function TinyQueue(data, compare) {
  7. if ( data === void 0 ) data = [];
  8. if ( compare === void 0 ) compare = defaultCompare;
  9. this.data = data;
  10. this.length = this.data.length;
  11. this.compare = compare;
  12. if (this.length > 0) {
  13. for (var i = (this.length >> 1) - 1; i >= 0; i--) { this._down(i); }
  14. }
  15. };
  16. TinyQueue.prototype.push = function push (item) {
  17. this.data.push(item);
  18. this.length++;
  19. this._up(this.length - 1);
  20. };
  21. TinyQueue.prototype.pop = function pop () {
  22. if (this.length === 0) { return undefined; }
  23. var top = this.data[0];
  24. var bottom = this.data.pop();
  25. this.length--;
  26. if (this.length > 0) {
  27. this.data[0] = bottom;
  28. this._down(0);
  29. }
  30. return top;
  31. };
  32. TinyQueue.prototype.peek = function peek () {
  33. return this.data[0];
  34. };
  35. TinyQueue.prototype._up = function _up (pos) {
  36. var ref = this;
  37. var data = ref.data;
  38. var compare = ref.compare;
  39. var item = data[pos];
  40. while (pos > 0) {
  41. var parent = (pos - 1) >> 1;
  42. var current = data[parent];
  43. if (compare(item, current) >= 0) { break; }
  44. data[pos] = current;
  45. pos = parent;
  46. }
  47. data[pos] = item;
  48. };
  49. TinyQueue.prototype._down = function _down (pos) {
  50. var ref = this;
  51. var data = ref.data;
  52. var compare = ref.compare;
  53. var halfLength = this.length >> 1;
  54. var item = data[pos];
  55. while (pos < halfLength) {
  56. var left = (pos << 1) + 1;
  57. var best = data[left];
  58. var right = left + 1;
  59. if (right < this.length && compare(data[right], best) < 0) {
  60. left = right;
  61. best = data[right];
  62. }
  63. if (compare(best, item) >= 0) { break; }
  64. data[pos] = best;
  65. pos = left;
  66. }
  67. data[pos] = item;
  68. };
  69. function defaultCompare(a, b) {
  70. return a < b ? -1 : a > b ? 1 : 0;
  71. }
  72. function checkWhichEventIsLeft (e1, e2) {
  73. if (e1.p.x > e2.p.x) { return 1 }
  74. if (e1.p.x < e2.p.x) { return -1 }
  75. if (e1.p.y !== e2.p.y) { return e1.p.y > e2.p.y ? 1 : -1 }
  76. return 1
  77. }
  78. function checkWhichSegmentHasRightEndpointFirst (seg1, seg2) {
  79. if (seg1.rightSweepEvent.p.x > seg2.rightSweepEvent.p.x) { return 1 }
  80. if (seg1.rightSweepEvent.p.x < seg2.rightSweepEvent.p.x) { return -1 }
  81. if (seg1.rightSweepEvent.p.y !== seg2.rightSweepEvent.p.y) { return seg1.rightSweepEvent.p.y < seg2.rightSweepEvent.p.y ? 1 : -1 }
  82. return 1
  83. }
  84. var Event = function Event (p, featureId, ringId, eventId) {
  85. this.p = {
  86. x: p[0],
  87. y: p[1]
  88. };
  89. this.featureId = featureId;
  90. this.ringId = ringId;
  91. this.eventId = eventId;
  92. this.otherEvent = null;
  93. this.isLeftEndpoint = null;
  94. };
  95. Event.prototype.isSamePoint = function isSamePoint (eventToCheck) {
  96. return this.p.x === eventToCheck.p.x && this.p.y === eventToCheck.p.y
  97. };
  98. function fillEventQueue (geojson, eventQueue) {
  99. if (geojson.type === 'FeatureCollection') {
  100. var features = geojson.features;
  101. for (var i = 0; i < features.length; i++) {
  102. processFeature(features[i], eventQueue);
  103. }
  104. } else {
  105. processFeature(geojson, eventQueue);
  106. }
  107. }
  108. var featureId = 0;
  109. var ringId = 0;
  110. var eventId = 0;
  111. function processFeature (featureOrGeometry, eventQueue) {
  112. var geom = featureOrGeometry.type === 'Feature' ? featureOrGeometry.geometry : featureOrGeometry;
  113. var coords = geom.coordinates;
  114. // standardise the input
  115. if (geom.type === 'Polygon' || geom.type === 'MultiLineString') { coords = [coords]; }
  116. if (geom.type === 'LineString') { coords = [[coords]]; }
  117. for (var i = 0; i < coords.length; i++) {
  118. for (var ii = 0; ii < coords[i].length; ii++) {
  119. var currentP = coords[i][ii][0];
  120. var nextP = null;
  121. ringId = ringId + 1;
  122. for (var iii = 0; iii < coords[i][ii].length - 1; iii++) {
  123. nextP = coords[i][ii][iii + 1];
  124. var e1 = new Event(currentP, featureId, ringId, eventId);
  125. var e2 = new Event(nextP, featureId, ringId, eventId + 1);
  126. e1.otherEvent = e2;
  127. e2.otherEvent = e1;
  128. if (checkWhichEventIsLeft(e1, e2) > 0) {
  129. e2.isLeftEndpoint = true;
  130. e1.isLeftEndpoint = false;
  131. } else {
  132. e1.isLeftEndpoint = true;
  133. e2.isLeftEndpoint = false;
  134. }
  135. eventQueue.push(e1);
  136. eventQueue.push(e2);
  137. currentP = nextP;
  138. eventId = eventId + 1;
  139. }
  140. }
  141. }
  142. featureId = featureId + 1;
  143. }
  144. var Segment = function Segment (event) {
  145. this.leftSweepEvent = event;
  146. this.rightSweepEvent = event.otherEvent;
  147. };
  148. function testSegmentIntersect (seg1, seg2) {
  149. if (seg1 === null || seg2 === null) { return false }
  150. if (seg1.leftSweepEvent.ringId === seg2.leftSweepEvent.ringId &&
  151. (seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
  152. seg1.rightSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
  153. seg1.rightSweepEvent.isSamePoint(seg2.rightSweepEvent) ||
  154. seg1.leftSweepEvent.isSamePoint(seg2.leftSweepEvent) ||
  155. seg1.leftSweepEvent.isSamePoint(seg2.rightSweepEvent))) { return false }
  156. var x1 = seg1.leftSweepEvent.p.x;
  157. var y1 = seg1.leftSweepEvent.p.y;
  158. var x2 = seg1.rightSweepEvent.p.x;
  159. var y2 = seg1.rightSweepEvent.p.y;
  160. var x3 = seg2.leftSweepEvent.p.x;
  161. var y3 = seg2.leftSweepEvent.p.y;
  162. var x4 = seg2.rightSweepEvent.p.x;
  163. var y4 = seg2.rightSweepEvent.p.y;
  164. var denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
  165. var numeA = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3));
  166. var numeB = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3));
  167. if (denom === 0) {
  168. if (numeA === 0 && numeB === 0) { return false }
  169. return false
  170. }
  171. var uA = numeA / denom;
  172. var uB = numeB / denom;
  173. if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
  174. var x = x1 + (uA * (x2 - x1));
  175. var y = y1 + (uA * (y2 - y1));
  176. return [x, y]
  177. }
  178. return false
  179. }
  180. // import {debugEventAndSegments, debugRemovingSegment} from './debug'
  181. function runCheck (eventQueue, ignoreSelfIntersections) {
  182. ignoreSelfIntersections = ignoreSelfIntersections ? ignoreSelfIntersections : false;
  183. var intersectionPoints = [];
  184. var outQueue = new TinyQueue([], checkWhichSegmentHasRightEndpointFirst);
  185. while (eventQueue.length) {
  186. var event = eventQueue.pop();
  187. if (event.isLeftEndpoint) {
  188. // debugEventAndSegments(event.p, outQueue.data)
  189. var segment = new Segment(event);
  190. for (var i = 0; i < outQueue.data.length; i++) {
  191. var otherSeg = outQueue.data[i];
  192. if (ignoreSelfIntersections) {
  193. if (otherSeg.leftSweepEvent.featureId === event.featureId) { continue }
  194. }
  195. var intersection = testSegmentIntersect(segment, otherSeg);
  196. if (intersection !== false) { intersectionPoints.push(intersection); }
  197. }
  198. outQueue.push(segment);
  199. } else if (event.isLeftEndpoint === false) {
  200. outQueue.pop();
  201. // const seg = outQueue.pop()
  202. // debugRemovingSegment(event.p, seg)
  203. }
  204. }
  205. return intersectionPoints
  206. }
  207. function sweeplineIntersections (geojson, ignoreSelfIntersections) {
  208. var eventQueue = new TinyQueue([], checkWhichEventIsLeft);
  209. fillEventQueue(geojson, eventQueue);
  210. return runCheck(eventQueue, ignoreSelfIntersections)
  211. }
  212. return sweeplineIntersections;
  213. })));