Skip to content

Commit

Permalink
Simplify, optimize. ~7.6x performance increase.
Browse files Browse the repository at this point in the history
* Creates valid polygon rings the first time
* Stores properties on nodes so it doesn't have to do nearest calls
* Removes two dependencies

turf-tin x 3,078 ops/sec ±0.73% (96 runs sampled)
turf-tin 2 x 23,254 ops/sec ±0.77% (99 runs sampled)
  • Loading branch information
tmcw committed Jan 16, 2015
1 parent 84e16a1 commit 595f732
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 139 deletions.
9 changes: 4 additions & 5 deletions bench.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
var tin = require('./');
global.tin = require('./');
var Benchmark = require('benchmark');
var fs = require('fs');

var points = JSON.parse(fs.readFileSync(__dirname+'/geojson/Points.geojson'));
global.points = JSON.parse(fs.readFileSync(__dirname+'/geojson/Points.geojson'));

var suite = new Benchmark.Suite('turf-tin');
suite
.add('turf-tin',function () {
tin(points, 'elevation');
global.tin(global.points, 'elevation');
})
.on('cycle', function (event) {
console.log(String(event.target));
})
.on('complete', function () {

})
.run();
.run();
250 changes: 120 additions & 130 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
//http://en.wikipedia.org/wiki/Delaunay_triangulation
//https://github.com/ironwallaby/delaunay
var polygon = require('turf-polygon');
var nearest = require('turf-nearest');
var featurecollection = require('turf-featurecollection');
var point = require('turf-point');

/**
* Takes a set of points and the name of a z-value property and
Expand Down Expand Up @@ -43,78 +41,57 @@ var point = require('turf-point');
* }
* //=tin
*/
module.exports = function(points, z){
module.exports = function(points, z) {
//break down points
var triangles = featurecollection(triangulate(points.features.map(function(p){
return {
x: p.geometry.coordinates[0],
y: p.geometry.coordinates[1]
return featurecollection(triangulate(points.features.map(function(p) {
var point = {
x: p.geometry.coordinates[0],
y: p.geometry.coordinates[1]
};
})).map(function(triangle){
return polygon([[[triangle.a.x, triangle.a.y],
if (z) point.z = p.properties[z];
return point;
})).map(function(triangle) {
return polygon([[
[triangle.a.x, triangle.a.y],
[triangle.b.x, triangle.b.y],
[triangle.c.x, triangle.c.y]]], {a: null, b: null, c: null});
}));

if (z) {
// add values from vertices
triangles.features.forEach(function(tri){
tri.geometry.coordinates[0].forEach(function(c, i){
var closest = nearest(point(c[0], c[1]), points);
switch (i) {
case 0:
tri.properties.a = closest.properties[z];
break;
case 1:
tri.properties.b = closest.properties[z];
break;
case 2:
tri.properties.c = closest.properties[z];
break;
}
[triangle.c.x, triangle.c.y],
[triangle.a.x, triangle.a.y]
]], {
a: triangle.a.z,
b: triangle.b.z,
c: triangle.c.z
});
});
}
triangles.features.forEach(correctRings);
return triangles;
}));
};

function correctRings(poly){
poly.geometry.coordinates.forEach(function(ring){
if (ring[0] !== ring.slice(-1)[0]) ring.push(ring[0]);
});
}

function Triangle(a, b, c) {
this.a = a;
this.b = b;
this.c = c;

var A = b.x - a.x,
B = b.y - a.y,
C = c.x - a.x,
D = c.y - a.y,
E = A * (a.x + b.x) + B * (a.y + b.y),
F = C * (a.x + c.x) + D * (a.y + c.y),
G = 2 * (A * (c.y - b.y) - B * (c.x - b.x)),
minx, miny, dx, dy;
B = b.y - a.y,
C = c.x - a.x,
D = c.y - a.y,
E = A * (a.x + b.x) + B * (a.y + b.y),
F = C * (a.x + c.x) + D * (a.y + c.y),
G = 2 * (A * (c.y - b.y) - B * (c.x - b.x)),
minx, miny, dx, dy;

/* If the points of the triangle are collinear, then just find the
* extremes and use the midpoint as the center of the circumcircle. */
if(Math.abs(G) < 0.000001) {
if (Math.abs(G) < 0.000001) {
minx = Math.min(a.x, b.x, c.x);
miny = Math.min(a.y, b.y, c.y);
dx = (Math.max(a.x, b.x, c.x) - minx) * 0.5;
dy = (Math.max(a.y, b.y, c.y) - miny) * 0.5;
dx = (Math.max(a.x, b.x, c.x) - minx) * 0.5;
dy = (Math.max(a.y, b.y, c.y) - miny) * 0.5;

this.x = minx + dx;
this.y = miny + dy;
this.r = dx * dx + dy * dy;
}

else {
this.x = (D*E - B*F) / G;
this.y = (A*F - C*E) / G;
} else {
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;
Expand All @@ -127,44 +104,47 @@ function byX(a, b) {

function dedup(edges) {
var j = edges.length,
a, b, i, m, 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
a, b, i, m, 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,
xmin = vertices[i].x,
xmax = vertices[0].x,
ymin = vertices[i].y,
ymax = ymin

while(i--) {
if(vertices[i].y < ymin) ymin = vertices[i].y
if(vertices[i].y > ymax) ymax = vertices[i].y
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,
xmin = vertices[i].x,
xmax = vertices[0].x,
ymin = vertices[i].y,
ymax = ymin;

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
Expand All @@ -175,78 +155,88 @@ function triangulate(vertices) {
* 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,
dy = ymax - ymin,
dmax = (dx > dy) ? dx : dy,
xmid = (xmax + xmin) * 0.5,
ymid = (ymax + ymin) * 0.5,
open = [
new Triangle(
{x: xmid - 20 * dmax, y: ymid - dmax, __sentinel: true},
{x: xmid , y: ymid + 20 * dmax, __sentinel: true},
{x: xmid + 20 * dmax, y: ymid - dmax, __sentinel: true}
)
],
closed = [],
edges = [],
j, a, b

/* Incrementally add each vertex to the mesh. */
i = vertices.length
while(i--) {
var dx = xmax - xmin,
dy = ymax - ymin,
dmax = (dx > dy) ? dx : dy,
xmid = (xmax + xmin) * 0.5,
ymid = (ymax + ymin) * 0.5,
open = [
new Triangle({
x: xmid - 20 * dmax,
y: ymid - dmax,
__sentinel: true
},
{
x: xmid,
y: ymid + 20 * dmax,
__sentinel: true
},
{
x: xmid + 20 * dmax,
y: ymid - dmax,
__sentinel: true
}
)],
closed = [],
edges = [],
j, a, b;

/* 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--) {
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
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
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)
);
open.splice(j, 1);
}

/* Remove any doubled edges. */
dedup(edges)
dedup(edges);

/* Add a new triangle for each edge. */
j = edges.length
while(j) {
b = edges[--j]
a = edges[--j]
open.push(new Triangle(a, b, vertices[i]))
j = edges.length;
while (j) {
b = edges[--j];
a = edges[--j];
open.push(new Triangle(a, b, vertices[i]));
}
}

/* 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)
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)
i = closed.length;
while (i--)
if (closed[i].a.__sentinel ||
closed[i].b.__sentinel ||
closed[i].c.__sentinel)
closed.splice(i, 1);

/* Yay, we're done! */
/* Yay, we're done! */
return closed;
}
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
},
"dependencies": {
"turf-featurecollection": "^1.0.0",
"turf-nearest": "^1.0.1",
"turf-point": "^1.2.0",
"turf-polygon": "^1.0.0"
}
}
3 changes: 1 addition & 2 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
var test = require('tape');
var fs = require('fs');
var tin = require('./');
var tin = require('./index.js');

test('tin', function(t){
var points = JSON.parse(fs.readFileSync(__dirname+'/geojson/Points.geojson'));

var tinned = tin(points, 'elevation');

t.equal(tinned.features[0].geometry.type, 'Polygon');
Expand Down

0 comments on commit 595f732

Please sign in to comment.