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.
This commit is contained in:
ahocevar
2013-05-07 10:49:51 +02:00
parent aa3cc0cec4
commit 666a010e3b
5 changed files with 109 additions and 39 deletions

View File

@@ -37,7 +37,7 @@
<div class="span4"> <div class="span4">
<h4 id="title">Vector layer example</h4> <h4 id="title">Vector layer example</h4>
<p id="shortdesc">Example of a vector layer.</p> <p id="shortdesc">Example of a countries vector layer with country information on hover.</p>
<div id="docs"> <div id="docs">
<p>See the <a href="vector-layer.js" target="_blank">vector-layer.js source</a> to see how this is done.</p> <p>See the <a href="vector-layer.js" target="_blank">vector-layer.js source</a> to see how this is done.</p>
</div> </div>

View File

@@ -42,31 +42,3 @@ ol.geom.squaredDistanceToSegment = function(coordinate, segment) {
return ol.coordinate.squaredDistance(coordinate, return ol.coordinate.squaredDistance(coordinate,
[v[0] + t * (w[0] - v[0]), v[1] + t * (w[1] - v[1])]); [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.<ol.Coordinate>} 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;
};

View File

@@ -33,3 +33,33 @@ goog.inherits(ol.geom.LinearRing, ol.geom.LineString);
ol.geom.LinearRing.prototype.getType = function() { ol.geom.LinearRing.prototype.getType = function() {
return ol.geom.GeometryType.LINEARRING; 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;
};

View File

@@ -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. * @param {ol.Coordinate} coordinate Coordinate.
* @return {boolean} Whether the coordinate is inside the polygon. * @return {boolean} Whether the coordinate is inside the polygon.
*/ */
ol.geom.Polygon.prototype.containsCoordinate = function(coordinate) { ol.geom.Polygon.prototype.containsCoordinate = function(coordinate) {
var rings = this.rings; var rings = this.rings;
var containsCoordinate = ol.geom.pointInPolygon(coordinate, var containsCoordinate;
rings[0].getCoordinates()); for (var i = 0, ii = rings.length; i < ii; ++i) {
if (containsCoordinate) { containsCoordinate = rings[i].containsCoordinate(coordinate);
// if inner ring contains point, polygon does not contain it // if inner ring (i > 0) contains coordinate, polygon does not contain it
for (var i = 1, ii = rings.length; i < ii; ++i) { if (i > 0) {
if (ol.geom.pointInPolygon(coordinate, rings[i].getCoordinates())) { containsCoordinate = !containsCoordinate;
containsCoordinate = false;
break;
} }
if (!containsCoordinate) {
break;
} }
} }
return containsCoordinate; return containsCoordinate;

View File

@@ -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<len; ++i) {
c = cases[i];
expect(ring.containsCoordinate(c.point)).to.be(c.contains);
}
});
});
}); });
goog.require('ol.geom.LinearRing'); goog.require('ol.geom.LinearRing');