sweeplineIntersections.esm.js 7.4 KB

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