From 54daee2374db28848de09d672675c674ac5117e0 Mon Sep 17 00:00:00 2001 From: morganherlocker Date: Wed, 6 May 2015 12:36:00 -0400 Subject: [PATCH 1/5] refactor poly processing --- bench.js | 6 +++--- index.js | 53 +++++++++++++++++++++++++++++++++++----------------- package.json | 4 +++- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/bench.js b/bench.js index d27f1df..8d18579 100644 --- a/bench.js +++ b/bench.js @@ -4,9 +4,9 @@ var fs = require('fs'); var point = require('turf-point'); var polygon = require('turf-polygon'); -var poly = polygon([[[0,0], [0,100], [100,100], [100,0]]]); -var ptIn = point(50, 50); -var ptOut = point(140, 150); +var poly = polygon([[[0,0], [0,100], [100,100], [100,0], [0,0]]]); +var ptIn = point([50, 50]); +var ptOut = point([140, 150]); var suite = new Benchmark.Suite('turf-inside'); suite diff --git a/index.js b/index.js index 859944d..7f40a8f 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,6 @@ var invariant = require('turf-invariant'); +var normalize = require('turf-normalize'); +var flatten = require('turf-flatten'); // http://en.wikipedia.org/wiki/Even%E2%80%93odd_rule // modified from: https://github.com/substack/point-in-polygon/blob/master/index.js @@ -62,33 +64,50 @@ var invariant = require('turf-invariant'); * var isInside2 = turf.inside(pt2, poly); * //=isInside2 */ -module.exports = function(point, polygon) { +module.exports = function(point, surface) { invariant.featureOf(point, 'Point', 'inside'); - var polys = polygon.geometry.coordinates; + + var fc = normalize(flatten(surface)); + + var isInside = false; + for(var i = 0; i < fc.features.length; i++) { + if(fc.features[i].geometry.type === 'Point') { + + } else if(fc.features[i].geometry.type === 'LineString') { + + } else if(fc.features[i].geometry.type === 'Polygon') { + if(pointInPolygon(point, fc.features[i])) { + isInside = true; + break; + } + } + } + return isInside; +}; + +function pointInPolygon (point, polygon) { + var poly = polygon.geometry.coordinates; var pt = [point.geometry.coordinates[0], point.geometry.coordinates[1]]; // normalize to multipolygon - if (polygon.geometry.type === 'Polygon') polys = [polys]; var insidePoly = false; - var i = 0; - while (i < polys.length && !insidePoly) { // check if it is in the outer ring first - if(inRing(pt, polys[i][0])) { - var inHole = false; - var k = 1; - // check for the point in any of the holes - while(k < polys[i].length && !inHole) { - if(inRing(pt, polys[i][k])) { - inHole = true; - } - k++; + if(inRing(pt, poly[0])) { + var inHole = false; + var k = 1; + // check for the point in any of the holes + while(k < poly.length && !inHole) { + if(inRing(pt, poly[k])) { + inHole = true; + break; } - if(!inHole) insidePoly = true; + k++; } - i++; + if(!inHole) insidePoly = true; } + return insidePoly; -}; +} // pt is [x,y] and ring is [[x,y], [x,y],..] function inRing (pt, ring) { diff --git a/package.json b/package.json index 1f91359..6929f46 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,9 @@ "bin": {}, "dependencies": { "minimist": "^1.1.0", - "turf-invariant": "^1.0.3" + "turf-flatten": "^1.0.0", + "turf-invariant": "^1.0.3", + "turf-normalize": "^1.0.2" }, "devDependencies": { "benchmark": "^1.0.0", From e19d2be51bded93ea9ccb3e5cb34ab3668d8e93a Mon Sep 17 00:00:00 2001 From: morganherlocker Date: Wed, 6 May 2015 12:43:15 -0400 Subject: [PATCH 2/5] shortcircuit on polygon to maintain benchmarks --- index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.js b/index.js index 7f40a8f..f7da292 100644 --- a/index.js +++ b/index.js @@ -67,6 +67,8 @@ var flatten = require('turf-flatten'); module.exports = function(point, surface) { invariant.featureOf(point, 'Point', 'inside'); + if(surface.geometry.type === 'Polygon') return pointInPolygon(point, surface) + var fc = normalize(flatten(surface)); var isInside = false; From 1e5aaa9e03e535142ba5003e8069af4f785ec47f Mon Sep 17 00:00:00 2001 From: morganherlocker Date: Wed, 6 May 2015 12:49:43 -0400 Subject: [PATCH 3/5] point in point --- index.js | 12 +++++++++++- test.js | 10 ++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index f7da292..70b0c0e 100644 --- a/index.js +++ b/index.js @@ -67,7 +67,8 @@ var flatten = require('turf-flatten'); module.exports = function(point, surface) { invariant.featureOf(point, 'Point', 'inside'); - if(surface.geometry.type === 'Polygon') return pointInPolygon(point, surface) + if(surface.geometry.type === 'Point') return pointInPoint(point, surface); + if(surface.geometry.type === 'Polygon') return pointInPolygon(point, surface); var fc = normalize(flatten(surface)); @@ -111,6 +112,15 @@ function pointInPolygon (point, polygon) { return insidePoly; } +function pointInPoint(pt1, pt2) { + if(pt1.geometry.coordinates[0] === pt2.geometry.coordinates[0] && + pt1.geometry.coordinates[1] === pt2.geometry.coordinates[1]) { + return true; + } else { + return false; + } +} + // pt is [x,y] and ring is [[x,y], [x,y],..] function inRing (pt, ring) { var isInside = false; diff --git a/test.js b/test.js index 6b235bb..d0cbb8d 100644 --- a/test.js +++ b/test.js @@ -62,3 +62,13 @@ test('multipolygon with hole', function (t) { t.end(); }); + +test('points', function (t) { + var pt2 = point([-86.69208526611328, 36.20373274711739]); + var pt1 = point([-86.72229766845702, 36.20258997094334]); + + t.false(inside(pt1, pt2)); + t.true(inside(pt1, pt1)); + + t.end(); +}); \ No newline at end of file From 2ca3533e8c77d6e8825cc6ff64f4231d8dda18de Mon Sep 17 00:00:00 2001 From: morganherlocker Date: Thu, 7 May 2015 17:08:52 -0400 Subject: [PATCH 4/5] every. single. type. --- index.js | 77 ++++++++++++++++++++++++++++-------- package.json | 12 ++++-- test.js | 109 ++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 165 insertions(+), 33 deletions(-) diff --git a/index.js b/index.js index 70b0c0e..5e678bf 100644 --- a/index.js +++ b/index.js @@ -67,27 +67,42 @@ var flatten = require('turf-flatten'); module.exports = function(point, surface) { invariant.featureOf(point, 'Point', 'inside'); - if(surface.geometry.type === 'Point') return pointInPoint(point, surface); - if(surface.geometry.type === 'Polygon') return pointInPolygon(point, surface); - - var fc = normalize(flatten(surface)); + if(surface.type === 'Feature' && + (surface.geometry.type === 'Polygon' || + surface.geometry.type === 'LineString' || + surface.geometry.type === 'Point')) return pointInSingle(point, surface); + else { + var fc = normalize(flatten(surface)); + var isInside = false; + for(var i = 0; i < fc.features.length; i++) { + if(fc.features[i].geometry.type === 'MultiPolygon' || + fc.features[i].geometry.type === 'MultiLineString' || + fc.features[i].geometry.type === 'MultiPoint') { + var multiFC = flatten(normalize(fc.features[i])); - var isInside = false; - for(var i = 0; i < fc.features.length; i++) { - if(fc.features[i].geometry.type === 'Point') { - - } else if(fc.features[i].geometry.type === 'LineString') { - - } else if(fc.features[i].geometry.type === 'Polygon') { - if(pointInPolygon(point, fc.features[i])) { - isInside = true; - break; + for(var k = 0; k < multiFC.features.length; k++) { + if(pointInSingle(point, multiFC.features[k])) { + isInside = true; + break; + } + } + } else { + if(pointInSingle(point, fc.features[i])) { + isInside = true; + break; + } } } + return isInside; } - return isInside; }; +function pointInSingle(point, feature) { + if(feature.geometry.type === 'Point') return pointInPoint(point, feature); + if(feature.geometry.type === 'LineString') return pointInLineString(point, feature); + else if(feature.geometry.type === 'Polygon') return pointInPolygon(point, feature); +} + function pointInPolygon (point, polygon) { var poly = polygon.geometry.coordinates; var pt = [point.geometry.coordinates[0], point.geometry.coordinates[1]]; @@ -112,7 +127,7 @@ function pointInPolygon (point, polygon) { return insidePoly; } -function pointInPoint(pt1, pt2) { +function pointInPoint (pt1, pt2) { if(pt1.geometry.coordinates[0] === pt2.geometry.coordinates[0] && pt1.geometry.coordinates[1] === pt2.geometry.coordinates[1]) { return true; @@ -121,6 +136,36 @@ function pointInPoint(pt1, pt2) { } } +function pointInLineString (point, line) { + var onLine = false; + var k = 0; + while(!onLine && k < line.geometry.coordinates.length - 1) { + var x = point.geometry.coordinates[0]; + var y = point.geometry.coordinates[1]; + var x1 = line.geometry.coordinates[k][0]; + var y1 = line.geometry.coordinates[k][1]; + var x2 = line.geometry.coordinates[k+1][0]; + var y2 = line.geometry.coordinates[k+1][1]; + if((x === x1 && y === y1) || + (x === x2 && y === y2) || + pointOnSegment(x, y, x1, y1, x2, y2)) { + onLine = true; + break; + } + k++; + } + return onLine; +} + +function pointOnSegment (x, y, x1, y1, x2, y2) { + var ab = Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); + var ap = Math.sqrt((x-x1)*(x-x1)+(y-y1)*(y-y1)); + var pb = Math.sqrt((x2-x)*(x2-x)+(y2-y)*(y2-y)); + if(ab === ap + pb) { + return true; + } +} + // pt is [x,y] and ring is [[x,y], [x,y],..] function inRing (pt, ring) { var isInside = false; diff --git a/package.json b/package.json index 6929f46..3e74900 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,16 @@ }, "devDependencies": { "benchmark": "^1.0.0", + "dox": "^0.6.1", + "doxme": "^1.4.3", "tape": "^3.5.0", + "turf-featurecollection": "^1.0.1", + "turf-geometrycollection": "^1.0.0", + "turf-linestring": "^1.0.2", + "turf-multilinestring": "^1.0.2", + "turf-multipoint": "^1.0.0", + "turf-multipolygon": "^1.0.1", "turf-point": "^2.0.0", - "turf-polygon": "^1.0.2", - "dox": "^0.6.1", - "doxme": "^1.4.3" + "turf-polygon": "^1.0.2" } } diff --git a/test.js b/test.js index d0cbb8d..4d306e9 100644 --- a/test.js +++ b/test.js @@ -1,7 +1,13 @@ var test = require('tape'); var inside = require('./'); var point = require('turf-point'); +var linestring = require('turf-linestring'); var polygon = require('turf-polygon'); +var multipoint = require('turf-multipoint'); +var multilinestring = require('turf-multilinestring'); +var multipolygon = require('turf-multipolygon'); +var geometrycollection = require('turf-geometrycollection'); +var featurecollection = require('turf-featurecollection'); var fs = require('fs'); test('bad type', function (t) { @@ -14,7 +20,7 @@ test('bad type', function (t) { t.end(); }); -test('featureCollection', function (t) { +test('concave/convex polygons', function (t) { // test for a simple polygon var poly = polygon([[[0,0], [0,100], [100,100], [100,0], [0,0]]]); var ptIn = point([50, 50]); @@ -40,9 +46,9 @@ test('poly with hole', function (t) { var ptOutsidePoly = point([-86.75079345703125, 36.18527313913089]); var polyHole = JSON.parse(fs.readFileSync(__dirname + '/fixtures/poly-with-hole.geojson')); - t.false(inside(ptInHole, polyHole)); - t.true(inside(ptInPoly, polyHole)); - t.false(inside(ptOutsidePoly, polyHole)); + t.false(inside(ptInHole, polyHole), 'out'); + t.true(inside(ptInPoly, polyHole), 'in'); + t.false(inside(ptOutsidePoly, polyHole), 'out'); t.end(); }); @@ -54,21 +60,96 @@ test('multipolygon with hole', function (t) { var ptOutsidePoly = point([-86.75302505493164, 36.23015046460186]); var multiPolyHole = JSON.parse(fs.readFileSync(__dirname + '/fixtures/multipoly-with-hole.geojson')); - t.false(inside(ptInHole, multiPolyHole)); - t.true(inside(ptInPoly, multiPolyHole)); - t.true(inside(ptInPoly2, multiPolyHole)); - t.true(inside(ptInPoly, multiPolyHole)); - t.false(inside(ptOutsidePoly, multiPolyHole)); + t.false(inside(ptInHole, multiPolyHole), 'out'); + t.true(inside(ptInPoly, multiPolyHole), 'in'); + t.true(inside(ptInPoly2, multiPolyHole), 'in'); + t.true(inside(ptInPoly, multiPolyHole), 'in'); + t.false(inside(ptOutsidePoly, multiPolyHole), 'out'); t.end(); }); -test('points', function (t) { - var pt2 = point([-86.69208526611328, 36.20373274711739]); - var pt1 = point([-86.72229766845702, 36.20258997094334]); +test('point', function (t) { + var pt1 = point([-86.69208526611328, 36.20373274711739]); + var pt2 = point([-86.72229766845702, 36.20258997094334]); - t.false(inside(pt1, pt2)); - t.true(inside(pt1, pt1)); + t.true(inside(pt1, pt1), 'in'); + t.false(inside(pt1, pt2), 'out'); + + t.end(); +}); + +test('linestring', function (t) { + var pt = point([1,1]); + var line1 = linestring([[86,42],[66,25],[93,23],[0,16],[-40,5],[16,-20],[1,1]], 'in'); + var line2 = linestring([[86,42],[66,25],[93,23],[86,42],[-54,63],[-80,53],[-60,52],[-54,63]], 'out'); + + t.true(inside(pt, line1), 'in'); + t.false(inside(pt, line2), 'out'); + + t.end(); +}); + +test('multipoint', function (t) { + var pt = point([1,1]); + var multipt1 = multipoint([[0,0], [1,1]], 'in'); + var multipt2 = multipoint([[0,0], [2,2]], 'out'); + + t.true(inside(pt, multipt1), 'in'); + t.false(inside(pt, multipt2), 'out'); + + t.end(); +}); + +test('multilinestring', function (t) { + var pt = point([1,1]); + var multiline1 = multilinestring([[[86,42],[66,25],[93,23]], [[1,1],[-40,5],[16,-20],[0,16]]], 'in'); + var multiline2 = multilinestring([[[86,42],[66,25],[93,23]], [[-54,63],[-80,53],[-60,52]]], 'out'); + + t.true(inside(pt, multiline1), 'in'); + t.false(inside(pt, multiline2), 'out'); + + t.end(); +}); + +test('multipolygon', function (t) { + var pt = point([1,1]); + var multipoly1 = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[0,16],[-40,5],[16,-20],[0,16]]]], 'in'); + var multipoly2 = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[-54,63],[-80,53],[-60,52],[-54,63]]]], 'out'); + + t.true(inside(pt, multipoly1), 'in'); + t.false(inside(pt, multipoly2), 'out'); + + t.end(); +}); + +test('geometrycollection', function (t) { + var pt = point([1,1]); + var multipoly1 = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[0,16],[-40,5],[16,-20],[0,16]]]]); + var multipoly2 = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[-54,63],[-80,53],[-60,52],[-54,63]]]]); + + var gc1 = geometrycollection([point([0,0]).geometry, multipoly1.geometry]); + var gc2 = geometrycollection([point([0,0]).geometry, multipoly2.geometry]); + var gc3 = geometrycollection([point([1,1]).geometry, multipoly2.geometry]); + t.true(inside(pt, gc1), 'in'); + t.false(inside(pt, gc2), 'out'); + t.true(inside(pt, gc3), 'in'); + + t.end(); +}); + +test('featurecollection', function (t) { + var pt = point([1,1]); + var multipoly1 = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[0,16],[-40,5],[16,-20],[0,16]]]]); + var multipoly2 = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[-54,63],[-80,53],[-60,52],[-54,63]]]]); + + var fc1 = featurecollection([point([0,0]), multipoly1]); + var fc2 = featurecollection([point([0,0]), multipoly2]); + var fc3 = featurecollection([point([1,1]), multipoly2]); + + t.true(inside(pt, fc1), 'in'); + t.false(inside(pt, fc2), 'out'); + t.true(inside(pt, fc3), 'in'); t.end(); }); \ No newline at end of file From d8b4d9383c220ad1a7606d49bccadee915da153e Mon Sep 17 00:00:00 2001 From: morganherlocker Date: Thu, 7 May 2015 17:18:23 -0400 Subject: [PATCH 5/5] bench more --- bench.js | 36 +++++++++++++++++++++++++++++++++--- test.js | 12 ++++++------ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/bench.js b/bench.js index 8d18579..c7e4b75 100644 --- a/bench.js +++ b/bench.js @@ -2,17 +2,47 @@ var inside = require('./'); var Benchmark = require('benchmark'); var fs = require('fs'); var point = require('turf-point'); +var linestring = require('turf-linestring'); var polygon = require('turf-polygon'); +var multipoint = require('turf-multipoint'); +var multilinestring = require('turf-multilinestring'); +var multipolygon = require('turf-multipolygon'); +var geometrycollection = require('turf-geometrycollection'); +var featurecollection = require('turf-featurecollection'); -var poly = polygon([[[0,0], [0,100], [100,100], [100,0], [0,0]]]); var ptIn = point([50, 50]); -var ptOut = point([140, 150]); + +var pt = point([50, 50]); +var multipt = multipoint([[0,0], [50,50]]); +var line = linestring([[86,42],[66,25],[93,23],[0,16],[-40,5],[16,-20],[1,1]]); +var multiline = multilinestring([[[86,42],[66,25],[93,23]], [[1,1],[-40,5],[16,-20],[0,16]]]); +var poly = polygon([[[0,0], [0,100], [100,100], [100,0], [0,0]]]); +var multipoly = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[0,16],[-40,5],[16,-20],[0,16]]]]); +var fc = featurecollection([poly, multiline]); var suite = new Benchmark.Suite('turf-inside'); suite - .add('turf-inside',function () { + .add('turf-inside - point',function () { + inside(ptIn, pt); + }) + .add('turf-inside - multipoint',function () { + inside(ptIn, multipt); + }) + .add('turf-inside - linestring',function () { + inside(ptIn, line); + }) + .add('turf-inside - multilinestring',function () { + inside(ptIn, multiline); + }) + .add('turf-inside - polygon',function () { inside(ptIn, poly); }) + .add('turf-inside - multipolygon',function () { + inside(ptIn, multipoly); + }) + .add('turf-inside - featurecollection',function () { + inside(ptIn, fc); + }) .on('cycle', function (event) { console.log(String(event.target)); }) diff --git a/test.js b/test.js index 4d306e9..6be05dd 100644 --- a/test.js +++ b/test.js @@ -81,8 +81,8 @@ test('point', function (t) { test('linestring', function (t) { var pt = point([1,1]); - var line1 = linestring([[86,42],[66,25],[93,23],[0,16],[-40,5],[16,-20],[1,1]], 'in'); - var line2 = linestring([[86,42],[66,25],[93,23],[86,42],[-54,63],[-80,53],[-60,52],[-54,63]], 'out'); + var line1 = linestring([[86,42],[66,25],[93,23],[0,16],[-40,5],[16,-20],[1,1]]); + var line2 = linestring([[86,42],[66,25],[93,23],[86,42],[-54,63],[-80,53],[-60,52],[-54,63]]); t.true(inside(pt, line1), 'in'); t.false(inside(pt, line2), 'out'); @@ -103,8 +103,8 @@ test('multipoint', function (t) { test('multilinestring', function (t) { var pt = point([1,1]); - var multiline1 = multilinestring([[[86,42],[66,25],[93,23]], [[1,1],[-40,5],[16,-20],[0,16]]], 'in'); - var multiline2 = multilinestring([[[86,42],[66,25],[93,23]], [[-54,63],[-80,53],[-60,52]]], 'out'); + var multiline1 = multilinestring([[[86,42],[66,25],[93,23]], [[1,1],[-40,5],[16,-20],[0,16]]]); + var multiline2 = multilinestring([[[86,42],[66,25],[93,23]], [[-54,63],[-80,53],[-60,52]]]); t.true(inside(pt, multiline1), 'in'); t.false(inside(pt, multiline2), 'out'); @@ -114,8 +114,8 @@ test('multilinestring', function (t) { test('multipolygon', function (t) { var pt = point([1,1]); - var multipoly1 = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[0,16],[-40,5],[16,-20],[0,16]]]], 'in'); - var multipoly2 = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[-54,63],[-80,53],[-60,52],[-54,63]]]], 'out'); + var multipoly1 = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[0,16],[-40,5],[16,-20],[0,16]]]]); + var multipoly2 = multipolygon([[[[86,42],[66,25],[93,23],[86,42]]], [[[-54,63],[-80,53],[-60,52],[-54,63]]]]); t.true(inside(pt, multipoly1), 'in'); t.false(inside(pt, multipoly2), 'out');