From 666a010e3b220c0d78218eacf63674c7c5eb9d21 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 7 May 2013 10:49:51 +0200 Subject: [PATCH 1/2] Point-in-polygon improvements As suggested by @tschaub in #674, geom.pointInPoly is not needed if we have geom.LinearRing#containsCoordinate. This pull request also adds tests and documentation on the limitations of the containsCoordinate method. I think for now it is ok to keep geometry/topology functions as simple as possible in ol3. If we decide to not rely on third party libraries like jsts for topology operations, we can always refine what we have and e.g. port topology operations over from ol2. --- examples/vector-layer.html | 2 +- src/ol/geom/base.js | 28 ------------ src/ol/geom/linearring.js | 30 +++++++++++++ src/ol/geom/polygon.js | 22 +++++----- test/spec/ol/geom/linearring.test.js | 66 ++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 39 deletions(-) diff --git a/examples/vector-layer.html b/examples/vector-layer.html index c34ff9cde5..6cea238384 100644 --- a/examples/vector-layer.html +++ b/examples/vector-layer.html @@ -37,7 +37,7 @@

Vector layer example

-

Example of a vector layer.

+

Example of a countries vector layer with country information on hover.

See the vector-layer.js source to see how this is done.

diff --git a/src/ol/geom/base.js b/src/ol/geom/base.js index 1b8d3e4a04..24c2ee75fe 100644 --- a/src/ol/geom/base.js +++ b/src/ol/geom/base.js @@ -42,31 +42,3 @@ ol.geom.squaredDistanceToSegment = function(coordinate, segment) { return ol.coordinate.squaredDistance(coordinate, [v[0] + t * (w[0] - v[0]), v[1] + t * (w[1] - v[1])]); }; - - -/** - * Calculate whether a point falls inside a polygon. - * - * @param {ol.Coordinate} coordinate Coordinate of the point. - * @param {Array.} vertices Vertices of the polygon. - * @return {boolean} Whether the point falls inside the polygon. - */ -ol.geom.pointInPolygon = function(coordinate, vertices) { - // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html - var x = coordinate[0], y = coordinate[1]; - var inside = false; - var xi, yi, xj, yj, intersect; - var numVertices = vertices.length; - for (var i = 0, j = numVertices - 1; i < numVertices; j = i++) { - xi = vertices[i][0]; - yi = vertices[i][1]; - xj = vertices[j][0]; - yj = vertices[j][1]; - intersect = ((yi > y) != (yj > y)) && - (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - if (intersect) { - inside = !inside; - } - } - return inside; -}; diff --git a/src/ol/geom/linearring.js b/src/ol/geom/linearring.js index 0d2180be87..8437fd8543 100644 --- a/src/ol/geom/linearring.js +++ b/src/ol/geom/linearring.js @@ -33,3 +33,33 @@ goog.inherits(ol.geom.LinearRing, ol.geom.LineString); ol.geom.LinearRing.prototype.getType = function() { return ol.geom.GeometryType.LINEARRING; }; + + +/** + * Check whether a given coordinate is inside this ring. Note that this is a + * fast and simple check - points on an edge or vertex of the ring are either + * classified inside or outside. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @return {boolean} Whether the coordinate is inside the ring. + */ +ol.geom.LinearRing.prototype.containsCoordinate = function(coordinate) { + // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + var x = coordinate[0], y = coordinate[1]; + var vertices = this.getCoordinates(); + var inside = false; + var xi, yi, xj, yj, intersect; + var numVertices = vertices.length; + for (var i = 0, j = numVertices - 1; i < numVertices; j = i++) { + xi = vertices[i][0]; + yi = vertices[i][1]; + xj = vertices[j][0]; + yj = vertices[j][1]; + intersect = ((yi > y) != (yj > y)) && + (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + if (intersect) { + inside = !inside; + } + } + return inside; +}; diff --git a/src/ol/geom/polygon.js b/src/ol/geom/polygon.js index 383cdebd98..57fd12ffd8 100644 --- a/src/ol/geom/polygon.js +++ b/src/ol/geom/polygon.js @@ -91,22 +91,24 @@ ol.geom.Polygon.prototype.getType = function() { /** - * Check whether a given coordinate is inside this polygon. + * Check whether a given coordinate is inside this polygon. Note that this is a + * fast and simple check - points on an edge or vertex of the polygon or one of + * its inner rings are either classified inside or outside. * * @param {ol.Coordinate} coordinate Coordinate. * @return {boolean} Whether the coordinate is inside the polygon. */ ol.geom.Polygon.prototype.containsCoordinate = function(coordinate) { var rings = this.rings; - var containsCoordinate = ol.geom.pointInPolygon(coordinate, - rings[0].getCoordinates()); - if (containsCoordinate) { - // if inner ring contains point, polygon does not contain it - for (var i = 1, ii = rings.length; i < ii; ++i) { - if (ol.geom.pointInPolygon(coordinate, rings[i].getCoordinates())) { - containsCoordinate = false; - break; - } + var containsCoordinate; + for (var i = 0, ii = rings.length; i < ii; ++i) { + containsCoordinate = rings[i].containsCoordinate(coordinate); + // if inner ring (i > 0) contains coordinate, polygon does not contain it + if (i > 0) { + containsCoordinate = !containsCoordinate; + } + if (!containsCoordinate) { + break; } } return containsCoordinate; diff --git a/test/spec/ol/geom/linearring.test.js b/test/spec/ol/geom/linearring.test.js index 402dfbf874..1c3380deff 100644 --- a/test/spec/ol/geom/linearring.test.js +++ b/test/spec/ol/geom/linearring.test.js @@ -40,6 +40,72 @@ describe('ol.geom.LinearRing', function() { }); + describe('#containsCoordinate()', function() { + + it('knows when a point coordinate is inside a ring', function() { + /** + * The ring: + * edge 3 + * (5, 10) __________ (15, 10) + * / / + * edge 4 / / edge 2 + * / / + * (0, 0) /_________/ (10, 0) + * edge 1 + */ + var ring = new ol.geom.LinearRing( + [[0, 0], [10, 0], [15, 10], [5, 10]]); + + // contains: 1 (touches - not implemented), true (within), false (outside) + var cases = [{ + point: [5, 5], contains: true + }, { + point: [20, 20], contains: false + }, { + point: [15, 15], contains: false + }/*, { + point: [0, 0], contains: 1 // lower left corner + }, { + point: [10, 0], contains: 1 // lower right corner + }, { + point: [15, 10], contains: 1 // upper right corner + }, { + point: [5, 10], contains: 1 // upper left corner + }, { + point: [5, 0], contains: 1 // on edge 1 + }*/, { + point: [5, -0.1], contains: false // below edge 1 + }, { + point: [5, 0.1], contains: true // above edge 1 + }/*, { + point: [12.5, 5], contains: 1 // on edge 2 + }*/, { + point: [12.4, 5], contains: true // left of edge 2 + }, { + point: [12.6, 5], contains: false // right of edge 2 + }/*, { + point: [10, 10], contains: 1 // on edge 3 + }*/, { + point: [10, 9.9], contains: true // below edge 3 + }, { + point: [10, 10.1], contains: false // above edge 3 + }/*, { + point: [2.5, 5], contains: 1 // on edge 4 + }*/, { + point: [2.4, 5], contains: false // left of edge 4 + }, { + point: [2.6, 5], contains: true // right of edge 4 + }]; + + var len = cases.length; + var c; + for (var i=0; i Date: Tue, 7 May 2013 14:20:09 +0200 Subject: [PATCH 2/2] Entertaining linter and compiler --- src/ol/geom/polygon.js | 1 + test/spec/ol/geom/linearring.test.js | 41 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/ol/geom/polygon.js b/src/ol/geom/polygon.js index 57fd12ffd8..7da4f2a82a 100644 --- a/src/ol/geom/polygon.js +++ b/src/ol/geom/polygon.js @@ -100,6 +100,7 @@ ol.geom.Polygon.prototype.getType = function() { */ ol.geom.Polygon.prototype.containsCoordinate = function(coordinate) { var rings = this.rings; + /** @type {boolean} */ var containsCoordinate; for (var i = 0, ii = rings.length; i < ii; ++i) { containsCoordinate = rings[i].containsCoordinate(coordinate); diff --git a/test/spec/ol/geom/linearring.test.js b/test/spec/ol/geom/linearring.test.js index 1c3380deff..118b076f36 100644 --- a/test/spec/ol/geom/linearring.test.js +++ b/test/spec/ol/geom/linearring.test.js @@ -58,48 +58,47 @@ describe('ol.geom.LinearRing', function() { // contains: 1 (touches - not implemented), true (within), false (outside) var cases = [{ - point: [5, 5], contains: true + point: [5, 5], contains: true }, { - point: [20, 20], contains: false + point: [20, 20], contains: false }, { - point: [15, 15], contains: false + point: [15, 15], contains: false }/*, { - point: [0, 0], contains: 1 // lower left corner + point: [0, 0], contains: 1 // lower left corner }, { - point: [10, 0], contains: 1 // lower right corner + point: [10, 0], contains: 1 // lower right corner }, { - point: [15, 10], contains: 1 // upper right corner + point: [15, 10], contains: 1 // upper right corner }, { - point: [5, 10], contains: 1 // upper left corner + point: [5, 10], contains: 1 // upper left corner }, { - point: [5, 0], contains: 1 // on edge 1 + point: [5, 0], contains: 1 // on edge 1 }*/, { - point: [5, -0.1], contains: false // below edge 1 + point: [5, -0.1], contains: false // below edge 1 }, { - point: [5, 0.1], contains: true // above edge 1 + point: [5, 0.1], contains: true // above edge 1 }/*, { - point: [12.5, 5], contains: 1 // on edge 2 + point: [12.5, 5], contains: 1 // on edge 2 }*/, { - point: [12.4, 5], contains: true // left of edge 2 + point: [12.4, 5], contains: true // left of edge 2 }, { - point: [12.6, 5], contains: false // right of edge 2 + point: [12.6, 5], contains: false // right of edge 2 }/*, { - point: [10, 10], contains: 1 // on edge 3 + point: [10, 10], contains: 1 // on edge 3 }*/, { - point: [10, 9.9], contains: true // below edge 3 + point: [10, 9.9], contains: true // below edge 3 }, { - point: [10, 10.1], contains: false // above edge 3 + point: [10, 10.1], contains: false // above edge 3 }/*, { - point: [2.5, 5], contains: 1 // on edge 4 + point: [2.5, 5], contains: 1 // on edge 4 }*/, { - point: [2.4, 5], contains: false // left of edge 4 + point: [2.4, 5], contains: false // left of edge 4 }, { - point: [2.6, 5], contains: true // right of edge 4 + point: [2.6, 5], contains: true // right of edge 4 }]; - var len = cases.length; var c; - for (var i=0; i