dedup.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import hashmap from "./hash/hashmap.js";
  2. import equalPoint from "./hash/point-equal.js";
  3. import hashPoint from "./hash/point-hash.js";
  4. // Given a cut topology, combines duplicate arcs.
  5. export default function(topology) {
  6. var coordinates = topology.coordinates,
  7. lines = topology.lines, line,
  8. rings = topology.rings, ring,
  9. arcCount = lines.length + rings.length,
  10. i, n;
  11. delete topology.lines;
  12. delete topology.rings;
  13. // Count the number of (non-unique) arcs to initialize the hashmap safely.
  14. for (i = 0, n = lines.length; i < n; ++i) {
  15. line = lines[i]; while (line = line.next) ++arcCount;
  16. }
  17. for (i = 0, n = rings.length; i < n; ++i) {
  18. ring = rings[i]; while (ring = ring.next) ++arcCount;
  19. }
  20. var arcsByEnd = hashmap(arcCount * 2 * 1.4, hashPoint, equalPoint),
  21. arcs = topology.arcs = [];
  22. for (i = 0, n = lines.length; i < n; ++i) {
  23. line = lines[i];
  24. do {
  25. dedupLine(line);
  26. } while (line = line.next);
  27. }
  28. for (i = 0, n = rings.length; i < n; ++i) {
  29. ring = rings[i];
  30. if (ring.next) { // arc is no longer closed
  31. do {
  32. dedupLine(ring);
  33. } while (ring = ring.next);
  34. } else {
  35. dedupRing(ring);
  36. }
  37. }
  38. function dedupLine(arc) {
  39. var startPoint,
  40. endPoint,
  41. startArcs, startArc,
  42. endArcs, endArc,
  43. i, n;
  44. // Does this arc match an existing arc in order?
  45. if (startArcs = arcsByEnd.get(startPoint = coordinates[arc[0]])) {
  46. for (i = 0, n = startArcs.length; i < n; ++i) {
  47. startArc = startArcs[i];
  48. if (equalLine(startArc, arc)) {
  49. arc[0] = startArc[0];
  50. arc[1] = startArc[1];
  51. return;
  52. }
  53. }
  54. }
  55. // Does this arc match an existing arc in reverse order?
  56. if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[1]])) {
  57. for (i = 0, n = endArcs.length; i < n; ++i) {
  58. endArc = endArcs[i];
  59. if (reverseEqualLine(endArc, arc)) {
  60. arc[1] = endArc[0];
  61. arc[0] = endArc[1];
  62. return;
  63. }
  64. }
  65. }
  66. if (startArcs) startArcs.push(arc); else arcsByEnd.set(startPoint, [arc]);
  67. if (endArcs) endArcs.push(arc); else arcsByEnd.set(endPoint, [arc]);
  68. arcs.push(arc);
  69. }
  70. function dedupRing(arc) {
  71. var endPoint,
  72. endArcs,
  73. endArc,
  74. i, n;
  75. // Does this arc match an existing line in order, or reverse order?
  76. // Rings are closed, so their start point and end point is the same.
  77. if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0]])) {
  78. for (i = 0, n = endArcs.length; i < n; ++i) {
  79. endArc = endArcs[i];
  80. if (equalRing(endArc, arc)) {
  81. arc[0] = endArc[0];
  82. arc[1] = endArc[1];
  83. return;
  84. }
  85. if (reverseEqualRing(endArc, arc)) {
  86. arc[0] = endArc[1];
  87. arc[1] = endArc[0];
  88. return;
  89. }
  90. }
  91. }
  92. // Otherwise, does this arc match an existing ring in order, or reverse order?
  93. if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0] + findMinimumOffset(arc)])) {
  94. for (i = 0, n = endArcs.length; i < n; ++i) {
  95. endArc = endArcs[i];
  96. if (equalRing(endArc, arc)) {
  97. arc[0] = endArc[0];
  98. arc[1] = endArc[1];
  99. return;
  100. }
  101. if (reverseEqualRing(endArc, arc)) {
  102. arc[0] = endArc[1];
  103. arc[1] = endArc[0];
  104. return;
  105. }
  106. }
  107. }
  108. if (endArcs) endArcs.push(arc); else arcsByEnd.set(endPoint, [arc]);
  109. arcs.push(arc);
  110. }
  111. function equalLine(arcA, arcB) {
  112. var ia = arcA[0], ib = arcB[0],
  113. ja = arcA[1], jb = arcB[1];
  114. if (ia - ja !== ib - jb) return false;
  115. for (; ia <= ja; ++ia, ++ib) if (!equalPoint(coordinates[ia], coordinates[ib])) return false;
  116. return true;
  117. }
  118. function reverseEqualLine(arcA, arcB) {
  119. var ia = arcA[0], ib = arcB[0],
  120. ja = arcA[1], jb = arcB[1];
  121. if (ia - ja !== ib - jb) return false;
  122. for (; ia <= ja; ++ia, --jb) if (!equalPoint(coordinates[ia], coordinates[jb])) return false;
  123. return true;
  124. }
  125. function equalRing(arcA, arcB) {
  126. var ia = arcA[0], ib = arcB[0],
  127. ja = arcA[1], jb = arcB[1],
  128. n = ja - ia;
  129. if (n !== jb - ib) return false;
  130. var ka = findMinimumOffset(arcA),
  131. kb = findMinimumOffset(arcB);
  132. for (var i = 0; i < n; ++i) {
  133. if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[ib + (i + kb) % n])) return false;
  134. }
  135. return true;
  136. }
  137. function reverseEqualRing(arcA, arcB) {
  138. var ia = arcA[0], ib = arcB[0],
  139. ja = arcA[1], jb = arcB[1],
  140. n = ja - ia;
  141. if (n !== jb - ib) return false;
  142. var ka = findMinimumOffset(arcA),
  143. kb = n - findMinimumOffset(arcB);
  144. for (var i = 0; i < n; ++i) {
  145. if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[jb - (i + kb) % n])) return false;
  146. }
  147. return true;
  148. }
  149. // Rings are rotated to a consistent, but arbitrary, start point.
  150. // This is necessary to detect when a ring and a rotated copy are dupes.
  151. function findMinimumOffset(arc) {
  152. var start = arc[0],
  153. end = arc[1],
  154. mid = start,
  155. minimum = mid,
  156. minimumPoint = coordinates[mid];
  157. while (++mid < end) {
  158. var point = coordinates[mid];
  159. if (point[0] < minimumPoint[0] || point[0] === minimumPoint[0] && point[1] < minimumPoint[1]) {
  160. minimum = mid;
  161. minimumPoint = point;
  162. }
  163. }
  164. return minimum - start;
  165. }
  166. return topology;
  167. }