index.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. // http://en.wikipedia.org/wiki/Delaunay_triangulation
  2. // https://github.com/ironwallaby/delaunay
  3. import { featureCollection, polygon } from "@turf/helpers";
  4. /**
  5. * Takes a set of {@link Point|points} and creates a
  6. * [Triangulated Irregular Network](http://en.wikipedia.org/wiki/Triangulated_irregular_network),
  7. * or a TIN for short, returned as a collection of Polygons. These are often used
  8. * for developing elevation contour maps or stepped heat visualizations.
  9. *
  10. * If an optional z-value property is provided then it is added as properties called `a`, `b`,
  11. * and `c` representing its value at each of the points that represent the corners of the
  12. * triangle.
  13. *
  14. * @name tin
  15. * @param {FeatureCollection<Point>} points input points
  16. * @param {String} [z] name of the property from which to pull z values
  17. * This is optional: if not given, then there will be no extra data added to the derived triangles.
  18. * @returns {FeatureCollection<Polygon>} TIN output
  19. * @example
  20. * // generate some random point data
  21. * var points = turf.randomPoint(30, {bbox: [50, 30, 70, 50]});
  22. *
  23. * // add a random property to each point between 0 and 9
  24. * for (var i = 0; i < points.features.length; i++) {
  25. * points.features[i].properties.z = ~~(Math.random() * 9);
  26. * }
  27. * var tin = turf.tin(points, 'z');
  28. *
  29. * //addToMap
  30. * var addToMap = [tin, points]
  31. * for (var i = 0; i < tin.features.length; i++) {
  32. * var properties = tin.features[i].properties;
  33. * properties.fill = '#' + properties.a + properties.b + properties.c;
  34. * }
  35. */
  36. export default function tin(points, z) {
  37. // break down points
  38. var isPointZ = false;
  39. return featureCollection(triangulate(points.features.map(function (p) {
  40. var point = {
  41. x: p.geometry.coordinates[0],
  42. y: p.geometry.coordinates[1],
  43. };
  44. if (z) {
  45. point.z = p.properties[z];
  46. }
  47. else if (p.geometry.coordinates.length === 3) {
  48. isPointZ = true;
  49. point.z = p.geometry.coordinates[2];
  50. }
  51. return point;
  52. })).map(function (triangle) {
  53. var a = [triangle.a.x, triangle.a.y];
  54. var b = [triangle.b.x, triangle.b.y];
  55. var c = [triangle.c.x, triangle.c.y];
  56. var properties = {};
  57. // Add z coordinates to triangle points if user passed
  58. // them in that way otherwise add it as a property.
  59. if (isPointZ) {
  60. a.push(triangle.a.z);
  61. b.push(triangle.b.z);
  62. c.push(triangle.c.z);
  63. }
  64. else {
  65. properties = {
  66. a: triangle.a.z,
  67. b: triangle.b.z,
  68. c: triangle.c.z,
  69. };
  70. }
  71. return polygon([[a, b, c, a]], properties);
  72. }));
  73. }
  74. var Triangle = /** @class */ (function () {
  75. function Triangle(a, b, c) {
  76. this.a = a;
  77. this.b = b;
  78. this.c = c;
  79. var A = b.x - a.x;
  80. var B = b.y - a.y;
  81. var C = c.x - a.x;
  82. var D = c.y - a.y;
  83. var E = A * (a.x + b.x) + B * (a.y + b.y);
  84. var F = C * (a.x + c.x) + D * (a.y + c.y);
  85. var G = 2 * (A * (c.y - b.y) - B * (c.x - b.x));
  86. var dx;
  87. var dy;
  88. // If the points of the triangle are collinear, then just find the
  89. // extremes and use the midpoint as the center of the circumcircle.
  90. this.x = (D * E - B * F) / G;
  91. this.y = (A * F - C * E) / G;
  92. dx = this.x - a.x;
  93. dy = this.y - a.y;
  94. this.r = dx * dx + dy * dy;
  95. }
  96. return Triangle;
  97. }());
  98. function byX(a, b) {
  99. return b.x - a.x;
  100. }
  101. function dedup(edges) {
  102. var j = edges.length;
  103. var a;
  104. var b;
  105. var i;
  106. var m;
  107. var n;
  108. outer: while (j) {
  109. b = edges[--j];
  110. a = edges[--j];
  111. i = j;
  112. while (i) {
  113. n = edges[--i];
  114. m = edges[--i];
  115. if ((a === m && b === n) || (a === n && b === m)) {
  116. edges.splice(j, 2);
  117. edges.splice(i, 2);
  118. j -= 2;
  119. continue outer;
  120. }
  121. }
  122. }
  123. }
  124. function triangulate(vertices) {
  125. // Bail if there aren't enough vertices to form any triangles.
  126. if (vertices.length < 3) {
  127. return [];
  128. }
  129. // Ensure the vertex array is in order of descending X coordinate
  130. // (which is needed to ensure a subquadratic runtime), and then find
  131. // the bounding box around the points.
  132. vertices.sort(byX);
  133. var i = vertices.length - 1;
  134. var xmin = vertices[i].x;
  135. var xmax = vertices[0].x;
  136. var ymin = vertices[i].y;
  137. var ymax = ymin;
  138. var epsilon = 1e-12;
  139. var a;
  140. var b;
  141. var c;
  142. var A;
  143. var B;
  144. var G;
  145. while (i--) {
  146. if (vertices[i].y < ymin) {
  147. ymin = vertices[i].y;
  148. }
  149. if (vertices[i].y > ymax) {
  150. ymax = vertices[i].y;
  151. }
  152. }
  153. // Find a supertriangle, which is a triangle that surrounds all the
  154. // vertices. This is used like something of a sentinel value to remove
  155. // cases in the main algorithm, and is removed before we return any
  156. // results.
  157. // Once found, put it in the "open" list. (The "open" list is for
  158. // triangles who may still need to be considered; the "closed" list is
  159. // for triangles which do not.)
  160. var dx = xmax - xmin;
  161. var dy = ymax - ymin;
  162. var dmax = dx > dy ? dx : dy;
  163. var xmid = (xmax + xmin) * 0.5;
  164. var ymid = (ymax + ymin) * 0.5;
  165. var open = [
  166. new Triangle({
  167. __sentinel: true,
  168. x: xmid - 20 * dmax,
  169. y: ymid - dmax,
  170. }, {
  171. __sentinel: true,
  172. x: xmid,
  173. y: ymid + 20 * dmax,
  174. }, {
  175. __sentinel: true,
  176. x: xmid + 20 * dmax,
  177. y: ymid - dmax,
  178. }),
  179. ];
  180. var closed = [];
  181. var edges = [];
  182. var j;
  183. // Incrementally add each vertex to the mesh.
  184. i = vertices.length;
  185. while (i--) {
  186. // For each open triangle, check to see if the current point is
  187. // inside it's circumcircle. If it is, remove the triangle and add
  188. // it's edges to an edge list.
  189. edges.length = 0;
  190. j = open.length;
  191. while (j--) {
  192. // If this point is to the right of this triangle's circumcircle,
  193. // then this triangle should never get checked again. Remove it
  194. // from the open list, add it to the closed list, and skip.
  195. dx = vertices[i].x - open[j].x;
  196. if (dx > 0 && dx * dx > open[j].r) {
  197. closed.push(open[j]);
  198. open.splice(j, 1);
  199. continue;
  200. }
  201. // If not, skip this triangle.
  202. dy = vertices[i].y - open[j].y;
  203. if (dx * dx + dy * dy > open[j].r) {
  204. continue;
  205. }
  206. // Remove the triangle and add it's edges to the edge list.
  207. edges.push(open[j].a, open[j].b, open[j].b, open[j].c, open[j].c, open[j].a);
  208. open.splice(j, 1);
  209. }
  210. // Remove any doubled edges.
  211. dedup(edges);
  212. // Add a new triangle for each edge.
  213. j = edges.length;
  214. while (j) {
  215. b = edges[--j];
  216. a = edges[--j];
  217. c = vertices[i];
  218. // Avoid adding colinear triangles (which have error-prone
  219. // circumcircles)
  220. A = b.x - a.x;
  221. B = b.y - a.y;
  222. G = 2 * (A * (c.y - b.y) - B * (c.x - b.x));
  223. if (Math.abs(G) > epsilon) {
  224. open.push(new Triangle(a, b, c));
  225. }
  226. }
  227. }
  228. // Copy any remaining open triangles to the closed list, and then
  229. // remove any triangles that share a vertex with the supertriangle.
  230. Array.prototype.push.apply(closed, open);
  231. i = closed.length;
  232. while (i--) {
  233. if (closed[i].a.__sentinel ||
  234. closed[i].b.__sentinel ||
  235. closed[i].c.__sentinel) {
  236. closed.splice(i, 1);
  237. }
  238. }
  239. return closed;
  240. }