polygon-clipping.cjs.js 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803
  1. 'use strict';
  2. var SplayTree = require('splaytree');
  3. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  4. var SplayTree__default = /*#__PURE__*/_interopDefaultLegacy(SplayTree);
  5. /**
  6. * A bounding box has the format:
  7. *
  8. * { ll: { x: xmin, y: ymin }, ur: { x: xmax, y: ymax } }
  9. *
  10. */
  11. const isInBbox = (bbox, point) => {
  12. return bbox.ll.x <= point.x && point.x <= bbox.ur.x && bbox.ll.y <= point.y && point.y <= bbox.ur.y;
  13. };
  14. /* Returns either null, or a bbox (aka an ordered pair of points)
  15. * If there is only one point of overlap, a bbox with identical points
  16. * will be returned */
  17. const getBboxOverlap = (b1, b2) => {
  18. // check if the bboxes overlap at all
  19. 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;
  20. // find the middle two X values
  21. const lowerX = b1.ll.x < b2.ll.x ? b2.ll.x : b1.ll.x;
  22. const upperX = b1.ur.x < b2.ur.x ? b1.ur.x : b2.ur.x;
  23. // find the middle two Y values
  24. const lowerY = b1.ll.y < b2.ll.y ? b2.ll.y : b1.ll.y;
  25. const upperY = b1.ur.y < b2.ur.y ? b1.ur.y : b2.ur.y;
  26. // put those middle values together to get the overlap
  27. return {
  28. ll: {
  29. x: lowerX,
  30. y: lowerY
  31. },
  32. ur: {
  33. x: upperX,
  34. y: upperY
  35. }
  36. };
  37. };
  38. /* Javascript doesn't do integer math. Everything is
  39. * floating point with percision Number.EPSILON.
  40. *
  41. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON
  42. */
  43. let epsilon$1 = Number.EPSILON;
  44. // IE Polyfill
  45. if (epsilon$1 === undefined) epsilon$1 = Math.pow(2, -52);
  46. const EPSILON_SQ = epsilon$1 * epsilon$1;
  47. /* FLP comparator */
  48. const cmp = (a, b) => {
  49. // check if they're both 0
  50. if (-epsilon$1 < a && a < epsilon$1) {
  51. if (-epsilon$1 < b && b < epsilon$1) {
  52. return 0;
  53. }
  54. }
  55. // check if they're flp equal
  56. const ab = a - b;
  57. if (ab * ab < EPSILON_SQ * a * b) {
  58. return 0;
  59. }
  60. // normal comparison
  61. return a < b ? -1 : 1;
  62. };
  63. /**
  64. * This class rounds incoming values sufficiently so that
  65. * floating points problems are, for the most part, avoided.
  66. *
  67. * Incoming points are have their x & y values tested against
  68. * all previously seen x & y values. If either is 'too close'
  69. * to a previously seen value, it's value is 'snapped' to the
  70. * previously seen value.
  71. *
  72. * All points should be rounded by this class before being
  73. * stored in any data structures in the rest of this algorithm.
  74. */
  75. class PtRounder {
  76. constructor() {
  77. this.reset();
  78. }
  79. reset() {
  80. this.xRounder = new CoordRounder();
  81. this.yRounder = new CoordRounder();
  82. }
  83. round(x, y) {
  84. return {
  85. x: this.xRounder.round(x),
  86. y: this.yRounder.round(y)
  87. };
  88. }
  89. }
  90. class CoordRounder {
  91. constructor() {
  92. this.tree = new SplayTree__default["default"]();
  93. // preseed with 0 so we don't end up with values < Number.EPSILON
  94. this.round(0);
  95. }
  96. // Note: this can rounds input values backwards or forwards.
  97. // You might ask, why not restrict this to just rounding
  98. // forwards? Wouldn't that allow left endpoints to always
  99. // remain left endpoints during splitting (never change to
  100. // right). No - it wouldn't, because we snap intersections
  101. // to endpoints (to establish independence from the segment
  102. // angle for t-intersections).
  103. round(coord) {
  104. const node = this.tree.add(coord);
  105. const prevNode = this.tree.prev(node);
  106. if (prevNode !== null && cmp(node.key, prevNode.key) === 0) {
  107. this.tree.remove(coord);
  108. return prevNode.key;
  109. }
  110. const nextNode = this.tree.next(node);
  111. if (nextNode !== null && cmp(node.key, nextNode.key) === 0) {
  112. this.tree.remove(coord);
  113. return nextNode.key;
  114. }
  115. return coord;
  116. }
  117. }
  118. // singleton available by import
  119. const rounder = new PtRounder();
  120. const epsilon = 1.1102230246251565e-16;
  121. const splitter = 134217729;
  122. const resulterrbound = (3 + 8 * epsilon) * epsilon;
  123. // fast_expansion_sum_zeroelim routine from oritinal code
  124. function sum(elen, e, flen, f, h) {
  125. let Q, Qnew, hh, bvirt;
  126. let enow = e[0];
  127. let fnow = f[0];
  128. let eindex = 0;
  129. let findex = 0;
  130. if ((fnow > enow) === (fnow > -enow)) {
  131. Q = enow;
  132. enow = e[++eindex];
  133. } else {
  134. Q = fnow;
  135. fnow = f[++findex];
  136. }
  137. let hindex = 0;
  138. if (eindex < elen && findex < flen) {
  139. if ((fnow > enow) === (fnow > -enow)) {
  140. Qnew = enow + Q;
  141. hh = Q - (Qnew - enow);
  142. enow = e[++eindex];
  143. } else {
  144. Qnew = fnow + Q;
  145. hh = Q - (Qnew - fnow);
  146. fnow = f[++findex];
  147. }
  148. Q = Qnew;
  149. if (hh !== 0) {
  150. h[hindex++] = hh;
  151. }
  152. while (eindex < elen && findex < flen) {
  153. if ((fnow > enow) === (fnow > -enow)) {
  154. Qnew = Q + enow;
  155. bvirt = Qnew - Q;
  156. hh = Q - (Qnew - bvirt) + (enow - bvirt);
  157. enow = e[++eindex];
  158. } else {
  159. Qnew = Q + fnow;
  160. bvirt = Qnew - Q;
  161. hh = Q - (Qnew - bvirt) + (fnow - bvirt);
  162. fnow = f[++findex];
  163. }
  164. Q = Qnew;
  165. if (hh !== 0) {
  166. h[hindex++] = hh;
  167. }
  168. }
  169. }
  170. while (eindex < elen) {
  171. Qnew = Q + enow;
  172. bvirt = Qnew - Q;
  173. hh = Q - (Qnew - bvirt) + (enow - bvirt);
  174. enow = e[++eindex];
  175. Q = Qnew;
  176. if (hh !== 0) {
  177. h[hindex++] = hh;
  178. }
  179. }
  180. while (findex < flen) {
  181. Qnew = Q + fnow;
  182. bvirt = Qnew - Q;
  183. hh = Q - (Qnew - bvirt) + (fnow - bvirt);
  184. fnow = f[++findex];
  185. Q = Qnew;
  186. if (hh !== 0) {
  187. h[hindex++] = hh;
  188. }
  189. }
  190. if (Q !== 0 || hindex === 0) {
  191. h[hindex++] = Q;
  192. }
  193. return hindex;
  194. }
  195. function estimate(elen, e) {
  196. let Q = e[0];
  197. for (let i = 1; i < elen; i++) Q += e[i];
  198. return Q;
  199. }
  200. function vec(n) {
  201. return new Float64Array(n);
  202. }
  203. const ccwerrboundA = (3 + 16 * epsilon) * epsilon;
  204. const ccwerrboundB = (2 + 12 * epsilon) * epsilon;
  205. const ccwerrboundC = (9 + 64 * epsilon) * epsilon * epsilon;
  206. const B = vec(4);
  207. const C1 = vec(8);
  208. const C2 = vec(12);
  209. const D = vec(16);
  210. const u = vec(4);
  211. function orient2dadapt(ax, ay, bx, by, cx, cy, detsum) {
  212. let acxtail, acytail, bcxtail, bcytail;
  213. let bvirt, c, ahi, alo, bhi, blo, _i, _j, _0, s1, s0, t1, t0, u3;
  214. const acx = ax - cx;
  215. const bcx = bx - cx;
  216. const acy = ay - cy;
  217. const bcy = by - cy;
  218. s1 = acx * bcy;
  219. c = splitter * acx;
  220. ahi = c - (c - acx);
  221. alo = acx - ahi;
  222. c = splitter * bcy;
  223. bhi = c - (c - bcy);
  224. blo = bcy - bhi;
  225. s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
  226. t1 = acy * bcx;
  227. c = splitter * acy;
  228. ahi = c - (c - acy);
  229. alo = acy - ahi;
  230. c = splitter * bcx;
  231. bhi = c - (c - bcx);
  232. blo = bcx - bhi;
  233. t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
  234. _i = s0 - t0;
  235. bvirt = s0 - _i;
  236. B[0] = s0 - (_i + bvirt) + (bvirt - t0);
  237. _j = s1 + _i;
  238. bvirt = _j - s1;
  239. _0 = s1 - (_j - bvirt) + (_i - bvirt);
  240. _i = _0 - t1;
  241. bvirt = _0 - _i;
  242. B[1] = _0 - (_i + bvirt) + (bvirt - t1);
  243. u3 = _j + _i;
  244. bvirt = u3 - _j;
  245. B[2] = _j - (u3 - bvirt) + (_i - bvirt);
  246. B[3] = u3;
  247. let det = estimate(4, B);
  248. let errbound = ccwerrboundB * detsum;
  249. if (det >= errbound || -det >= errbound) {
  250. return det;
  251. }
  252. bvirt = ax - acx;
  253. acxtail = ax - (acx + bvirt) + (bvirt - cx);
  254. bvirt = bx - bcx;
  255. bcxtail = bx - (bcx + bvirt) + (bvirt - cx);
  256. bvirt = ay - acy;
  257. acytail = ay - (acy + bvirt) + (bvirt - cy);
  258. bvirt = by - bcy;
  259. bcytail = by - (bcy + bvirt) + (bvirt - cy);
  260. if (acxtail === 0 && acytail === 0 && bcxtail === 0 && bcytail === 0) {
  261. return det;
  262. }
  263. errbound = ccwerrboundC * detsum + resulterrbound * Math.abs(det);
  264. det += (acx * bcytail + bcy * acxtail) - (acy * bcxtail + bcx * acytail);
  265. if (det >= errbound || -det >= errbound) return det;
  266. s1 = acxtail * bcy;
  267. c = splitter * acxtail;
  268. ahi = c - (c - acxtail);
  269. alo = acxtail - ahi;
  270. c = splitter * bcy;
  271. bhi = c - (c - bcy);
  272. blo = bcy - bhi;
  273. s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
  274. t1 = acytail * bcx;
  275. c = splitter * acytail;
  276. ahi = c - (c - acytail);
  277. alo = acytail - ahi;
  278. c = splitter * bcx;
  279. bhi = c - (c - bcx);
  280. blo = bcx - bhi;
  281. t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
  282. _i = s0 - t0;
  283. bvirt = s0 - _i;
  284. u[0] = s0 - (_i + bvirt) + (bvirt - t0);
  285. _j = s1 + _i;
  286. bvirt = _j - s1;
  287. _0 = s1 - (_j - bvirt) + (_i - bvirt);
  288. _i = _0 - t1;
  289. bvirt = _0 - _i;
  290. u[1] = _0 - (_i + bvirt) + (bvirt - t1);
  291. u3 = _j + _i;
  292. bvirt = u3 - _j;
  293. u[2] = _j - (u3 - bvirt) + (_i - bvirt);
  294. u[3] = u3;
  295. const C1len = sum(4, B, 4, u, C1);
  296. s1 = acx * bcytail;
  297. c = splitter * acx;
  298. ahi = c - (c - acx);
  299. alo = acx - ahi;
  300. c = splitter * bcytail;
  301. bhi = c - (c - bcytail);
  302. blo = bcytail - bhi;
  303. s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
  304. t1 = acy * bcxtail;
  305. c = splitter * acy;
  306. ahi = c - (c - acy);
  307. alo = acy - ahi;
  308. c = splitter * bcxtail;
  309. bhi = c - (c - bcxtail);
  310. blo = bcxtail - bhi;
  311. t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
  312. _i = s0 - t0;
  313. bvirt = s0 - _i;
  314. u[0] = s0 - (_i + bvirt) + (bvirt - t0);
  315. _j = s1 + _i;
  316. bvirt = _j - s1;
  317. _0 = s1 - (_j - bvirt) + (_i - bvirt);
  318. _i = _0 - t1;
  319. bvirt = _0 - _i;
  320. u[1] = _0 - (_i + bvirt) + (bvirt - t1);
  321. u3 = _j + _i;
  322. bvirt = u3 - _j;
  323. u[2] = _j - (u3 - bvirt) + (_i - bvirt);
  324. u[3] = u3;
  325. const C2len = sum(C1len, C1, 4, u, C2);
  326. s1 = acxtail * bcytail;
  327. c = splitter * acxtail;
  328. ahi = c - (c - acxtail);
  329. alo = acxtail - ahi;
  330. c = splitter * bcytail;
  331. bhi = c - (c - bcytail);
  332. blo = bcytail - bhi;
  333. s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
  334. t1 = acytail * bcxtail;
  335. c = splitter * acytail;
  336. ahi = c - (c - acytail);
  337. alo = acytail - ahi;
  338. c = splitter * bcxtail;
  339. bhi = c - (c - bcxtail);
  340. blo = bcxtail - bhi;
  341. t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
  342. _i = s0 - t0;
  343. bvirt = s0 - _i;
  344. u[0] = s0 - (_i + bvirt) + (bvirt - t0);
  345. _j = s1 + _i;
  346. bvirt = _j - s1;
  347. _0 = s1 - (_j - bvirt) + (_i - bvirt);
  348. _i = _0 - t1;
  349. bvirt = _0 - _i;
  350. u[1] = _0 - (_i + bvirt) + (bvirt - t1);
  351. u3 = _j + _i;
  352. bvirt = u3 - _j;
  353. u[2] = _j - (u3 - bvirt) + (_i - bvirt);
  354. u[3] = u3;
  355. const Dlen = sum(C2len, C2, 4, u, D);
  356. return D[Dlen - 1];
  357. }
  358. function orient2d(ax, ay, bx, by, cx, cy) {
  359. const detleft = (ay - cy) * (bx - cx);
  360. const detright = (ax - cx) * (by - cy);
  361. const det = detleft - detright;
  362. const detsum = Math.abs(detleft + detright);
  363. if (Math.abs(det) >= ccwerrboundA * detsum) return det;
  364. return -orient2dadapt(ax, ay, bx, by, cx, cy, detsum);
  365. }
  366. /* Cross Product of two vectors with first point at origin */
  367. const crossProduct = (a, b) => a.x * b.y - a.y * b.x;
  368. /* Dot Product of two vectors with first point at origin */
  369. const dotProduct = (a, b) => a.x * b.x + a.y * b.y;
  370. /* Comparator for two vectors with same starting point */
  371. const compareVectorAngles = (basePt, endPt1, endPt2) => {
  372. const res = orient2d(basePt.x, basePt.y, endPt1.x, endPt1.y, endPt2.x, endPt2.y);
  373. if (res > 0) return -1;
  374. if (res < 0) return 1;
  375. return 0;
  376. };
  377. const length = v => Math.sqrt(dotProduct(v, v));
  378. /* Get the sine of the angle from pShared -> pAngle to pShaed -> pBase */
  379. const sineOfAngle = (pShared, pBase, pAngle) => {
  380. const vBase = {
  381. x: pBase.x - pShared.x,
  382. y: pBase.y - pShared.y
  383. };
  384. const vAngle = {
  385. x: pAngle.x - pShared.x,
  386. y: pAngle.y - pShared.y
  387. };
  388. return crossProduct(vAngle, vBase) / length(vAngle) / length(vBase);
  389. };
  390. /* Get the cosine of the angle from pShared -> pAngle to pShaed -> pBase */
  391. const cosineOfAngle = (pShared, pBase, pAngle) => {
  392. const vBase = {
  393. x: pBase.x - pShared.x,
  394. y: pBase.y - pShared.y
  395. };
  396. const vAngle = {
  397. x: pAngle.x - pShared.x,
  398. y: pAngle.y - pShared.y
  399. };
  400. return dotProduct(vAngle, vBase) / length(vAngle) / length(vBase);
  401. };
  402. /* Get the x coordinate where the given line (defined by a point and vector)
  403. * crosses the horizontal line with the given y coordiante.
  404. * In the case of parrallel lines (including overlapping ones) returns null. */
  405. const horizontalIntersection = (pt, v, y) => {
  406. if (v.y === 0) return null;
  407. return {
  408. x: pt.x + v.x / v.y * (y - pt.y),
  409. y: y
  410. };
  411. };
  412. /* Get the y coordinate where the given line (defined by a point and vector)
  413. * crosses the vertical line with the given x coordiante.
  414. * In the case of parrallel lines (including overlapping ones) returns null. */
  415. const verticalIntersection = (pt, v, x) => {
  416. if (v.x === 0) return null;
  417. return {
  418. x: x,
  419. y: pt.y + v.y / v.x * (x - pt.x)
  420. };
  421. };
  422. /* Get the intersection of two lines, each defined by a base point and a vector.
  423. * In the case of parrallel lines (including overlapping ones) returns null. */
  424. const intersection$1 = (pt1, v1, pt2, v2) => {
  425. // take some shortcuts for vertical and horizontal lines
  426. // this also ensures we don't calculate an intersection and then discover
  427. // it's actually outside the bounding box of the line
  428. if (v1.x === 0) return verticalIntersection(pt2, v2, pt1.x);
  429. if (v2.x === 0) return verticalIntersection(pt1, v1, pt2.x);
  430. if (v1.y === 0) return horizontalIntersection(pt2, v2, pt1.y);
  431. if (v2.y === 0) return horizontalIntersection(pt1, v1, pt2.y);
  432. // General case for non-overlapping segments.
  433. // This algorithm is based on Schneider and Eberly.
  434. // http://www.cimec.org.ar/~ncalvo/Schneider_Eberly.pdf - pg 244
  435. const kross = crossProduct(v1, v2);
  436. if (kross == 0) return null;
  437. const ve = {
  438. x: pt2.x - pt1.x,
  439. y: pt2.y - pt1.y
  440. };
  441. const d1 = crossProduct(ve, v1) / kross;
  442. const d2 = crossProduct(ve, v2) / kross;
  443. // take the average of the two calculations to minimize rounding error
  444. const x1 = pt1.x + d2 * v1.x,
  445. x2 = pt2.x + d1 * v2.x;
  446. const y1 = pt1.y + d2 * v1.y,
  447. y2 = pt2.y + d1 * v2.y;
  448. const x = (x1 + x2) / 2;
  449. const y = (y1 + y2) / 2;
  450. return {
  451. x: x,
  452. y: y
  453. };
  454. };
  455. class SweepEvent {
  456. // for ordering sweep events in the sweep event queue
  457. static compare(a, b) {
  458. // favor event with a point that the sweep line hits first
  459. const ptCmp = SweepEvent.comparePoints(a.point, b.point);
  460. if (ptCmp !== 0) return ptCmp;
  461. // the points are the same, so link them if needed
  462. if (a.point !== b.point) a.link(b);
  463. // favor right events over left
  464. if (a.isLeft !== b.isLeft) return a.isLeft ? 1 : -1;
  465. // we have two matching left or right endpoints
  466. // ordering of this case is the same as for their segments
  467. return Segment.compare(a.segment, b.segment);
  468. }
  469. // for ordering points in sweep line order
  470. static comparePoints(aPt, bPt) {
  471. if (aPt.x < bPt.x) return -1;
  472. if (aPt.x > bPt.x) return 1;
  473. if (aPt.y < bPt.y) return -1;
  474. if (aPt.y > bPt.y) return 1;
  475. return 0;
  476. }
  477. // Warning: 'point' input will be modified and re-used (for performance)
  478. constructor(point, isLeft) {
  479. if (point.events === undefined) point.events = [this];else point.events.push(this);
  480. this.point = point;
  481. this.isLeft = isLeft;
  482. // this.segment, this.otherSE set by factory
  483. }
  484. link(other) {
  485. if (other.point === this.point) {
  486. throw new Error("Tried to link already linked events");
  487. }
  488. const otherEvents = other.point.events;
  489. for (let i = 0, iMax = otherEvents.length; i < iMax; i++) {
  490. const evt = otherEvents[i];
  491. this.point.events.push(evt);
  492. evt.point = this.point;
  493. }
  494. this.checkForConsuming();
  495. }
  496. /* Do a pass over our linked events and check to see if any pair
  497. * of segments match, and should be consumed. */
  498. checkForConsuming() {
  499. // FIXME: The loops in this method run O(n^2) => no good.
  500. // Maintain little ordered sweep event trees?
  501. // Can we maintaining an ordering that avoids the need
  502. // for the re-sorting with getLeftmostComparator in geom-out?
  503. // Compare each pair of events to see if other events also match
  504. const numEvents = this.point.events.length;
  505. for (let i = 0; i < numEvents; i++) {
  506. const evt1 = this.point.events[i];
  507. if (evt1.segment.consumedBy !== undefined) continue;
  508. for (let j = i + 1; j < numEvents; j++) {
  509. const evt2 = this.point.events[j];
  510. if (evt2.consumedBy !== undefined) continue;
  511. if (evt1.otherSE.point.events !== evt2.otherSE.point.events) continue;
  512. evt1.segment.consume(evt2.segment);
  513. }
  514. }
  515. }
  516. getAvailableLinkedEvents() {
  517. // point.events is always of length 2 or greater
  518. const events = [];
  519. for (let i = 0, iMax = this.point.events.length; i < iMax; i++) {
  520. const evt = this.point.events[i];
  521. if (evt !== this && !evt.segment.ringOut && evt.segment.isInResult()) {
  522. events.push(evt);
  523. }
  524. }
  525. return events;
  526. }
  527. /**
  528. * Returns a comparator function for sorting linked events that will
  529. * favor the event that will give us the smallest left-side angle.
  530. * All ring construction starts as low as possible heading to the right,
  531. * so by always turning left as sharp as possible we'll get polygons
  532. * without uncessary loops & holes.
  533. *
  534. * The comparator function has a compute cache such that it avoids
  535. * re-computing already-computed values.
  536. */
  537. getLeftmostComparator(baseEvent) {
  538. const cache = new Map();
  539. const fillCache = linkedEvent => {
  540. const nextEvent = linkedEvent.otherSE;
  541. cache.set(linkedEvent, {
  542. sine: sineOfAngle(this.point, baseEvent.point, nextEvent.point),
  543. cosine: cosineOfAngle(this.point, baseEvent.point, nextEvent.point)
  544. });
  545. };
  546. return (a, b) => {
  547. if (!cache.has(a)) fillCache(a);
  548. if (!cache.has(b)) fillCache(b);
  549. const {
  550. sine: asine,
  551. cosine: acosine
  552. } = cache.get(a);
  553. const {
  554. sine: bsine,
  555. cosine: bcosine
  556. } = cache.get(b);
  557. // both on or above x-axis
  558. if (asine >= 0 && bsine >= 0) {
  559. if (acosine < bcosine) return 1;
  560. if (acosine > bcosine) return -1;
  561. return 0;
  562. }
  563. // both below x-axis
  564. if (asine < 0 && bsine < 0) {
  565. if (acosine < bcosine) return -1;
  566. if (acosine > bcosine) return 1;
  567. return 0;
  568. }
  569. // one above x-axis, one below
  570. if (bsine < asine) return -1;
  571. if (bsine > asine) return 1;
  572. return 0;
  573. };
  574. }
  575. }
  576. // Give segments unique ID's to get consistent sorting of
  577. // segments and sweep events when all else is identical
  578. let segmentId = 0;
  579. class Segment {
  580. /* This compare() function is for ordering segments in the sweep
  581. * line tree, and does so according to the following criteria:
  582. *
  583. * Consider the vertical line that lies an infinestimal step to the
  584. * right of the right-more of the two left endpoints of the input
  585. * segments. Imagine slowly moving a point up from negative infinity
  586. * in the increasing y direction. Which of the two segments will that
  587. * point intersect first? That segment comes 'before' the other one.
  588. *
  589. * If neither segment would be intersected by such a line, (if one
  590. * or more of the segments are vertical) then the line to be considered
  591. * is directly on the right-more of the two left inputs.
  592. */
  593. static compare(a, b) {
  594. const alx = a.leftSE.point.x;
  595. const blx = b.leftSE.point.x;
  596. const arx = a.rightSE.point.x;
  597. const brx = b.rightSE.point.x;
  598. // check if they're even in the same vertical plane
  599. if (brx < alx) return 1;
  600. if (arx < blx) return -1;
  601. const aly = a.leftSE.point.y;
  602. const bly = b.leftSE.point.y;
  603. const ary = a.rightSE.point.y;
  604. const bry = b.rightSE.point.y;
  605. // is left endpoint of segment B the right-more?
  606. if (alx < blx) {
  607. // are the two segments in the same horizontal plane?
  608. if (bly < aly && bly < ary) return 1;
  609. if (bly > aly && bly > ary) return -1;
  610. // is the B left endpoint colinear to segment A?
  611. const aCmpBLeft = a.comparePoint(b.leftSE.point);
  612. if (aCmpBLeft < 0) return 1;
  613. if (aCmpBLeft > 0) return -1;
  614. // is the A right endpoint colinear to segment B ?
  615. const bCmpARight = b.comparePoint(a.rightSE.point);
  616. if (bCmpARight !== 0) return bCmpARight;
  617. // colinear segments, consider the one with left-more
  618. // left endpoint to be first (arbitrary?)
  619. return -1;
  620. }
  621. // is left endpoint of segment A the right-more?
  622. if (alx > blx) {
  623. if (aly < bly && aly < bry) return -1;
  624. if (aly > bly && aly > bry) return 1;
  625. // is the A left endpoint colinear to segment B?
  626. const bCmpALeft = b.comparePoint(a.leftSE.point);
  627. if (bCmpALeft !== 0) return bCmpALeft;
  628. // is the B right endpoint colinear to segment A?
  629. const aCmpBRight = a.comparePoint(b.rightSE.point);
  630. if (aCmpBRight < 0) return 1;
  631. if (aCmpBRight > 0) return -1;
  632. // colinear segments, consider the one with left-more
  633. // left endpoint to be first (arbitrary?)
  634. return 1;
  635. }
  636. // if we get here, the two left endpoints are in the same
  637. // vertical plane, ie alx === blx
  638. // consider the lower left-endpoint to come first
  639. if (aly < bly) return -1;
  640. if (aly > bly) return 1;
  641. // left endpoints are identical
  642. // check for colinearity by using the left-more right endpoint
  643. // is the A right endpoint more left-more?
  644. if (arx < brx) {
  645. const bCmpARight = b.comparePoint(a.rightSE.point);
  646. if (bCmpARight !== 0) return bCmpARight;
  647. }
  648. // is the B right endpoint more left-more?
  649. if (arx > brx) {
  650. const aCmpBRight = a.comparePoint(b.rightSE.point);
  651. if (aCmpBRight < 0) return 1;
  652. if (aCmpBRight > 0) return -1;
  653. }
  654. if (arx !== brx) {
  655. // are these two [almost] vertical segments with opposite orientation?
  656. // if so, the one with the lower right endpoint comes first
  657. const ay = ary - aly;
  658. const ax = arx - alx;
  659. const by = bry - bly;
  660. const bx = brx - blx;
  661. if (ay > ax && by < bx) return 1;
  662. if (ay < ax && by > bx) return -1;
  663. }
  664. // we have colinear segments with matching orientation
  665. // consider the one with more left-more right endpoint to be first
  666. if (arx > brx) return 1;
  667. if (arx < brx) return -1;
  668. // if we get here, two two right endpoints are in the same
  669. // vertical plane, ie arx === brx
  670. // consider the lower right-endpoint to come first
  671. if (ary < bry) return -1;
  672. if (ary > bry) return 1;
  673. // right endpoints identical as well, so the segments are idential
  674. // fall back on creation order as consistent tie-breaker
  675. if (a.id < b.id) return -1;
  676. if (a.id > b.id) return 1;
  677. // identical segment, ie a === b
  678. return 0;
  679. }
  680. /* Warning: a reference to ringWindings input will be stored,
  681. * and possibly will be later modified */
  682. constructor(leftSE, rightSE, rings, windings) {
  683. this.id = ++segmentId;
  684. this.leftSE = leftSE;
  685. leftSE.segment = this;
  686. leftSE.otherSE = rightSE;
  687. this.rightSE = rightSE;
  688. rightSE.segment = this;
  689. rightSE.otherSE = leftSE;
  690. this.rings = rings;
  691. this.windings = windings;
  692. // left unset for performance, set later in algorithm
  693. // this.ringOut, this.consumedBy, this.prev
  694. }
  695. static fromRing(pt1, pt2, ring) {
  696. let leftPt, rightPt, winding;
  697. // ordering the two points according to sweep line ordering
  698. const cmpPts = SweepEvent.comparePoints(pt1, pt2);
  699. if (cmpPts < 0) {
  700. leftPt = pt1;
  701. rightPt = pt2;
  702. winding = 1;
  703. } else if (cmpPts > 0) {
  704. leftPt = pt2;
  705. rightPt = pt1;
  706. winding = -1;
  707. } else throw new Error(`Tried to create degenerate segment at [${pt1.x}, ${pt1.y}]`);
  708. const leftSE = new SweepEvent(leftPt, true);
  709. const rightSE = new SweepEvent(rightPt, false);
  710. return new Segment(leftSE, rightSE, [ring], [winding]);
  711. }
  712. /* When a segment is split, the rightSE is replaced with a new sweep event */
  713. replaceRightSE(newRightSE) {
  714. this.rightSE = newRightSE;
  715. this.rightSE.segment = this;
  716. this.rightSE.otherSE = this.leftSE;
  717. this.leftSE.otherSE = this.rightSE;
  718. }
  719. bbox() {
  720. const y1 = this.leftSE.point.y;
  721. const y2 = this.rightSE.point.y;
  722. return {
  723. ll: {
  724. x: this.leftSE.point.x,
  725. y: y1 < y2 ? y1 : y2
  726. },
  727. ur: {
  728. x: this.rightSE.point.x,
  729. y: y1 > y2 ? y1 : y2
  730. }
  731. };
  732. }
  733. /* A vector from the left point to the right */
  734. vector() {
  735. return {
  736. x: this.rightSE.point.x - this.leftSE.point.x,
  737. y: this.rightSE.point.y - this.leftSE.point.y
  738. };
  739. }
  740. isAnEndpoint(pt) {
  741. 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;
  742. }
  743. /* Compare this segment with a point.
  744. *
  745. * A point P is considered to be colinear to a segment if there
  746. * exists a distance D such that if we travel along the segment
  747. * from one * endpoint towards the other a distance D, we find
  748. * ourselves at point P.
  749. *
  750. * Return value indicates:
  751. *
  752. * 1: point lies above the segment (to the left of vertical)
  753. * 0: point is colinear to segment
  754. * -1: point lies below the segment (to the right of vertical)
  755. */
  756. comparePoint(point) {
  757. if (this.isAnEndpoint(point)) return 0;
  758. const lPt = this.leftSE.point;
  759. const rPt = this.rightSE.point;
  760. const v = this.vector();
  761. // Exactly vertical segments.
  762. if (lPt.x === rPt.x) {
  763. if (point.x === lPt.x) return 0;
  764. return point.x < lPt.x ? 1 : -1;
  765. }
  766. // Nearly vertical segments with an intersection.
  767. // Check to see where a point on the line with matching Y coordinate is.
  768. const yDist = (point.y - lPt.y) / v.y;
  769. const xFromYDist = lPt.x + yDist * v.x;
  770. if (point.x === xFromYDist) return 0;
  771. // General case.
  772. // Check to see where a point on the line with matching X coordinate is.
  773. const xDist = (point.x - lPt.x) / v.x;
  774. const yFromXDist = lPt.y + xDist * v.y;
  775. if (point.y === yFromXDist) return 0;
  776. return point.y < yFromXDist ? -1 : 1;
  777. }
  778. /**
  779. * Given another segment, returns the first non-trivial intersection
  780. * between the two segments (in terms of sweep line ordering), if it exists.
  781. *
  782. * A 'non-trivial' intersection is one that will cause one or both of the
  783. * segments to be split(). As such, 'trivial' vs. 'non-trivial' intersection:
  784. *
  785. * * endpoint of segA with endpoint of segB --> trivial
  786. * * endpoint of segA with point along segB --> non-trivial
  787. * * endpoint of segB with point along segA --> non-trivial
  788. * * point along segA with point along segB --> non-trivial
  789. *
  790. * If no non-trivial intersection exists, return null
  791. * Else, return null.
  792. */
  793. getIntersection(other) {
  794. // If bboxes don't overlap, there can't be any intersections
  795. const tBbox = this.bbox();
  796. const oBbox = other.bbox();
  797. const bboxOverlap = getBboxOverlap(tBbox, oBbox);
  798. if (bboxOverlap === null) return null;
  799. // We first check to see if the endpoints can be considered intersections.
  800. // This will 'snap' intersections to endpoints if possible, and will
  801. // handle cases of colinearity.
  802. const tlp = this.leftSE.point;
  803. const trp = this.rightSE.point;
  804. const olp = other.leftSE.point;
  805. const orp = other.rightSE.point;
  806. // does each endpoint touch the other segment?
  807. // note that we restrict the 'touching' definition to only allow segments
  808. // to touch endpoints that lie forward from where we are in the sweep line pass
  809. const touchesOtherLSE = isInBbox(tBbox, olp) && this.comparePoint(olp) === 0;
  810. const touchesThisLSE = isInBbox(oBbox, tlp) && other.comparePoint(tlp) === 0;
  811. const touchesOtherRSE = isInBbox(tBbox, orp) && this.comparePoint(orp) === 0;
  812. const touchesThisRSE = isInBbox(oBbox, trp) && other.comparePoint(trp) === 0;
  813. // do left endpoints match?
  814. if (touchesThisLSE && touchesOtherLSE) {
  815. // these two cases are for colinear segments with matching left
  816. // endpoints, and one segment being longer than the other
  817. if (touchesThisRSE && !touchesOtherRSE) return trp;
  818. if (!touchesThisRSE && touchesOtherRSE) return orp;
  819. // either the two segments match exactly (two trival intersections)
  820. // or just on their left endpoint (one trivial intersection
  821. return null;
  822. }
  823. // does this left endpoint matches (other doesn't)
  824. if (touchesThisLSE) {
  825. // check for segments that just intersect on opposing endpoints
  826. if (touchesOtherRSE) {
  827. if (tlp.x === orp.x && tlp.y === orp.y) return null;
  828. }
  829. // t-intersection on left endpoint
  830. return tlp;
  831. }
  832. // does other left endpoint matches (this doesn't)
  833. if (touchesOtherLSE) {
  834. // check for segments that just intersect on opposing endpoints
  835. if (touchesThisRSE) {
  836. if (trp.x === olp.x && trp.y === olp.y) return null;
  837. }
  838. // t-intersection on left endpoint
  839. return olp;
  840. }
  841. // trivial intersection on right endpoints
  842. if (touchesThisRSE && touchesOtherRSE) return null;
  843. // t-intersections on just one right endpoint
  844. if (touchesThisRSE) return trp;
  845. if (touchesOtherRSE) return orp;
  846. // None of our endpoints intersect. Look for a general intersection between
  847. // infinite lines laid over the segments
  848. const pt = intersection$1(tlp, this.vector(), olp, other.vector());
  849. // are the segments parrallel? Note that if they were colinear with overlap,
  850. // they would have an endpoint intersection and that case was already handled above
  851. if (pt === null) return null;
  852. // is the intersection found between the lines not on the segments?
  853. if (!isInBbox(bboxOverlap, pt)) return null;
  854. // round the the computed point if needed
  855. return rounder.round(pt.x, pt.y);
  856. }
  857. /**
  858. * Split the given segment into multiple segments on the given points.
  859. * * Each existing segment will retain its leftSE and a new rightSE will be
  860. * generated for it.
  861. * * A new segment will be generated which will adopt the original segment's
  862. * rightSE, and a new leftSE will be generated for it.
  863. * * If there are more than two points given to split on, new segments
  864. * in the middle will be generated with new leftSE and rightSE's.
  865. * * An array of the newly generated SweepEvents will be returned.
  866. *
  867. * Warning: input array of points is modified
  868. */
  869. split(point) {
  870. const newEvents = [];
  871. const alreadyLinked = point.events !== undefined;
  872. const newLeftSE = new SweepEvent(point, true);
  873. const newRightSE = new SweepEvent(point, false);
  874. const oldRightSE = this.rightSE;
  875. this.replaceRightSE(newRightSE);
  876. newEvents.push(newRightSE);
  877. newEvents.push(newLeftSE);
  878. const newSeg = new Segment(newLeftSE, oldRightSE, this.rings.slice(), this.windings.slice());
  879. // when splitting a nearly vertical downward-facing segment,
  880. // sometimes one of the resulting new segments is vertical, in which
  881. // case its left and right events may need to be swapped
  882. if (SweepEvent.comparePoints(newSeg.leftSE.point, newSeg.rightSE.point) > 0) {
  883. newSeg.swapEvents();
  884. }
  885. if (SweepEvent.comparePoints(this.leftSE.point, this.rightSE.point) > 0) {
  886. this.swapEvents();
  887. }
  888. // in the point we just used to create new sweep events with was already
  889. // linked to other events, we need to check if either of the affected
  890. // segments should be consumed
  891. if (alreadyLinked) {
  892. newLeftSE.checkForConsuming();
  893. newRightSE.checkForConsuming();
  894. }
  895. return newEvents;
  896. }
  897. /* Swap which event is left and right */
  898. swapEvents() {
  899. const tmpEvt = this.rightSE;
  900. this.rightSE = this.leftSE;
  901. this.leftSE = tmpEvt;
  902. this.leftSE.isLeft = true;
  903. this.rightSE.isLeft = false;
  904. for (let i = 0, iMax = this.windings.length; i < iMax; i++) {
  905. this.windings[i] *= -1;
  906. }
  907. }
  908. /* Consume another segment. We take their rings under our wing
  909. * and mark them as consumed. Use for perfectly overlapping segments */
  910. consume(other) {
  911. let consumer = this;
  912. let consumee = other;
  913. while (consumer.consumedBy) consumer = consumer.consumedBy;
  914. while (consumee.consumedBy) consumee = consumee.consumedBy;
  915. const cmp = Segment.compare(consumer, consumee);
  916. if (cmp === 0) return; // already consumed
  917. // the winner of the consumption is the earlier segment
  918. // according to sweep line ordering
  919. if (cmp > 0) {
  920. const tmp = consumer;
  921. consumer = consumee;
  922. consumee = tmp;
  923. }
  924. // make sure a segment doesn't consume it's prev
  925. if (consumer.prev === consumee) {
  926. const tmp = consumer;
  927. consumer = consumee;
  928. consumee = tmp;
  929. }
  930. for (let i = 0, iMax = consumee.rings.length; i < iMax; i++) {
  931. const ring = consumee.rings[i];
  932. const winding = consumee.windings[i];
  933. const index = consumer.rings.indexOf(ring);
  934. if (index === -1) {
  935. consumer.rings.push(ring);
  936. consumer.windings.push(winding);
  937. } else consumer.windings[index] += winding;
  938. }
  939. consumee.rings = null;
  940. consumee.windings = null;
  941. consumee.consumedBy = consumer;
  942. // mark sweep events consumed as to maintain ordering in sweep event queue
  943. consumee.leftSE.consumedBy = consumer.leftSE;
  944. consumee.rightSE.consumedBy = consumer.rightSE;
  945. }
  946. /* The first segment previous segment chain that is in the result */
  947. prevInResult() {
  948. if (this._prevInResult !== undefined) return this._prevInResult;
  949. if (!this.prev) this._prevInResult = null;else if (this.prev.isInResult()) this._prevInResult = this.prev;else this._prevInResult = this.prev.prevInResult();
  950. return this._prevInResult;
  951. }
  952. beforeState() {
  953. if (this._beforeState !== undefined) return this._beforeState;
  954. if (!this.prev) this._beforeState = {
  955. rings: [],
  956. windings: [],
  957. multiPolys: []
  958. };else {
  959. const seg = this.prev.consumedBy || this.prev;
  960. this._beforeState = seg.afterState();
  961. }
  962. return this._beforeState;
  963. }
  964. afterState() {
  965. if (this._afterState !== undefined) return this._afterState;
  966. const beforeState = this.beforeState();
  967. this._afterState = {
  968. rings: beforeState.rings.slice(0),
  969. windings: beforeState.windings.slice(0),
  970. multiPolys: []
  971. };
  972. const ringsAfter = this._afterState.rings;
  973. const windingsAfter = this._afterState.windings;
  974. const mpsAfter = this._afterState.multiPolys;
  975. // calculate ringsAfter, windingsAfter
  976. for (let i = 0, iMax = this.rings.length; i < iMax; i++) {
  977. const ring = this.rings[i];
  978. const winding = this.windings[i];
  979. const index = ringsAfter.indexOf(ring);
  980. if (index === -1) {
  981. ringsAfter.push(ring);
  982. windingsAfter.push(winding);
  983. } else windingsAfter[index] += winding;
  984. }
  985. // calcualte polysAfter
  986. const polysAfter = [];
  987. const polysExclude = [];
  988. for (let i = 0, iMax = ringsAfter.length; i < iMax; i++) {
  989. if (windingsAfter[i] === 0) continue; // non-zero rule
  990. const ring = ringsAfter[i];
  991. const poly = ring.poly;
  992. if (polysExclude.indexOf(poly) !== -1) continue;
  993. if (ring.isExterior) polysAfter.push(poly);else {
  994. if (polysExclude.indexOf(poly) === -1) polysExclude.push(poly);
  995. const index = polysAfter.indexOf(ring.poly);
  996. if (index !== -1) polysAfter.splice(index, 1);
  997. }
  998. }
  999. // calculate multiPolysAfter
  1000. for (let i = 0, iMax = polysAfter.length; i < iMax; i++) {
  1001. const mp = polysAfter[i].multiPoly;
  1002. if (mpsAfter.indexOf(mp) === -1) mpsAfter.push(mp);
  1003. }
  1004. return this._afterState;
  1005. }
  1006. /* Is this segment part of the final result? */
  1007. isInResult() {
  1008. // if we've been consumed, we're not in the result
  1009. if (this.consumedBy) return false;
  1010. if (this._isInResult !== undefined) return this._isInResult;
  1011. const mpsBefore = this.beforeState().multiPolys;
  1012. const mpsAfter = this.afterState().multiPolys;
  1013. switch (operation.type) {
  1014. case "union":
  1015. {
  1016. // UNION - included iff:
  1017. // * On one side of us there is 0 poly interiors AND
  1018. // * On the other side there is 1 or more.
  1019. const noBefores = mpsBefore.length === 0;
  1020. const noAfters = mpsAfter.length === 0;
  1021. this._isInResult = noBefores !== noAfters;
  1022. break;
  1023. }
  1024. case "intersection":
  1025. {
  1026. // INTERSECTION - included iff:
  1027. // * on one side of us all multipolys are rep. with poly interiors AND
  1028. // * on the other side of us, not all multipolys are repsented
  1029. // with poly interiors
  1030. let least;
  1031. let most;
  1032. if (mpsBefore.length < mpsAfter.length) {
  1033. least = mpsBefore.length;
  1034. most = mpsAfter.length;
  1035. } else {
  1036. least = mpsAfter.length;
  1037. most = mpsBefore.length;
  1038. }
  1039. this._isInResult = most === operation.numMultiPolys && least < most;
  1040. break;
  1041. }
  1042. case "xor":
  1043. {
  1044. // XOR - included iff:
  1045. // * the difference between the number of multipolys represented
  1046. // with poly interiors on our two sides is an odd number
  1047. const diff = Math.abs(mpsBefore.length - mpsAfter.length);
  1048. this._isInResult = diff % 2 === 1;
  1049. break;
  1050. }
  1051. case "difference":
  1052. {
  1053. // DIFFERENCE included iff:
  1054. // * on exactly one side, we have just the subject
  1055. const isJustSubject = mps => mps.length === 1 && mps[0].isSubject;
  1056. this._isInResult = isJustSubject(mpsBefore) !== isJustSubject(mpsAfter);
  1057. break;
  1058. }
  1059. default:
  1060. throw new Error(`Unrecognized operation type found ${operation.type}`);
  1061. }
  1062. return this._isInResult;
  1063. }
  1064. }
  1065. class RingIn {
  1066. constructor(geomRing, poly, isExterior) {
  1067. if (!Array.isArray(geomRing) || geomRing.length === 0) {
  1068. throw new Error("Input geometry is not a valid Polygon or MultiPolygon");
  1069. }
  1070. this.poly = poly;
  1071. this.isExterior = isExterior;
  1072. this.segments = [];
  1073. if (typeof geomRing[0][0] !== "number" || typeof geomRing[0][1] !== "number") {
  1074. throw new Error("Input geometry is not a valid Polygon or MultiPolygon");
  1075. }
  1076. const firstPoint = rounder.round(geomRing[0][0], geomRing[0][1]);
  1077. this.bbox = {
  1078. ll: {
  1079. x: firstPoint.x,
  1080. y: firstPoint.y
  1081. },
  1082. ur: {
  1083. x: firstPoint.x,
  1084. y: firstPoint.y
  1085. }
  1086. };
  1087. let prevPoint = firstPoint;
  1088. for (let i = 1, iMax = geomRing.length; i < iMax; i++) {
  1089. if (typeof geomRing[i][0] !== "number" || typeof geomRing[i][1] !== "number") {
  1090. throw new Error("Input geometry is not a valid Polygon or MultiPolygon");
  1091. }
  1092. let point = rounder.round(geomRing[i][0], geomRing[i][1]);
  1093. // skip repeated points
  1094. if (point.x === prevPoint.x && point.y === prevPoint.y) continue;
  1095. this.segments.push(Segment.fromRing(prevPoint, point, this));
  1096. if (point.x < this.bbox.ll.x) this.bbox.ll.x = point.x;
  1097. if (point.y < this.bbox.ll.y) this.bbox.ll.y = point.y;
  1098. if (point.x > this.bbox.ur.x) this.bbox.ur.x = point.x;
  1099. if (point.y > this.bbox.ur.y) this.bbox.ur.y = point.y;
  1100. prevPoint = point;
  1101. }
  1102. // add segment from last to first if last is not the same as first
  1103. if (firstPoint.x !== prevPoint.x || firstPoint.y !== prevPoint.y) {
  1104. this.segments.push(Segment.fromRing(prevPoint, firstPoint, this));
  1105. }
  1106. }
  1107. getSweepEvents() {
  1108. const sweepEvents = [];
  1109. for (let i = 0, iMax = this.segments.length; i < iMax; i++) {
  1110. const segment = this.segments[i];
  1111. sweepEvents.push(segment.leftSE);
  1112. sweepEvents.push(segment.rightSE);
  1113. }
  1114. return sweepEvents;
  1115. }
  1116. }
  1117. class PolyIn {
  1118. constructor(geomPoly, multiPoly) {
  1119. if (!Array.isArray(geomPoly)) {
  1120. throw new Error("Input geometry is not a valid Polygon or MultiPolygon");
  1121. }
  1122. this.exteriorRing = new RingIn(geomPoly[0], this, true);
  1123. // copy by value
  1124. this.bbox = {
  1125. ll: {
  1126. x: this.exteriorRing.bbox.ll.x,
  1127. y: this.exteriorRing.bbox.ll.y
  1128. },
  1129. ur: {
  1130. x: this.exteriorRing.bbox.ur.x,
  1131. y: this.exteriorRing.bbox.ur.y
  1132. }
  1133. };
  1134. this.interiorRings = [];
  1135. for (let i = 1, iMax = geomPoly.length; i < iMax; i++) {
  1136. const ring = new RingIn(geomPoly[i], this, false);
  1137. if (ring.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = ring.bbox.ll.x;
  1138. if (ring.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = ring.bbox.ll.y;
  1139. if (ring.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = ring.bbox.ur.x;
  1140. if (ring.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = ring.bbox.ur.y;
  1141. this.interiorRings.push(ring);
  1142. }
  1143. this.multiPoly = multiPoly;
  1144. }
  1145. getSweepEvents() {
  1146. const sweepEvents = this.exteriorRing.getSweepEvents();
  1147. for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) {
  1148. const ringSweepEvents = this.interiorRings[i].getSweepEvents();
  1149. for (let j = 0, jMax = ringSweepEvents.length; j < jMax; j++) {
  1150. sweepEvents.push(ringSweepEvents[j]);
  1151. }
  1152. }
  1153. return sweepEvents;
  1154. }
  1155. }
  1156. class MultiPolyIn {
  1157. constructor(geom, isSubject) {
  1158. if (!Array.isArray(geom)) {
  1159. throw new Error("Input geometry is not a valid Polygon or MultiPolygon");
  1160. }
  1161. try {
  1162. // if the input looks like a polygon, convert it to a multipolygon
  1163. if (typeof geom[0][0][0] === "number") geom = [geom];
  1164. } catch (ex) {
  1165. // The input is either malformed or has empty arrays.
  1166. // In either case, it will be handled later on.
  1167. }
  1168. this.polys = [];
  1169. this.bbox = {
  1170. ll: {
  1171. x: Number.POSITIVE_INFINITY,
  1172. y: Number.POSITIVE_INFINITY
  1173. },
  1174. ur: {
  1175. x: Number.NEGATIVE_INFINITY,
  1176. y: Number.NEGATIVE_INFINITY
  1177. }
  1178. };
  1179. for (let i = 0, iMax = geom.length; i < iMax; i++) {
  1180. const poly = new PolyIn(geom[i], this);
  1181. if (poly.bbox.ll.x < this.bbox.ll.x) this.bbox.ll.x = poly.bbox.ll.x;
  1182. if (poly.bbox.ll.y < this.bbox.ll.y) this.bbox.ll.y = poly.bbox.ll.y;
  1183. if (poly.bbox.ur.x > this.bbox.ur.x) this.bbox.ur.x = poly.bbox.ur.x;
  1184. if (poly.bbox.ur.y > this.bbox.ur.y) this.bbox.ur.y = poly.bbox.ur.y;
  1185. this.polys.push(poly);
  1186. }
  1187. this.isSubject = isSubject;
  1188. }
  1189. getSweepEvents() {
  1190. const sweepEvents = [];
  1191. for (let i = 0, iMax = this.polys.length; i < iMax; i++) {
  1192. const polySweepEvents = this.polys[i].getSweepEvents();
  1193. for (let j = 0, jMax = polySweepEvents.length; j < jMax; j++) {
  1194. sweepEvents.push(polySweepEvents[j]);
  1195. }
  1196. }
  1197. return sweepEvents;
  1198. }
  1199. }
  1200. class RingOut {
  1201. /* Given the segments from the sweep line pass, compute & return a series
  1202. * of closed rings from all the segments marked to be part of the result */
  1203. static factory(allSegments) {
  1204. const ringsOut = [];
  1205. for (let i = 0, iMax = allSegments.length; i < iMax; i++) {
  1206. const segment = allSegments[i];
  1207. if (!segment.isInResult() || segment.ringOut) continue;
  1208. let prevEvent = null;
  1209. let event = segment.leftSE;
  1210. let nextEvent = segment.rightSE;
  1211. const events = [event];
  1212. const startingPoint = event.point;
  1213. const intersectionLEs = [];
  1214. /* Walk the chain of linked events to form a closed ring */
  1215. while (true) {
  1216. prevEvent = event;
  1217. event = nextEvent;
  1218. events.push(event);
  1219. /* Is the ring complete? */
  1220. if (event.point === startingPoint) break;
  1221. while (true) {
  1222. const availableLEs = event.getAvailableLinkedEvents();
  1223. /* Did we hit a dead end? This shouldn't happen.
  1224. * Indicates some earlier part of the algorithm malfunctioned. */
  1225. if (availableLEs.length === 0) {
  1226. const firstPt = events[0].point;
  1227. const lastPt = events[events.length - 1].point;
  1228. throw new Error(`Unable to complete output ring starting at [${firstPt.x},` + ` ${firstPt.y}]. Last matching segment found ends at` + ` [${lastPt.x}, ${lastPt.y}].`);
  1229. }
  1230. /* Only one way to go, so cotinue on the path */
  1231. if (availableLEs.length === 1) {
  1232. nextEvent = availableLEs[0].otherSE;
  1233. break;
  1234. }
  1235. /* We must have an intersection. Check for a completed loop */
  1236. let indexLE = null;
  1237. for (let j = 0, jMax = intersectionLEs.length; j < jMax; j++) {
  1238. if (intersectionLEs[j].point === event.point) {
  1239. indexLE = j;
  1240. break;
  1241. }
  1242. }
  1243. /* Found a completed loop. Cut that off and make a ring */
  1244. if (indexLE !== null) {
  1245. const intersectionLE = intersectionLEs.splice(indexLE)[0];
  1246. const ringEvents = events.splice(intersectionLE.index);
  1247. ringEvents.unshift(ringEvents[0].otherSE);
  1248. ringsOut.push(new RingOut(ringEvents.reverse()));
  1249. continue;
  1250. }
  1251. /* register the intersection */
  1252. intersectionLEs.push({
  1253. index: events.length,
  1254. point: event.point
  1255. });
  1256. /* Choose the left-most option to continue the walk */
  1257. const comparator = event.getLeftmostComparator(prevEvent);
  1258. nextEvent = availableLEs.sort(comparator)[0].otherSE;
  1259. break;
  1260. }
  1261. }
  1262. ringsOut.push(new RingOut(events));
  1263. }
  1264. return ringsOut;
  1265. }
  1266. constructor(events) {
  1267. this.events = events;
  1268. for (let i = 0, iMax = events.length; i < iMax; i++) {
  1269. events[i].segment.ringOut = this;
  1270. }
  1271. this.poly = null;
  1272. }
  1273. getGeom() {
  1274. // Remove superfluous points (ie extra points along a straight line),
  1275. let prevPt = this.events[0].point;
  1276. const points = [prevPt];
  1277. for (let i = 1, iMax = this.events.length - 1; i < iMax; i++) {
  1278. const pt = this.events[i].point;
  1279. const nextPt = this.events[i + 1].point;
  1280. if (compareVectorAngles(pt, prevPt, nextPt) === 0) continue;
  1281. points.push(pt);
  1282. prevPt = pt;
  1283. }
  1284. // ring was all (within rounding error of angle calc) colinear points
  1285. if (points.length === 1) return null;
  1286. // check if the starting point is necessary
  1287. const pt = points[0];
  1288. const nextPt = points[1];
  1289. if (compareVectorAngles(pt, prevPt, nextPt) === 0) points.shift();
  1290. points.push(points[0]);
  1291. const step = this.isExteriorRing() ? 1 : -1;
  1292. const iStart = this.isExteriorRing() ? 0 : points.length - 1;
  1293. const iEnd = this.isExteriorRing() ? points.length : -1;
  1294. const orderedPoints = [];
  1295. for (let i = iStart; i != iEnd; i += step) orderedPoints.push([points[i].x, points[i].y]);
  1296. return orderedPoints;
  1297. }
  1298. isExteriorRing() {
  1299. if (this._isExteriorRing === undefined) {
  1300. const enclosing = this.enclosingRing();
  1301. this._isExteriorRing = enclosing ? !enclosing.isExteriorRing() : true;
  1302. }
  1303. return this._isExteriorRing;
  1304. }
  1305. enclosingRing() {
  1306. if (this._enclosingRing === undefined) {
  1307. this._enclosingRing = this._calcEnclosingRing();
  1308. }
  1309. return this._enclosingRing;
  1310. }
  1311. /* Returns the ring that encloses this one, if any */
  1312. _calcEnclosingRing() {
  1313. // start with the ealier sweep line event so that the prevSeg
  1314. // chain doesn't lead us inside of a loop of ours
  1315. let leftMostEvt = this.events[0];
  1316. for (let i = 1, iMax = this.events.length; i < iMax; i++) {
  1317. const evt = this.events[i];
  1318. if (SweepEvent.compare(leftMostEvt, evt) > 0) leftMostEvt = evt;
  1319. }
  1320. let prevSeg = leftMostEvt.segment.prevInResult();
  1321. let prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null;
  1322. while (true) {
  1323. // no segment found, thus no ring can enclose us
  1324. if (!prevSeg) return null;
  1325. // no segments below prev segment found, thus the ring of the prev
  1326. // segment must loop back around and enclose us
  1327. if (!prevPrevSeg) return prevSeg.ringOut;
  1328. // if the two segments are of different rings, the ring of the prev
  1329. // segment must either loop around us or the ring of the prev prev
  1330. // seg, which would make us and the ring of the prev peers
  1331. if (prevPrevSeg.ringOut !== prevSeg.ringOut) {
  1332. if (prevPrevSeg.ringOut.enclosingRing() !== prevSeg.ringOut) {
  1333. return prevSeg.ringOut;
  1334. } else return prevSeg.ringOut.enclosingRing();
  1335. }
  1336. // two segments are from the same ring, so this was a penisula
  1337. // of that ring. iterate downward, keep searching
  1338. prevSeg = prevPrevSeg.prevInResult();
  1339. prevPrevSeg = prevSeg ? prevSeg.prevInResult() : null;
  1340. }
  1341. }
  1342. }
  1343. class PolyOut {
  1344. constructor(exteriorRing) {
  1345. this.exteriorRing = exteriorRing;
  1346. exteriorRing.poly = this;
  1347. this.interiorRings = [];
  1348. }
  1349. addInterior(ring) {
  1350. this.interiorRings.push(ring);
  1351. ring.poly = this;
  1352. }
  1353. getGeom() {
  1354. const geom = [this.exteriorRing.getGeom()];
  1355. // exterior ring was all (within rounding error of angle calc) colinear points
  1356. if (geom[0] === null) return null;
  1357. for (let i = 0, iMax = this.interiorRings.length; i < iMax; i++) {
  1358. const ringGeom = this.interiorRings[i].getGeom();
  1359. // interior ring was all (within rounding error of angle calc) colinear points
  1360. if (ringGeom === null) continue;
  1361. geom.push(ringGeom);
  1362. }
  1363. return geom;
  1364. }
  1365. }
  1366. class MultiPolyOut {
  1367. constructor(rings) {
  1368. this.rings = rings;
  1369. this.polys = this._composePolys(rings);
  1370. }
  1371. getGeom() {
  1372. const geom = [];
  1373. for (let i = 0, iMax = this.polys.length; i < iMax; i++) {
  1374. const polyGeom = this.polys[i].getGeom();
  1375. // exterior ring was all (within rounding error of angle calc) colinear points
  1376. if (polyGeom === null) continue;
  1377. geom.push(polyGeom);
  1378. }
  1379. return geom;
  1380. }
  1381. _composePolys(rings) {
  1382. const polys = [];
  1383. for (let i = 0, iMax = rings.length; i < iMax; i++) {
  1384. const ring = rings[i];
  1385. if (ring.poly) continue;
  1386. if (ring.isExteriorRing()) polys.push(new PolyOut(ring));else {
  1387. const enclosingRing = ring.enclosingRing();
  1388. if (!enclosingRing.poly) polys.push(new PolyOut(enclosingRing));
  1389. enclosingRing.poly.addInterior(ring);
  1390. }
  1391. }
  1392. return polys;
  1393. }
  1394. }
  1395. /**
  1396. * NOTE: We must be careful not to change any segments while
  1397. * they are in the SplayTree. AFAIK, there's no way to tell
  1398. * the tree to rebalance itself - thus before splitting
  1399. * a segment that's in the tree, we remove it from the tree,
  1400. * do the split, then re-insert it. (Even though splitting a
  1401. * segment *shouldn't* change its correct position in the
  1402. * sweep line tree, the reality is because of rounding errors,
  1403. * it sometimes does.)
  1404. */
  1405. class SweepLine {
  1406. constructor(queue) {
  1407. let comparator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Segment.compare;
  1408. this.queue = queue;
  1409. this.tree = new SplayTree__default["default"](comparator);
  1410. this.segments = [];
  1411. }
  1412. process(event) {
  1413. const segment = event.segment;
  1414. const newEvents = [];
  1415. // if we've already been consumed by another segment,
  1416. // clean up our body parts and get out
  1417. if (event.consumedBy) {
  1418. if (event.isLeft) this.queue.remove(event.otherSE);else this.tree.remove(segment);
  1419. return newEvents;
  1420. }
  1421. const node = event.isLeft ? this.tree.add(segment) : this.tree.find(segment);
  1422. if (!node) throw new Error(`Unable to find segment #${segment.id} ` + `[${segment.leftSE.point.x}, ${segment.leftSE.point.y}] -> ` + `[${segment.rightSE.point.x}, ${segment.rightSE.point.y}] ` + "in SweepLine tree.");
  1423. let prevNode = node;
  1424. let nextNode = node;
  1425. let prevSeg = undefined;
  1426. let nextSeg = undefined;
  1427. // skip consumed segments still in tree
  1428. while (prevSeg === undefined) {
  1429. prevNode = this.tree.prev(prevNode);
  1430. if (prevNode === null) prevSeg = null;else if (prevNode.key.consumedBy === undefined) prevSeg = prevNode.key;
  1431. }
  1432. // skip consumed segments still in tree
  1433. while (nextSeg === undefined) {
  1434. nextNode = this.tree.next(nextNode);
  1435. if (nextNode === null) nextSeg = null;else if (nextNode.key.consumedBy === undefined) nextSeg = nextNode.key;
  1436. }
  1437. if (event.isLeft) {
  1438. // Check for intersections against the previous segment in the sweep line
  1439. let prevMySplitter = null;
  1440. if (prevSeg) {
  1441. const prevInter = prevSeg.getIntersection(segment);
  1442. if (prevInter !== null) {
  1443. if (!segment.isAnEndpoint(prevInter)) prevMySplitter = prevInter;
  1444. if (!prevSeg.isAnEndpoint(prevInter)) {
  1445. const newEventsFromSplit = this._splitSafely(prevSeg, prevInter);
  1446. for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {
  1447. newEvents.push(newEventsFromSplit[i]);
  1448. }
  1449. }
  1450. }
  1451. }
  1452. // Check for intersections against the next segment in the sweep line
  1453. let nextMySplitter = null;
  1454. if (nextSeg) {
  1455. const nextInter = nextSeg.getIntersection(segment);
  1456. if (nextInter !== null) {
  1457. if (!segment.isAnEndpoint(nextInter)) nextMySplitter = nextInter;
  1458. if (!nextSeg.isAnEndpoint(nextInter)) {
  1459. const newEventsFromSplit = this._splitSafely(nextSeg, nextInter);
  1460. for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {
  1461. newEvents.push(newEventsFromSplit[i]);
  1462. }
  1463. }
  1464. }
  1465. }
  1466. // For simplicity, even if we find more than one intersection we only
  1467. // spilt on the 'earliest' (sweep-line style) of the intersections.
  1468. // The other intersection will be handled in a future process().
  1469. if (prevMySplitter !== null || nextMySplitter !== null) {
  1470. let mySplitter = null;
  1471. if (prevMySplitter === null) mySplitter = nextMySplitter;else if (nextMySplitter === null) mySplitter = prevMySplitter;else {
  1472. const cmpSplitters = SweepEvent.comparePoints(prevMySplitter, nextMySplitter);
  1473. mySplitter = cmpSplitters <= 0 ? prevMySplitter : nextMySplitter;
  1474. }
  1475. // Rounding errors can cause changes in ordering,
  1476. // so remove afected segments and right sweep events before splitting
  1477. this.queue.remove(segment.rightSE);
  1478. newEvents.push(segment.rightSE);
  1479. const newEventsFromSplit = segment.split(mySplitter);
  1480. for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {
  1481. newEvents.push(newEventsFromSplit[i]);
  1482. }
  1483. }
  1484. if (newEvents.length > 0) {
  1485. // We found some intersections, so re-do the current event to
  1486. // make sure sweep line ordering is totally consistent for later
  1487. // use with the segment 'prev' pointers
  1488. this.tree.remove(segment);
  1489. newEvents.push(event);
  1490. } else {
  1491. // done with left event
  1492. this.segments.push(segment);
  1493. segment.prev = prevSeg;
  1494. }
  1495. } else {
  1496. // event.isRight
  1497. // since we're about to be removed from the sweep line, check for
  1498. // intersections between our previous and next segments
  1499. if (prevSeg && nextSeg) {
  1500. const inter = prevSeg.getIntersection(nextSeg);
  1501. if (inter !== null) {
  1502. if (!prevSeg.isAnEndpoint(inter)) {
  1503. const newEventsFromSplit = this._splitSafely(prevSeg, inter);
  1504. for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {
  1505. newEvents.push(newEventsFromSplit[i]);
  1506. }
  1507. }
  1508. if (!nextSeg.isAnEndpoint(inter)) {
  1509. const newEventsFromSplit = this._splitSafely(nextSeg, inter);
  1510. for (let i = 0, iMax = newEventsFromSplit.length; i < iMax; i++) {
  1511. newEvents.push(newEventsFromSplit[i]);
  1512. }
  1513. }
  1514. }
  1515. }
  1516. this.tree.remove(segment);
  1517. }
  1518. return newEvents;
  1519. }
  1520. /* Safely split a segment that is currently in the datastructures
  1521. * IE - a segment other than the one that is currently being processed. */
  1522. _splitSafely(seg, pt) {
  1523. // Rounding errors can cause changes in ordering,
  1524. // so remove afected segments and right sweep events before splitting
  1525. // removeNode() doesn't work, so have re-find the seg
  1526. // https://github.com/w8r/splay-tree/pull/5
  1527. this.tree.remove(seg);
  1528. const rightSE = seg.rightSE;
  1529. this.queue.remove(rightSE);
  1530. const newEvents = seg.split(pt);
  1531. newEvents.push(rightSE);
  1532. // splitting can trigger consumption
  1533. if (seg.consumedBy === undefined) this.tree.add(seg);
  1534. return newEvents;
  1535. }
  1536. }
  1537. // Limits on iterative processes to prevent infinite loops - usually caused by floating-point math round-off errors.
  1538. const POLYGON_CLIPPING_MAX_QUEUE_SIZE = typeof process !== "undefined" && process.env.POLYGON_CLIPPING_MAX_QUEUE_SIZE || 1000000;
  1539. const POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS = typeof process !== "undefined" && process.env.POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS || 1000000;
  1540. class Operation {
  1541. run(type, geom, moreGeoms) {
  1542. operation.type = type;
  1543. rounder.reset();
  1544. /* Convert inputs to MultiPoly objects */
  1545. const multipolys = [new MultiPolyIn(geom, true)];
  1546. for (let i = 0, iMax = moreGeoms.length; i < iMax; i++) {
  1547. multipolys.push(new MultiPolyIn(moreGeoms[i], false));
  1548. }
  1549. operation.numMultiPolys = multipolys.length;
  1550. /* BBox optimization for difference operation
  1551. * If the bbox of a multipolygon that's part of the clipping doesn't
  1552. * intersect the bbox of the subject at all, we can just drop that
  1553. * multiploygon. */
  1554. if (operation.type === "difference") {
  1555. // in place removal
  1556. const subject = multipolys[0];
  1557. let i = 1;
  1558. while (i < multipolys.length) {
  1559. if (getBboxOverlap(multipolys[i].bbox, subject.bbox) !== null) i++;else multipolys.splice(i, 1);
  1560. }
  1561. }
  1562. /* BBox optimization for intersection operation
  1563. * If we can find any pair of multipolygons whose bbox does not overlap,
  1564. * then the result will be empty. */
  1565. if (operation.type === "intersection") {
  1566. // TODO: this is O(n^2) in number of polygons. By sorting the bboxes,
  1567. // it could be optimized to O(n * ln(n))
  1568. for (let i = 0, iMax = multipolys.length; i < iMax; i++) {
  1569. const mpA = multipolys[i];
  1570. for (let j = i + 1, jMax = multipolys.length; j < jMax; j++) {
  1571. if (getBboxOverlap(mpA.bbox, multipolys[j].bbox) === null) return [];
  1572. }
  1573. }
  1574. }
  1575. /* Put segment endpoints in a priority queue */
  1576. const queue = new SplayTree__default["default"](SweepEvent.compare);
  1577. for (let i = 0, iMax = multipolys.length; i < iMax; i++) {
  1578. const sweepEvents = multipolys[i].getSweepEvents();
  1579. for (let j = 0, jMax = sweepEvents.length; j < jMax; j++) {
  1580. queue.insert(sweepEvents[j]);
  1581. if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) {
  1582. // prevents an infinite loop, an otherwise common manifestation of bugs
  1583. throw new Error("Infinite loop when putting segment endpoints in a priority queue " + "(queue size too big).");
  1584. }
  1585. }
  1586. }
  1587. /* Pass the sweep line over those endpoints */
  1588. const sweepLine = new SweepLine(queue);
  1589. let prevQueueSize = queue.size;
  1590. let node = queue.pop();
  1591. while (node) {
  1592. const evt = node.key;
  1593. if (queue.size === prevQueueSize) {
  1594. // prevents an infinite loop, an otherwise common manifestation of bugs
  1595. const seg = evt.segment;
  1596. throw new Error(`Unable to pop() ${evt.isLeft ? "left" : "right"} SweepEvent ` + `[${evt.point.x}, ${evt.point.y}] from segment #${seg.id} ` + `[${seg.leftSE.point.x}, ${seg.leftSE.point.y}] -> ` + `[${seg.rightSE.point.x}, ${seg.rightSE.point.y}] from queue.`);
  1597. }
  1598. if (queue.size > POLYGON_CLIPPING_MAX_QUEUE_SIZE) {
  1599. // prevents an infinite loop, an otherwise common manifestation of bugs
  1600. throw new Error("Infinite loop when passing sweep line over endpoints " + "(queue size too big).");
  1601. }
  1602. if (sweepLine.segments.length > POLYGON_CLIPPING_MAX_SWEEPLINE_SEGMENTS) {
  1603. // prevents an infinite loop, an otherwise common manifestation of bugs
  1604. throw new Error("Infinite loop when passing sweep line over endpoints " + "(too many sweep line segments).");
  1605. }
  1606. const newEvents = sweepLine.process(evt);
  1607. for (let i = 0, iMax = newEvents.length; i < iMax; i++) {
  1608. const evt = newEvents[i];
  1609. if (evt.consumedBy === undefined) queue.insert(evt);
  1610. }
  1611. prevQueueSize = queue.size;
  1612. node = queue.pop();
  1613. }
  1614. // free some memory we don't need anymore
  1615. rounder.reset();
  1616. /* Collect and compile segments we're keeping into a multipolygon */
  1617. const ringsOut = RingOut.factory(sweepLine.segments);
  1618. const result = new MultiPolyOut(ringsOut);
  1619. return result.getGeom();
  1620. }
  1621. }
  1622. // singleton available by import
  1623. const operation = new Operation();
  1624. const union = function (geom) {
  1625. for (var _len = arguments.length, moreGeoms = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  1626. moreGeoms[_key - 1] = arguments[_key];
  1627. }
  1628. return operation.run("union", geom, moreGeoms);
  1629. };
  1630. const intersection = function (geom) {
  1631. for (var _len2 = arguments.length, moreGeoms = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
  1632. moreGeoms[_key2 - 1] = arguments[_key2];
  1633. }
  1634. return operation.run("intersection", geom, moreGeoms);
  1635. };
  1636. const xor = function (geom) {
  1637. for (var _len3 = arguments.length, moreGeoms = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
  1638. moreGeoms[_key3 - 1] = arguments[_key3];
  1639. }
  1640. return operation.run("xor", geom, moreGeoms);
  1641. };
  1642. const difference = function (subjectGeom) {
  1643. for (var _len4 = arguments.length, clippingGeoms = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
  1644. clippingGeoms[_key4 - 1] = arguments[_key4];
  1645. }
  1646. return operation.run("difference", subjectGeom, clippingGeoms);
  1647. };
  1648. var index = {
  1649. union: union,
  1650. intersection: intersection,
  1651. xor: xor,
  1652. difference: difference
  1653. };
  1654. module.exports = index;