244 lines
7.9 KiB
JavaScript
Executable File
244 lines
7.9 KiB
JavaScript
Executable File
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
// http://en.wikipedia.org/wiki/Delaunay_triangulation
|
|
// https://github.com/ironwallaby/delaunay
|
|
var helpers_1 = require("@turf/helpers");
|
|
/**
|
|
* Takes a set of {@link Point|points} and creates a
|
|
* [Triangulated Irregular Network](http://en.wikipedia.org/wiki/Triangulated_irregular_network),
|
|
* or a TIN for short, returned as a collection of Polygons. These are often used
|
|
* for developing elevation contour maps or stepped heat visualizations.
|
|
*
|
|
* If an optional z-value property is provided then it is added as properties called `a`, `b`,
|
|
* and `c` representing its value at each of the points that represent the corners of the
|
|
* triangle.
|
|
*
|
|
* @name tin
|
|
* @param {FeatureCollection<Point>} points input points
|
|
* @param {String} [z] name of the property from which to pull z values
|
|
* This is optional: if not given, then there will be no extra data added to the derived triangles.
|
|
* @returns {FeatureCollection<Polygon>} TIN output
|
|
* @example
|
|
* // generate some random point data
|
|
* var points = turf.randomPoint(30, {bbox: [50, 30, 70, 50]});
|
|
*
|
|
* // add a random property to each point between 0 and 9
|
|
* for (var i = 0; i < points.features.length; i++) {
|
|
* points.features[i].properties.z = ~~(Math.random() * 9);
|
|
* }
|
|
* var tin = turf.tin(points, 'z');
|
|
*
|
|
* //addToMap
|
|
* var addToMap = [tin, points]
|
|
* for (var i = 0; i < tin.features.length; i++) {
|
|
* var properties = tin.features[i].properties;
|
|
* properties.fill = '#' + properties.a + properties.b + properties.c;
|
|
* }
|
|
*/
|
|
function tin(points, z) {
|
|
// break down points
|
|
var isPointZ = false;
|
|
return helpers_1.featureCollection(triangulate(points.features.map(function (p) {
|
|
var point = {
|
|
x: p.geometry.coordinates[0],
|
|
y: p.geometry.coordinates[1],
|
|
};
|
|
if (z) {
|
|
point.z = p.properties[z];
|
|
}
|
|
else if (p.geometry.coordinates.length === 3) {
|
|
isPointZ = true;
|
|
point.z = p.geometry.coordinates[2];
|
|
}
|
|
return point;
|
|
})).map(function (triangle) {
|
|
var a = [triangle.a.x, triangle.a.y];
|
|
var b = [triangle.b.x, triangle.b.y];
|
|
var c = [triangle.c.x, triangle.c.y];
|
|
var properties = {};
|
|
// Add z coordinates to triangle points if user passed
|
|
// them in that way otherwise add it as a property.
|
|
if (isPointZ) {
|
|
a.push(triangle.a.z);
|
|
b.push(triangle.b.z);
|
|
c.push(triangle.c.z);
|
|
}
|
|
else {
|
|
properties = {
|
|
a: triangle.a.z,
|
|
b: triangle.b.z,
|
|
c: triangle.c.z,
|
|
};
|
|
}
|
|
return helpers_1.polygon([[a, b, c, a]], properties);
|
|
}));
|
|
}
|
|
exports.default = tin;
|
|
var Triangle = /** @class */ (function () {
|
|
function Triangle(a, b, c) {
|
|
this.a = a;
|
|
this.b = b;
|
|
this.c = c;
|
|
var A = b.x - a.x;
|
|
var B = b.y - a.y;
|
|
var C = c.x - a.x;
|
|
var D = c.y - a.y;
|
|
var E = A * (a.x + b.x) + B * (a.y + b.y);
|
|
var F = C * (a.x + c.x) + D * (a.y + c.y);
|
|
var G = 2 * (A * (c.y - b.y) - B * (c.x - b.x));
|
|
var dx;
|
|
var dy;
|
|
// If the points of the triangle are collinear, then just find the
|
|
// extremes and use the midpoint as the center of the circumcircle.
|
|
this.x = (D * E - B * F) / G;
|
|
this.y = (A * F - C * E) / G;
|
|
dx = this.x - a.x;
|
|
dy = this.y - a.y;
|
|
this.r = dx * dx + dy * dy;
|
|
}
|
|
return Triangle;
|
|
}());
|
|
function byX(a, b) {
|
|
return b.x - a.x;
|
|
}
|
|
function dedup(edges) {
|
|
var j = edges.length;
|
|
var a;
|
|
var b;
|
|
var i;
|
|
var m;
|
|
var n;
|
|
outer: while (j) {
|
|
b = edges[--j];
|
|
a = edges[--j];
|
|
i = j;
|
|
while (i) {
|
|
n = edges[--i];
|
|
m = edges[--i];
|
|
if ((a === m && b === n) || (a === n && b === m)) {
|
|
edges.splice(j, 2);
|
|
edges.splice(i, 2);
|
|
j -= 2;
|
|
continue outer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function triangulate(vertices) {
|
|
// Bail if there aren't enough vertices to form any triangles.
|
|
if (vertices.length < 3) {
|
|
return [];
|
|
}
|
|
// Ensure the vertex array is in order of descending X coordinate
|
|
// (which is needed to ensure a subquadratic runtime), and then find
|
|
// the bounding box around the points.
|
|
vertices.sort(byX);
|
|
var i = vertices.length - 1;
|
|
var xmin = vertices[i].x;
|
|
var xmax = vertices[0].x;
|
|
var ymin = vertices[i].y;
|
|
var ymax = ymin;
|
|
var epsilon = 1e-12;
|
|
var a;
|
|
var b;
|
|
var c;
|
|
var A;
|
|
var B;
|
|
var G;
|
|
while (i--) {
|
|
if (vertices[i].y < ymin) {
|
|
ymin = vertices[i].y;
|
|
}
|
|
if (vertices[i].y > ymax) {
|
|
ymax = vertices[i].y;
|
|
}
|
|
}
|
|
// Find a supertriangle, which is a triangle that surrounds all the
|
|
// vertices. This is used like something of a sentinel value to remove
|
|
// cases in the main algorithm, and is removed before we return any
|
|
// results.
|
|
// Once found, put it in the "open" list. (The "open" list is for
|
|
// triangles who may still need to be considered; the "closed" list is
|
|
// for triangles which do not.)
|
|
var dx = xmax - xmin;
|
|
var dy = ymax - ymin;
|
|
var dmax = dx > dy ? dx : dy;
|
|
var xmid = (xmax + xmin) * 0.5;
|
|
var ymid = (ymax + ymin) * 0.5;
|
|
var open = [
|
|
new Triangle({
|
|
__sentinel: true,
|
|
x: xmid - 20 * dmax,
|
|
y: ymid - dmax,
|
|
}, {
|
|
__sentinel: true,
|
|
x: xmid,
|
|
y: ymid + 20 * dmax,
|
|
}, {
|
|
__sentinel: true,
|
|
x: xmid + 20 * dmax,
|
|
y: ymid - dmax,
|
|
}),
|
|
];
|
|
var closed = [];
|
|
var edges = [];
|
|
var j;
|
|
// Incrementally add each vertex to the mesh.
|
|
i = vertices.length;
|
|
while (i--) {
|
|
// For each open triangle, check to see if the current point is
|
|
// inside it's circumcircle. If it is, remove the triangle and add
|
|
// it's edges to an edge list.
|
|
edges.length = 0;
|
|
j = open.length;
|
|
while (j--) {
|
|
// If this point is to the right of this triangle's circumcircle,
|
|
// then this triangle should never get checked again. Remove it
|
|
// from the open list, add it to the closed list, and skip.
|
|
dx = vertices[i].x - open[j].x;
|
|
if (dx > 0 && dx * dx > open[j].r) {
|
|
closed.push(open[j]);
|
|
open.splice(j, 1);
|
|
continue;
|
|
}
|
|
// If not, skip this triangle.
|
|
dy = vertices[i].y - open[j].y;
|
|
if (dx * dx + dy * dy > open[j].r) {
|
|
continue;
|
|
}
|
|
// Remove the triangle and add it's edges to the edge list.
|
|
edges.push(open[j].a, open[j].b, open[j].b, open[j].c, open[j].c, open[j].a);
|
|
open.splice(j, 1);
|
|
}
|
|
// Remove any doubled edges.
|
|
dedup(edges);
|
|
// Add a new triangle for each edge.
|
|
j = edges.length;
|
|
while (j) {
|
|
b = edges[--j];
|
|
a = edges[--j];
|
|
c = vertices[i];
|
|
// Avoid adding colinear triangles (which have error-prone
|
|
// circumcircles)
|
|
A = b.x - a.x;
|
|
B = b.y - a.y;
|
|
G = 2 * (A * (c.y - b.y) - B * (c.x - b.x));
|
|
if (Math.abs(G) > epsilon) {
|
|
open.push(new Triangle(a, b, c));
|
|
}
|
|
}
|
|
}
|
|
// Copy any remaining open triangles to the closed list, and then
|
|
// remove any triangles that share a vertex with the supertriangle.
|
|
Array.prototype.push.apply(closed, open);
|
|
i = closed.length;
|
|
while (i--) {
|
|
if (closed[i].a.__sentinel ||
|
|
closed[i].b.__sentinel ||
|
|
closed[i].c.__sentinel) {
|
|
closed.splice(i, 1);
|
|
}
|
|
}
|
|
return closed;
|
|
}
|