summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPirate Praveen <praveen@debian.org>2017-10-10 12:57:12 +0530
committerPirate Praveen <praveen@debian.org>2017-10-10 12:57:12 +0530
commitb7f17f1cee3fd2d63ca365385ce5eba10995c338 (patch)
tree25a724927abedf46ce386fbda3ced667e6f0cc4e
Import Upstream version 1.0.3
-rw-r--r--.eslintrc8
-rw-r--r--.gitignore5
-rw-r--r--.npmignore3
-rw-r--r--LICENSE27
-rw-r--r--README.md42
-rw-r--r--d3-polygon.sublime-project13
-rw-r--r--img/hull.pngbin0 -> 11243 bytes
-rw-r--r--index.js5
-rw-r--r--package.json39
-rw-r--r--src/area.js15
-rw-r--r--src/centroid.js20
-rw-r--r--src/contains.js16
-rw-r--r--src/cross.js7
-rw-r--r--src/hull.js49
-rw-r--r--src/length.js23
-rw-r--r--test/area-test.js37
-rw-r--r--test/centroid-test.js37
-rw-r--r--test/contains-test.js28
-rw-r--r--test/hull-test.js33
-rw-r--r--test/length-test.js24
20 files changed, 431 insertions, 0 deletions
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..d1da9f4
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,8 @@
+parserOptions:
+ sourceType: module
+
+extends:
+ "eslint:recommended"
+
+rules:
+ no-cond-assign: 0
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..75704c6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.sublime-workspace
+.DS_Store
+build/
+node_modules
+npm-debug.log
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..dfb770e
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,3 @@
+*.sublime-*
+build/*.zip
+test/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..721bd22
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2010-2016 Mike Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of contributors may be used to
+ endorse or promote products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..770877a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+# d3-polygon
+
+This module provides a few basic geometric operations for two-dimensional polygons. Each polygon is represented as an array of two-element arrays [​[<i>x1</i>, <i>y1</i>], [<i>x2</i>, <i>y2</i>], …], and may either be closed (wherein the first and last point are the same) or open (wherein they are not). Typically polygons are in counterclockwise order, assuming a coordinate system where the origin ⟨0,0⟩ is in the top-left corner.
+
+## Installing
+
+If you use NPM, `npm install d3-polygon`. Otherwise, download the [latest release](https://github.com/d3/d3-polygon/releases/latest). You can also load directly from [d3js.org](https://d3js.org), either as a [standalone library](https://d3js.org/d3-polygon.v1.min.js) or as part of [D3 4.0](https://github.com/d3/d3). AMD, CommonJS, and vanilla environments are supported. In vanilla, a `d3` global is exported:
+
+```html
+<script src="https://d3js.org/d3-polygon.v1.min.js"></script>
+<script>
+
+var hull = d3.polygonHull(points);
+
+</script>
+```
+
+[Try d3-polygon in your browser.](https://tonicdev.com/npm/d3-polygon)
+
+## API Reference
+
+<a href="#polygonArea" name="polygonArea">#</a> d3.<b>polygonArea</b>(<i>polygon</i>) [<>](https://github.com/d3/d3-polygon/blob/master/src/area.js#L1 "Source Code")
+
+Returns the signed area of the specified *polygon*. If the vertices of the polygon are in counterclockwise order (assuming a coordinate system where the origin ⟨0,0⟩ is in the top-left corner), the returned area is positive; otherwise it is negative, or zero.
+
+<a href="#polygonCentroid" name="polygonCentroid">#</a> d3.<b>polygonCentroid</b>(<i>polygon</i>) [<>](https://github.com/d3/d3-polygon/blob/master/src/centroid.js#L1 "Source Code")
+
+Returns the [centroid](https://en.wikipedia.org/wiki/Centroid) of the specified *polygon*.
+
+<a href="#polygonHull" name="polygonHull">#</a> d3.<b>polygonHull</b>(<i>points</i>) [<>](https://github.com/d3/d3-polygon/blob/master/src/hull.js#L23 "Source Code")
+
+<a href="http://bl.ocks.org/mbostock/6f14f7b7f267a85f7cdc"><img src="https://raw.githubusercontent.com/d3/d3-polygon/master/img/hull.png" width="250" height="250"></a>
+
+Returns the [convex hull](https://en.wikipedia.org/wiki/Convex_hull) of the specified *points* using [Andrew’s monotone chain algorithm](http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain). The returned hull is represented as an array containing a subset of the input *points* arranged in counterclockwise order. Returns null if *points* has fewer than three elements.
+
+<a href="#polygonContains" name="polygonContains">#</a> d3.<b>polygonContains</b>(<i>polygon</i>, <i>point</i>) [<>](https://github.com/d3/d3-polygon/blob/master/src/contains.js#L1 "Source Code")
+
+Returns true if and only if the specified *point* is [inside the specified *polygon*](https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html).
+
+<a href="#polygonLength" name="polygonLength">#</a> d3.<b>polygonLength</b>(<i>polygon</i>) [<>](https://github.com/d3/d3-polygon/blob/master/src/length.js#L1 "Source Code")
+
+Returns the length of the perimeter of the specified *polygon*.
diff --git a/d3-polygon.sublime-project b/d3-polygon.sublime-project
new file mode 100644
index 0000000..6e0c2d4
--- /dev/null
+++ b/d3-polygon.sublime-project
@@ -0,0 +1,13 @@
+{
+ "folders": [
+ {
+ "path": ".",
+ "file_exclude_patterns": [
+ "*.sublime-workspace"
+ ],
+ "folder_exclude_patterns": [
+ "build"
+ ]
+ }
+ ]
+}
diff --git a/img/hull.png b/img/hull.png
new file mode 100644
index 0000000..d95ab60
--- /dev/null
+++ b/img/hull.png
Binary files differ
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..ffabecc
--- /dev/null
+++ b/index.js
@@ -0,0 +1,5 @@
+export {default as polygonArea} from "./src/area";
+export {default as polygonCentroid} from "./src/centroid";
+export {default as polygonHull} from "./src/hull";
+export {default as polygonContains} from "./src/contains";
+export {default as polygonLength} from "./src/length";
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..5b91217
--- /dev/null
+++ b/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "d3-polygon",
+ "version": "1.0.3",
+ "description": "Operations for two-dimensional polygons.",
+ "keywords": [
+ "d3",
+ "d3-module",
+ "polygon",
+ "hull",
+ "geometry",
+ "graphics"
+ ],
+ "homepage": "https://d3js.org/d3-polygon/",
+ "license": "BSD-3-Clause",
+ "author": {
+ "name": "Mike Bostock",
+ "url": "http://bost.ocks.org/mike"
+ },
+ "main": "build/d3-polygon.js",
+ "module": "index",
+ "jsnext:main": "index",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/d3/d3-polygon.git"
+ },
+ "scripts": {
+ "pretest": "rm -rf build && mkdir build && rollup --banner \"$(preamble)\" -f umd -n d3 -o build/d3-polygon.js -- index.js",
+ "test": "tape 'test/**/*-test.js' && eslint index.js src",
+ "prepublish": "npm run test && uglifyjs --preamble \"$(preamble)\" build/d3-polygon.js -c -m -o build/d3-polygon.min.js",
+ "postpublish": "git push && git push --tags && cd ../d3.github.com && git pull && cp ../d3-polygon/build/d3-polygon.js d3-polygon.v1.js && cp ../d3-polygon/build/d3-polygon.min.js d3-polygon.v1.min.js && git add d3-polygon.v1.js d3-polygon.v1.min.js && git commit -m \"d3-polygon ${npm_package_version}\" && git push && cd - && zip -j build/d3-polygon.zip -- LICENSE README.md build/d3-polygon.js build/d3-polygon.min.js"
+ },
+ "devDependencies": {
+ "eslint": "3",
+ "package-preamble": "0.0",
+ "rollup": "0.41",
+ "tape": "4",
+ "uglify-js": "^2.8.11"
+ }
+}
diff --git a/src/area.js b/src/area.js
new file mode 100644
index 0000000..109efb4
--- /dev/null
+++ b/src/area.js
@@ -0,0 +1,15 @@
+export default function(polygon) {
+ var i = -1,
+ n = polygon.length,
+ a,
+ b = polygon[n - 1],
+ area = 0;
+
+ while (++i < n) {
+ a = b;
+ b = polygon[i];
+ area += a[1] * b[0] - a[0] * b[1];
+ }
+
+ return area / 2;
+}
diff --git a/src/centroid.js b/src/centroid.js
new file mode 100644
index 0000000..6c8ece1
--- /dev/null
+++ b/src/centroid.js
@@ -0,0 +1,20 @@
+export default function(polygon) {
+ var i = -1,
+ n = polygon.length,
+ x = 0,
+ y = 0,
+ a,
+ b = polygon[n - 1],
+ c,
+ k = 0;
+
+ while (++i < n) {
+ a = b;
+ b = polygon[i];
+ k += c = a[0] * b[1] - b[0] * a[1];
+ x += (a[0] + b[0]) * c;
+ y += (a[1] + b[1]) * c;
+ }
+
+ return k *= 3, [x / k, y / k];
+}
diff --git a/src/contains.js b/src/contains.js
new file mode 100644
index 0000000..a0beabc
--- /dev/null
+++ b/src/contains.js
@@ -0,0 +1,16 @@
+export default function(polygon, point) {
+ var n = polygon.length,
+ p = polygon[n - 1],
+ x = point[0], y = point[1],
+ x0 = p[0], y0 = p[1],
+ x1, y1,
+ inside = false;
+
+ for (var i = 0; i < n; ++i) {
+ p = polygon[i], x1 = p[0], y1 = p[1];
+ if (((y1 > y) !== (y0 > y)) && (x < (x0 - x1) * (y - y1) / (y0 - y1) + x1)) inside = !inside;
+ x0 = x1, y0 = y1;
+ }
+
+ return inside;
+}
diff --git a/src/cross.js b/src/cross.js
new file mode 100644
index 0000000..11a6df0
--- /dev/null
+++ b/src/cross.js
@@ -0,0 +1,7 @@
+// Returns the 2D cross product of AB and AC vectors, i.e., the z-component of
+// the 3D cross product in a quadrant I Cartesian coordinate system (+x is
+// right, +y is up). Returns a positive value if ABC is counter-clockwise,
+// negative if clockwise, and zero if the points are collinear.
+export default function(a, b, c) {
+ return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
+}
diff --git a/src/hull.js b/src/hull.js
new file mode 100644
index 0000000..7a8f957
--- /dev/null
+++ b/src/hull.js
@@ -0,0 +1,49 @@
+import cross from "./cross";
+
+function lexicographicOrder(a, b) {
+ return a[0] - b[0] || a[1] - b[1];
+}
+
+// Computes the upper convex hull per the monotone chain algorithm.
+// Assumes points.length >= 3, is sorted by x, unique in y.
+// Returns an array of indices into points in left-to-right order.
+function computeUpperHullIndexes(points) {
+ var n = points.length,
+ indexes = [0, 1],
+ size = 2;
+
+ for (var i = 2; i < n; ++i) {
+ while (size > 1 && cross(points[indexes[size - 2]], points[indexes[size - 1]], points[i]) <= 0) --size;
+ indexes[size++] = i;
+ }
+
+ return indexes.slice(0, size); // remove popped points
+}
+
+export default function(points) {
+ if ((n = points.length) < 3) return null;
+
+ var i,
+ n,
+ sortedPoints = new Array(n),
+ flippedPoints = new Array(n);
+
+ for (i = 0; i < n; ++i) sortedPoints[i] = [+points[i][0], +points[i][1], i];
+ sortedPoints.sort(lexicographicOrder);
+ for (i = 0; i < n; ++i) flippedPoints[i] = [sortedPoints[i][0], -sortedPoints[i][1]];
+
+ var upperIndexes = computeUpperHullIndexes(sortedPoints),
+ lowerIndexes = computeUpperHullIndexes(flippedPoints);
+
+ // Construct the hull polygon, removing possible duplicate endpoints.
+ var skipLeft = lowerIndexes[0] === upperIndexes[0],
+ skipRight = lowerIndexes[lowerIndexes.length - 1] === upperIndexes[upperIndexes.length - 1],
+ hull = [];
+
+ // Add upper hull in right-to-l order.
+ // Then add lower hull in left-to-right order.
+ for (i = upperIndexes.length - 1; i >= 0; --i) hull.push(points[sortedPoints[upperIndexes[i]][2]]);
+ for (i = +skipLeft; i < lowerIndexes.length - skipRight; ++i) hull.push(points[sortedPoints[lowerIndexes[i]][2]]);
+
+ return hull;
+}
diff --git a/src/length.js b/src/length.js
new file mode 100644
index 0000000..79a90b8
--- /dev/null
+++ b/src/length.js
@@ -0,0 +1,23 @@
+export default function(polygon) {
+ var i = -1,
+ n = polygon.length,
+ b = polygon[n - 1],
+ xa,
+ ya,
+ xb = b[0],
+ yb = b[1],
+ perimeter = 0;
+
+ while (++i < n) {
+ xa = xb;
+ ya = yb;
+ b = polygon[i];
+ xb = b[0];
+ yb = b[1];
+ xa -= xb;
+ ya -= yb;
+ perimeter += Math.sqrt(xa * xa + ya * ya);
+ }
+
+ return perimeter;
+}
diff --git a/test/area-test.js b/test/area-test.js
new file mode 100644
index 0000000..55f607e
--- /dev/null
+++ b/test/area-test.js
@@ -0,0 +1,37 @@
+var tape = require("tape"),
+ polygon = require("../");
+
+tape("polygonArea(polygon) returns the expected value for closed counterclockwise polygons", function(test) {
+ test.equal(polygon.polygonArea([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]), 1);
+ test.end();
+});
+
+tape("polygonArea(polygon) returns the expected value for closed clockwise polygons", function(test) {
+ test.equal(polygon.polygonArea([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]), -1);
+ test.equal(polygon.polygonArea([[1, 1], [3, 2], [2, 3], [1, 1]]), -1.5);
+ test.end();
+});
+
+tape("polygonArea(polygon) returns the expected value for open counterclockwise polygons", function(test) {
+ test.equal(polygon.polygonArea([[0, 0], [0, 1], [1, 1], [1, 0]]), 1);
+ test.end();
+});
+
+tape("polygonArea(polygon) returns the expected value for open clockwise polygons", function(test) {
+ test.equal(polygon.polygonArea([[0, 0], [1, 0], [1, 1], [0, 1]]), -1);
+ test.equal(polygon.polygonArea([[1, 1], [3, 2], [2, 3]]), -1.5);
+ test.end();
+});
+
+tape("polygonArea(polygon) returns the expected value for a very large polygon", function(test) {
+ var start = 0,
+ stop = 1e8,
+ step = 1e4,
+ points = [];
+ for (var value = 0; value < stop; value += step) points.push([0, value]);
+ for (var value = 0; value < stop; value += step) points.push([value, stop]);
+ for (var value = stop - step; value >= 0; value -= step) points.push([stop, value]);
+ for (var value = stop - step; value >= 0; value -= step) points.push([value, 0]);
+ test.equal(polygon.polygonArea(points), 1e16 - 5e7);
+ test.end();
+});
diff --git a/test/centroid-test.js b/test/centroid-test.js
new file mode 100644
index 0000000..1d16e69
--- /dev/null
+++ b/test/centroid-test.js
@@ -0,0 +1,37 @@
+var tape = require("tape"),
+ polygon = require("../");
+
+tape("polygonCentroid(points) returns the expected value for closed counterclockwise polygons", function(test) {
+ test.deepEqual(polygon.polygonCentroid([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]), [.5, .5]);
+ test.end();
+});
+
+tape("polygonCentroid(points) returns the expected value for closed clockwise polygons", function(test) {
+ test.deepEqual(polygon.polygonCentroid([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]), [.5, .5]);
+ test.deepEqual(polygon.polygonCentroid([[1, 1], [3, 2], [2, 3], [1, 1]]), [2, 2]);
+ test.end();
+});
+
+tape("polygonCentroid(points) returns the expected value for open counterclockwise polygons", function(test) {
+ test.deepEqual(polygon.polygonCentroid([[0, 0], [0, 1], [1, 1], [1, 0]]), [.5, .5]);
+ test.end();
+});
+
+tape("polygonCentroid(points) returns the expected value for closed counterclockwise polygons", function(test) {
+ test.deepEqual(polygon.polygonCentroid([[0, 0], [1, 0], [1, 1], [0, 1]]), [.5, .5]);
+ test.deepEqual(polygon.polygonCentroid([[1, 1], [3, 2], [2, 3]]), [2, 2]);
+ test.end();
+});
+
+tape("polygonCentroid(polygon) returns the expected value for a very large polygon", function(test) {
+ var start = 0,
+ stop = 1e8,
+ step = 1e4,
+ points = [];
+ for (var value = 0; value < stop; value += step) points.push([0, value]);
+ for (var value = 0; value < stop; value += step) points.push([value, stop]);
+ for (var value = stop - step; value >= 0; value -= step) points.push([stop, value]);
+ for (var value = stop - step; value >= 0; value -= step) points.push([value, 0]);
+ test.deepEqual(polygon.polygonCentroid(points), [49999999.75000187, 49999999.75001216]);
+ test.end();
+});
diff --git a/test/contains-test.js b/test/contains-test.js
new file mode 100644
index 0000000..0f3315d
--- /dev/null
+++ b/test/contains-test.js
@@ -0,0 +1,28 @@
+var tape = require("tape"),
+ polygon = require("../");
+
+tape("polygonContains(polygon, point) returns the expected value for closed counterclockwise polygons", function(test) {
+ test.equal(polygon.polygonContains([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], [0.5, 0.5]), true);
+ test.equal(polygon.polygonContains([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], [1.5, 0.5]), false);
+ test.equal(polygon.polygonContains([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], [-0.5, 0.5]), false);
+ test.equal(polygon.polygonContains([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], [0.5, 1.5]), false);
+ test.equal(polygon.polygonContains([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], [0.5, -0.5]), false);
+ test.end();
+});
+
+tape("polygonContains(polygon, point) returns the expected value for closed clockwise polygons", function(test) {
+ test.equal(polygon.polygonContains([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]], [0.5, 0.5]), true);
+ test.equal(polygon.polygonContains([[1, 1], [3, 2], [2, 3], [1, 1]], [1.5, 1.5]), true);
+ test.end();
+});
+
+tape("polygonContains(polygon, point) returns the expected value for open counterclockwise polygons", function(test) {
+ test.equal(polygon.polygonContains([[0, 0], [0, 1], [1, 1], [1, 0]], [0.5, 0.5]), true);
+ test.end();
+});
+
+tape("polygonContains(polygon, point) returns the expected value for open clockwise polygons", function(test) {
+ test.equal(polygon.polygonContains([[0, 0], [1, 0], [1, 1], [0, 1]], [0.5, 0.5]), true);
+ test.equal(polygon.polygonContains([[1, 1], [3, 2], [2, 3]], [1.5, 1.5]), true);
+ test.end();
+});
diff --git a/test/hull-test.js b/test/hull-test.js
new file mode 100644
index 0000000..73b834b
--- /dev/null
+++ b/test/hull-test.js
@@ -0,0 +1,33 @@
+var tape = require("tape"),
+ polygon = require("../");
+
+tape("polygonHull(points) returns null if points has fewer than three elements", function(test) {
+ test.equal(polygon.polygonHull([]), null);
+ test.equal(polygon.polygonHull([[0, 1]]), null);
+ test.equal(polygon.polygonHull([[0, 1], [1, 0]]), null);
+ test.end();
+});
+
+tape("polygonHull(points) returns the convex hull of the specified points", function(test) {
+ test.deepEqual(polygon.polygonHull([[200, 200], [760, 300], [500, 500]]), [[760, 300], [200, 200], [500, 500]]);
+ test.deepEqual(polygon.polygonHull([[200, 200], [760, 300], [500, 500], [400, 400]]), [[760, 300], [200, 200], [500, 500]]);
+ test.end();
+});
+
+tape("polygonHull(points) handles points with duplicate ordinates", function(test) {
+ test.deepEqual(polygon.polygonHull([[-10, -10], [10, 10], [10, -10], [-10, 10]]), [[10, 10], [10, -10], [-10, -10], [-10, 10]]);
+ test.end();
+});
+
+tape("polygonHull(points) handles overlapping upper and lower hulls", function(test) {
+ test.deepEqual(polygon.polygonHull([[0, -10], [0, 10], [0, 0], [10, 0], [-10, 0]]), [[10, 0], [0, -10], [-10, 0], [0, 10]]);
+ test.end();
+});
+
+// Cases below taken from http://uva.onlinejudge.org/external/6/681.html
+tape("polygonHull(points) handles various non-trivial hulls", function(test) {
+ test.deepEqual(polygon.polygonHull([[60, 20], [250, 140], [180, 170], [79, 140], [50, 60], [60, 20]]), [[250, 140], [60, 20], [50, 60], [79, 140], [180, 170]]);
+ test.deepEqual(polygon.polygonHull([[50, 60], [60, 20], [70, 45], [100, 70], [125, 90], [200, 113], [250, 140], [180, 170], [105, 140], [79, 140], [60, 85], [50, 60]]), [[250, 140], [60, 20], [50, 60], [79, 140], [180, 170]]);
+ test.deepEqual(polygon.polygonHull([[30, 30], [50, 60], [60, 20], [70, 45], [86, 39], [112, 60], [200, 113], [250, 50], [300, 200], [130, 240], [76, 150], [47, 76], [36, 40], [33, 35], [30, 30]]), [[300, 200], [250, 50], [60, 20], [30, 30], [47, 76], [76, 150], [130, 240]]);
+ test.end();
+});
diff --git a/test/length-test.js b/test/length-test.js
new file mode 100644
index 0000000..3cf1178
--- /dev/null
+++ b/test/length-test.js
@@ -0,0 +1,24 @@
+var tape = require("tape"),
+ polygon = require("../");
+
+tape("polygonLength(polygon) returns the expected value for closed counterclockwise polygons", function(test) {
+ test.equal(polygon.polygonLength([[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]), 4);
+ test.end();
+});
+
+tape("polygonLength(polygon) returns the expected value for closed clockwise polygons", function(test) {
+ test.equal(polygon.polygonLength([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]), 4);
+ test.equal(polygon.polygonLength([[1, 1], [3, 2], [2, 3], [1, 1]]), Math.sqrt(20) + Math.sqrt(2));
+ test.end();
+});
+
+tape("polygonLength(polygon) returns the expected value for open counterclockwise polygons", function(test) {
+ test.equal(polygon.polygonLength([[0, 0], [0, 1], [1, 1], [1, 0]]), 4);
+ test.end();
+});
+
+tape("polygonLength(polygon) returns the expected value for open clockwise polygons", function(test) {
+ test.equal(polygon.polygonLength([[0, 0], [1, 0], [1, 1], [0, 1]]), 4);
+ test.equal(polygon.polygonLength([[1, 1], [3, 2], [2, 3]]), Math.sqrt(20) + Math.sqrt(2));
+ test.end();
+});