diff --git a/src/contains.js b/src/contains.js index 952a62d..0a2f710 100644 --- a/src/contains.js +++ b/src/contains.js @@ -1,6 +1,6 @@ import {default as polygonContains} from "./polygonContains"; import {default as distance} from "./distance"; -import {epsilon, radians} from "./math"; +import {epsilon2, radians} from "./math"; var containsObjectType = { Feature: function(object, point) { @@ -59,10 +59,23 @@ function containsPoint(coordinates, point) { } function containsLine(coordinates, point) { - var ab = distance(coordinates[0], coordinates[1]), - ao = distance(coordinates[0], point), - ob = distance(point, coordinates[1]); - return ao + ob <= ab + epsilon; + var ao, bo, ab; + for (var i = 0, n = coordinates.length; i < n; i++) { + bo = distance(coordinates[i], point); + if (bo === 0) return true; + if (i > 0) { + ab = distance(coordinates[i], coordinates[i - 1]); + if ( + ab > 0 && + ao <= ab && + bo <= ab && + (ao + bo - ab) * (1 - ((ao - bo) / ab) ** 2) < epsilon2 * ab + ) + return true; + } + ao = bo; + } + return false; } function containsPolygon(coordinates, point) { diff --git a/test/contains-test.js b/test/contains-test.js index 94f7c53..7864963 100644 --- a/test/contains-test.js +++ b/test/contains-test.js @@ -31,6 +31,35 @@ tape("a LineString contains any point on the Great Circle path", function(test) test.end(); }); +tape("a LineString with 2+ points contains those points", function(test) { + var points = [[0, 0], [1,2], [3, 4], [5, 6]]; + var feature = {type: "LineString", coordinates: points}; + points.forEach(point => { + test.equal(d3.geoContains(feature, point), true); + }); + test.end(); +}); + +tape("a LineString contains epsilon-distant points", function(test) { + var epsilon = 1e-6; + var line = [[0, 0], [0, 10], [10, 10], [10, 0]]; + var points = [[0, 5], [epsilon * 1, 5], [0, epsilon], [epsilon * 1, epsilon]]; + points.forEach(point => { + test.true(d3.geoContains({type:"LineString", coordinates: line}, point)); + }); + test.end(); +}); + +tape("a LineString does not contain 10*epsilon-distant points", function(test) { + var epsilon = 1e-6; + var line = [[0, 0], [0, 10], [10, 10], [10, 0]]; + var points = [[epsilon * 10, 5], [epsilon * 10, epsilon]]; + points.forEach(point => { + test.false(d3.geoContains({type:"LineString", coordinates: line}, point)); + }); + test.end(); +}); + tape("a MultiLineString contains any point on one of its components", function(test) { test.equal(d3.geoContains({type: "MultiLineString", coordinates: [[[0, 0], [1,2]], [[2, 3], [4,5]]]}, [2, 3]), true); test.equal(d3.geoContains({type: "MultiLineString", coordinates: [[[0, 0], [1,2]], [[2, 3], [4,5]]]}, [5, 6]), false); @@ -113,3 +142,4 @@ tape("null contains nothing", function(test) { test.equal(d3.geoContains(null, [0, 0]), false); test.end(); }); +