Comparing version 1.0.2 to 1.0.3
@@ -1,6 +0,6 @@ | ||
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')); | ||
@@ -10,3 +10,3 @@ var suite = new Benchmark.Suite('turf-tin'); | ||
.add('turf-tin',function () { | ||
tin(points, 'elevation'); | ||
global.tin(global.points, 'elevation'); | ||
}) | ||
@@ -17,4 +17,3 @@ .on('cycle', function (event) { | ||
.on('complete', function () { | ||
}) | ||
.run(); | ||
.run(); |
343
index.js
//http://en.wikipedia.org/wiki/Delaunay_triangulation | ||
//https://github.com/ironwallaby/delaunay | ||
var polygon = require('turf-polygon'); | ||
var nearest = require('turf-nearest'); | ||
var point = require('turf-point'); | ||
var featurecollection = require('turf-featurecollection'); | ||
@@ -10,121 +9,95 @@ /** | ||
* creates a [Triangulated Irregular Network](http://en.wikipedia.org/wiki/Triangulated_irregular_network), | ||
* or a TIN for short. These are often used | ||
* or a TIN for short, returned as a collection of Polygons. These are often used | ||
* for developing elevation contour maps or stepped heat visualizations. | ||
* | ||
* This triangulates the points, as well as adds properties called `a`, `b`, | ||
* and `c` representing the value of the given `propertyName` at each of | ||
* the points that represent the corners of the triangle. | ||
* | ||
* @module turf/tin | ||
* @param {GeoJSONFeatureCollection} points - a GeoJSON FeatureCollection containing | ||
* Features with Point geometries | ||
* @param {string} propertyName - name of the property from which to pull z values | ||
* @return {GeoJSONFeatureCollection} output | ||
* @param {FeatureCollection} points - a GeoJSON FeatureCollection containing | ||
* Features with {@link Point} geometries | ||
* @param {string=} propertyName - 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. | ||
* @return {FeatureCollection} TIN output | ||
* @example | ||
* var fs = require('fs') | ||
* var z = 'elevation' | ||
* var pts = JSON.parse(fs.readFileSync('/path/to/pts.geojson')) | ||
* var tinPolys = turf.tin(pts, z) | ||
* console.log(tinPolys) | ||
* // generate some random point data | ||
* var points = turf.random('points', 30, { | ||
* bbox: [50, 30, 70, 50] | ||
* }); | ||
* //=points | ||
* // 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') | ||
* for (var i = 0; i < tin.features.length; i++) { | ||
* var properties = tin.features[i].properties; | ||
* // roughly turn the properties of each | ||
* // triangle into a fill color | ||
* // so we can visualize the result | ||
* properties.fill = '#' + properties.a + | ||
* properties.b + properties.c; | ||
* } | ||
* //=tin | ||
*/ | ||
module.exports = function(points, z){ | ||
module.exports = function(points, z) { | ||
//break down points | ||
var vertices = []; | ||
points.features.forEach(function(p){ | ||
vertices.push({x:p.geometry.coordinates[0], y:p.geometry.coordinates[1]}); | ||
}) | ||
var triangulated = triangulate(vertices); | ||
var triangles = { | ||
type: 'FeatureCollection', | ||
features: [] | ||
}; | ||
triangulated.forEach(function(triangle){ | ||
var coords = [[[triangle.a.x, triangle.a.y], [triangle.b.x, triangle.b.y], [triangle.c.x, triangle.c.y]]]; | ||
var poly = polygon(coords, {a: null, b: null, c: null}); | ||
triangles.features.push(poly); | ||
}); | ||
if(z){ | ||
// add values from vertices | ||
triangles.features.forEach(function(tri){ | ||
var coordinateNumber = 1; | ||
tri.geometry.coordinates[0].forEach(function(c){ | ||
var closest = nearest(point(c[0], c[1]), points); | ||
if(coordinateNumber === 1){ | ||
tri.properties.a = closest.properties[z]; | ||
} | ||
else if(coordinateNumber === 2){ | ||
tri.properties.b = closest.properties[z]; | ||
} | ||
else if(coordinateNumber === 3){ | ||
tri.properties.c = closest.properties[z]; | ||
} | ||
coordinateNumber++; | ||
return 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]; | ||
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], | ||
[triangle.a.x, triangle.a.y] | ||
]], { | ||
a: triangle.a.z, | ||
b: triangle.b.z, | ||
c: triangle.c.z | ||
}); | ||
}); | ||
} | ||
})); | ||
}; | ||
triangles.features.forEach(function(tri){ | ||
tri = correctRings(tri); | ||
}); | ||
return triangles; | ||
} | ||
function correctRings(poly){ | ||
poly.geometry.coordinates.forEach(function(ring){ | ||
var isWrapped = ring[0] === ring.slice(-1)[0]; | ||
if(!isWrapped){ | ||
ring.push(ring[0]); | ||
} | ||
}) | ||
return poly; | ||
} | ||
function Triangle(a, b, c) { | ||
this.a = a | ||
this.b = b | ||
this.c = 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) { | ||
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 | ||
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; | ||
this.x = minx + dx | ||
this.y = miny + dy | ||
this.r = dx * dx + dy * dy | ||
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; | ||
dx = this.x - a.x; | ||
dy = this.y - a.y; | ||
this.r = dx * dx + dy * dy; | ||
} | ||
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 | ||
} | ||
} | ||
Triangle.prototype.draw = function(ctx) { | ||
ctx.beginPath() | ||
ctx.moveTo(this.a.x, this.a.y) | ||
ctx.lineTo(this.b.x, this.b.y) | ||
ctx.lineTo(this.c.x, this.c.y) | ||
ctx.closePath() | ||
ctx.stroke() | ||
} | ||
function byX(a, b) { | ||
return b.x - a.x | ||
return b.x - a.x; | ||
} | ||
@@ -134,16 +107,17 @@ | ||
var j = edges.length, | ||
a, b, i, m, n | ||
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 | ||
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; | ||
} | ||
@@ -156,19 +130,21 @@ } | ||
/* Bail if there aren't enough vertices to form any triangles. */ | ||
if(vertices.length < 3) | ||
return [] | ||
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) | ||
/* 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 | ||
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 | ||
while (i--) { | ||
if (vertices[i].y < ymin) | ||
ymin = vertices[i].y; | ||
if (vertices[i].y > ymax) | ||
ymax = vertices[i].y; | ||
} | ||
@@ -184,41 +160,51 @@ | ||
* 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 | ||
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--) { | ||
/* 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; | ||
@@ -230,15 +216,15 @@ /* Remove the triangle and add it's edges to the edge list. */ | ||
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])); | ||
} | ||
@@ -249,20 +235,13 @@ } | ||
* 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! */ | ||
return closed | ||
/* Yay, we're done! */ | ||
return closed; | ||
} | ||
/*if (typeof module !== 'undefined') { | ||
module.exports = { | ||
Triangle: Triangle, | ||
triangulate: triangulate | ||
} | ||
}*/ |
{ | ||
"name": "turf-tin", | ||
"version": "1.0.2", | ||
"version": "1.0.3", | ||
"description": "turf tin module", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "tape test.js" | ||
"test": "tape test.js", | ||
"doc": "dox -r < index.js | doxme --readme > README.md" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/morganherlocker/turf-tin.git" | ||
"url": "https://github.com/Turfjs/turf-tin.git" | ||
}, | ||
@@ -21,14 +22,15 @@ "keywords": [ | ||
"bugs": { | ||
"url": "https://github.com/morganherlocker/turf-tin/issues" | ||
"url": "https://github.com/Turfjs/turf-tin/issues" | ||
}, | ||
"homepage": "https://github.com/morganherlocker/turf-tin", | ||
"homepage": "https://github.com/Turfjs/turf-tin", | ||
"devDependencies": { | ||
"benchmark": "^1.0.0", | ||
"tape": "^3.0.3" | ||
"tape": "^3.0.3", | ||
"dox": "^0.6.1", | ||
"doxme": "^1.4.2" | ||
}, | ||
"dependencies": { | ||
"turf-nearest": "^1.0.1", | ||
"turf-point": "^1.2.0", | ||
"turf-polygon": "^1.0.0" | ||
"turf-featurecollection": "^1.0.0", | ||
"turf-polygon": "^1.0.1" | ||
} | ||
} |
153
README.md
@@ -1,38 +0,145 @@ | ||
turf-tin | ||
======== | ||
# turf-tin | ||
[![build status](https://secure.travis-ci.org/Turfjs/turf-tin.png)](http://travis-ci.org/Turfjs/turf-tin) | ||
Takes a set of points and the name of a z-value property and creates a tin (Triangulated Irregular Network). These are often used for developing elevation contour maps or stepped heat visualizations. | ||
turf tin module | ||
###Install | ||
```sh | ||
npm install turf-tin | ||
``` | ||
### `turf.tin(points, propertyName)` | ||
###Parameters | ||
Takes a set of points and the name of a z-value property 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. | ||
|name|description| | ||
|---|---| | ||
|pts|a point featurecollection| | ||
|z|a z value to encode| | ||
This triangulates the points, as well as adds properties called `a`, `b`, | ||
and `c` representing the value of the given `propertyName` at each of | ||
the points that represent the corners of the triangle. | ||
###Usage | ||
### Parameters | ||
| parameter | type | description | | ||
| -------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `points` | FeatureCollection | - a GeoJSON FeatureCollection containing Features with Point geometries | | ||
| `propertyName` | string | _optional:_ - 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. | | ||
### Example | ||
```js | ||
tin(pts, z) | ||
// generate some random point data | ||
var points = turf.random('points', 30, { | ||
bbox: [50, 30, 70, 50] | ||
}); | ||
//=points | ||
// 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') | ||
for (var i = 0; i < tin.features.length; i++) { | ||
var properties = tin.features[i].properties; | ||
// roughly turn the properties of each | ||
// triangle into a fill color | ||
// so we can visualize the result | ||
properties.fill = '#' + properties.a + | ||
properties.b + properties.c; | ||
} | ||
//=tin | ||
``` | ||
###Example | ||
```javascript | ||
var tin = require('turf-tin') | ||
var fs = require('fs') | ||
### `undefined` | ||
var z = 'elevation' | ||
var pts = JSON.parse(fs.readFileSync('/path/to/pts.geojson')) | ||
If the points of the triangle are collinear, then just find the | ||
extremes and use the midpoint as the center of the circumcircle. | ||
var tinPolys = tin(pts, z) | ||
console.log(tinPolys) | ||
``` | ||
### `undefined` | ||
Bail if there aren't enough vertices to form any triangles. | ||
### `undefined` | ||
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. | ||
### `dx` | ||
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.) | ||
### `undefined` | ||
Incrementally add each vertex to the mesh. | ||
### `length` | ||
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. | ||
### `undefined` | ||
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. | ||
### `undefined` | ||
If not, skip this triangle. | ||
### `undefined` | ||
Remove the triangle and add it's edges to the edge list. | ||
### `undefined` | ||
Remove any doubled edges. | ||
### `undefined` | ||
Add a new triangle for each edge. | ||
### `undefined` | ||
Copy any remaining open triangles to the closed list, and then | ||
remove any triangles that share a vertex with the supertriangle. | ||
### `undefined` | ||
Yay, we're done! | ||
## Installation | ||
Requires [nodejs](http://nodejs.org/). | ||
```sh | ||
$ npm install turf-tin | ||
``` | ||
## Tests | ||
```sh | ||
$ npm test | ||
``` | ||
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'); | ||
@@ -9,0 +8,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
22160
2
146
4
246
+ Addedturf-featurecollection@1.0.1(transitive)
- Removedturf-nearest@^1.0.1
- Removedturf-point@^1.2.0
- Removedminimist@1.2.8(transitive)
- Removedturf-distance@1.1.0(transitive)
- Removedturf-invariant@1.0.3(transitive)
- Removedturf-nearest@1.0.2(transitive)
- Removedturf-point@1.2.1(transitive)
Updatedturf-polygon@^1.0.1