From c36f1ddc8b7c577661dfcfb57bae274f7605cb8a Mon Sep 17 00:00:00 2001 From: Ondrej Zara Date: Tue, 15 Jan 2019 10:03:34 +0100 Subject: [PATCH 1/2] test and fix for #154 --- src/contains.js | 14 ++++++++++---- test/contains-test.js | 9 +++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/contains.js b/src/contains.js index 952a62d..ab76bf8 100644 --- a/src/contains.js +++ b/src/contains.js @@ -58,13 +58,19 @@ function containsPoint(coordinates, point) { return distance(coordinates, point) === 0; } -function containsLine(coordinates, point) { - var ab = distance(coordinates[0], coordinates[1]), - ao = distance(coordinates[0], point), - ob = distance(point, coordinates[1]); +function containsLineSegment(a, b, point) { + var ab = distance(a, b), + ao = distance(a, point), + ob = distance(point, b); return ao + ob <= ab + epsilon; } +function containsLine(coordinates, point) { + var i = -1, n = coordinates.length-1; + while (++i < n) if (containsLineSegment(coordinates[i], coordinates[i+1], point)) return true; + return false; +} + function containsPolygon(coordinates, point) { return !!polygonContains(coordinates.map(ringRadians), pointRadians(point)); } diff --git a/test/contains-test.js b/test/contains-test.js index 94f7c53..525408b 100644 --- a/test/contains-test.js +++ b/test/contains-test.js @@ -113,3 +113,12 @@ tape("null contains nothing", function(test) { test.equal(d3.geoContains(null, [0, 0]), false); 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(); +}); From 7a6cf8c31b02ff64529ebafa1a733d443e3653f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Fri, 25 Jan 2019 12:30:06 +0100 Subject: [PATCH 2/2] Optimize by not computing the same distance twice for each point. In the tests, add points that do not belong to the line's coordinates, and some that do. A review of the formula based on https://beta.observablehq.com/d/39e5f445a82cbff8 --- src/contains.js | 27 +++++++++++++++++---------- test/contains-test.js | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/contains.js b/src/contains.js index ab76bf8..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) { @@ -58,16 +58,23 @@ function containsPoint(coordinates, point) { return distance(coordinates, point) === 0; } -function containsLineSegment(a, b, point) { - var ab = distance(a, b), - ao = distance(a, point), - ob = distance(point, b); - return ao + ob <= ab + epsilon; -} - function containsLine(coordinates, point) { - var i = -1, n = coordinates.length-1; - while (++i < n) if (containsLineSegment(coordinates[i], coordinates[i+1], point)) return true; + 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; } diff --git a/test/contains-test.js b/test/contains-test.js index 525408b..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); @@ -114,11 +143,3 @@ tape("null contains nothing", 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(); -});