diff --git a/bench.js b/bench.js index d27f1df..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]]]); -var ptIn = point(50, 50); -var ptOut = point(140, 150); +var ptIn = point([50, 50]); + +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/index.js b/index.js index 859944d..5e678bf 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,107 @@ 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; + + 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])); + + 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; + } +}; + +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]]; // 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; -}; +} + +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; + } +} + +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) { diff --git a/package.json b/package.json index 1f91359..3e74900 100644 --- a/package.json +++ b/package.json @@ -28,14 +28,22 @@ "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", + "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 6b235bb..6be05dd 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,11 +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('point', function (t) { + var pt1 = point([-86.69208526611328, 36.20373274711739]); + var pt2 = point([-86.72229766845702, 36.20258997094334]); + + 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]]); + 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'); + + 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]]]); + 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'); + + 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]]]]); + 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'); + + 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