diff --git a/lib/OpenLayers/Geometry.js b/lib/OpenLayers/Geometry.js index 3de74ad7ae..61db5dab6a 100644 --- a/lib/OpenLayers/Geometry.js +++ b/lib/OpenLayers/Geometry.js @@ -128,6 +128,28 @@ OpenLayers.Geometry = OpenLayers.Class({ // }, + /** + * APIMethod: distanceTo + * Calculate the closest distance between two geometries. + * + * Parameters: + * geometry - {} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options depend on the specific geometry type. + * + * Returns: + * {Number | Object} The distance between this geometry and the target. + * If details is true, the return will be an object with distance, + * x0, y0, x1, and x2 properties. The x0 and y0 properties represent + * the coordinates of the closest point on this geometry. The x1 and y1 + * properties represent the coordinates of the closest point on the + * target geometry. + */ + distanceTo: function(geometry, options) { + }, + /** * Method: atPoint * Note - This is only an approximation based on the bounds of the @@ -296,3 +318,46 @@ OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, point) { } return intersection; }; + +/** + * Function: OpenLayers.Geometry.distanceToSegment + * + * Parameters: + * point - {Object} An object with x and y properties representing the + * point coordinates. + * segment - {Object} An object with x1, y1, x2, and y2 properties + * representing endpoint coordinates. + * + * Returns: + * {Object} An object with distance, x, and y properties. The distance + * will be the shortest distance between the input point and segment. + * The x and y properties represent the coordinates along the segment + * where the shortest distance meets the segment. + */ +OpenLayers.Geometry.distanceToSegment = function(point, segment) { + var x0 = point.x; + var y0 = point.y; + var x1 = segment.x1; + var y1 = segment.y1; + var x2 = segment.x2; + var y2 = segment.y2; + var dx = x2 - x1; + var dy = y2 - y1; + var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) / + (Math.pow(dx, 2) + Math.pow(dy, 2)); + var x, y; + if(along <= 0.0) { + x = x1; + y = y1; + } else if(along >= 1.0) { + x = x2; + y = y2; + } else { + x = x1 + along * dx; + y = y1 + along * dy; + } + return { + distance: Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2)), + x: x, y: y + }; +}; diff --git a/lib/OpenLayers/Geometry/Collection.js b/lib/OpenLayers/Geometry/Collection.js index 7b98240885..d62866ceb7 100644 --- a/lib/OpenLayers/Geometry/Collection.js +++ b/lib/OpenLayers/Geometry/Collection.js @@ -279,6 +279,52 @@ OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, { } }, + /** + * APIMethod: distanceTo + * Calculate the closest distance between two geometries. + * + * Parameters: + * geometry - {} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options: + * details - {Boolean} Return details from the distance calculation. + * Default is false. + * edge - {Boolean} Calculate the distance from this geometry to the + * nearest edge of the target geometry. Default is true. If true, + * calling distanceTo from a geometry that is wholly contained within + * the target will result in a non-zero distance. If false, whenever + * geometries intersect, calling distanceTo will return 0. If false, + * details cannot be returned. + * + * Returns: + * {Number | Object} The distance between this geometry and the target. + * If details is true, the return will be an object with distance, + * x0, y0, x1, and y1 properties. The x0 and y0 properties represent + * the coordinates of the closest point on this geometry. The x1 and y1 + * properties represent the coordinates of the closest point on the + * target geometry. + */ + distanceTo: function(geometry, options) { + var edge = !(options && options.edge === false); + var details = edge && options && options.details; + var result, best; + var min = Number.POSITIVE_INFINITY; + for(var i=0, len=this.components.length; i} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options: + * details - {Boolean} Return details from the distance calculation. + * Default is false. + * edge - {Boolean} Calculate the distance from this geometry to the + * nearest edge of the target geometry. Default is true. If true, + * calling distanceTo from a geometry that is wholly contained within + * the target will result in a non-zero distance. If false, whenever + * geometries intersect, calling distanceTo will return 0. If false, + * details cannot be returned. + * + * Returns: + * {Number | Object} The distance between this geometry and the target. + * If details is true, the return will be an object with distance, + * x0, y0, x1, and x2 properties. The x0 and y0 properties represent + * the coordinates of the closest point on this geometry. The x1 and y1 + * properties represent the coordinates of the closest point on the + * target geometry. + */ + distanceTo: function(geometry, options) { + var edge = !(options && options.edge === false); + var details = edge && options && options.details; + var result, best = {}; + var min = Number.POSITIVE_INFINITY; + if(geometry instanceof OpenLayers.Geometry.Point) { + var segs = this.getSortedSegments(); + var x = geometry.x; + var y = geometry.y; + var seg; + for(var i=0, len=segs.length; i x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) { + break; + } + } + } + if(details) { + best = { + distance: best.distance, + x0: best.x, y0: best.y, + x1: x, y1: y + }; + } else { + best = best.distance; + } + } else if(geometry instanceof OpenLayers.Geometry.LineString) { + var segs0 = this.getSortedSegments(); + var segs1 = geometry.getSortedSegments(); + var seg0, seg1, intersection, x0, y0; + var len1 = segs1.length; + outer: for(var i=0, len=segs0.length; i} + * geometry - {} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options: + * details - {Boolean} Return details from the distance calculation. + * Default is false. + * edge - {Boolean} Calculate the distance from this geometry to the + * nearest edge of the target geometry. Default is true. If true, + * calling distanceTo from a geometry that is wholly contained within + * the target will result in a non-zero distance. If false, whenever + * geometries intersect, calling distanceTo will return 0. If false, + * details cannot be returned. + * + * Returns: + * {Number | Object} The distance between this geometry and the target. + * If details is true, the return will be an object with distance, + * x0, y0, x1, and x2 properties. The x0 and y0 properties represent + * the coordinates of the closest point on this geometry. The x1 and y1 + * properties represent the coordinates of the closest point on the + * target geometry. */ - distanceTo: function(point) { - var distance = 0.0; - if ( (this.x != null) && (this.y != null) && - (point != null) && (point.x != null) && (point.y != null) ) { - - var dx2 = Math.pow(this.x - point.x, 2); - var dy2 = Math.pow(this.y - point.y, 2); - distance = Math.sqrt( dx2 + dy2 ); + distanceTo: function(geometry, options) { + var edge = !(options && options.edge === false); + var details = edge && options && options.details; + var distance, x0, y0, x1, y1, result; + if(geometry instanceof OpenLayers.Geometry.Point) { + x0 = this.x; + y0 = this.y; + x1 = geometry.x; + y1 = geometry.y; + distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2)); + result = !details ? + distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance}; + } else { + result = geometry.distanceTo(this, options); + if(details) { + // switch coord order since this geom is target + result = { + x0: result.x1, y0: result.y1, + x1: result.x0, y1: result.y0, + distance: result.distance + }; + } } - return distance; + return result; }, /** diff --git a/lib/OpenLayers/Geometry/Polygon.js b/lib/OpenLayers/Geometry/Polygon.js index 9c6714bd61..b41f727fa6 100644 --- a/lib/OpenLayers/Geometry/Polygon.js +++ b/lib/OpenLayers/Geometry/Polygon.js @@ -156,6 +156,47 @@ OpenLayers.Geometry.Polygon = OpenLayers.Class( return intersect; }, + /** + * APIMethod: distanceTo + * Calculate the closest distance between two geometries. + * + * Parameters: + * geometry - {} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options: + * details - {Boolean} Return details from the distance calculation. + * Default is false. + * edge - {Boolean} Calculate the distance from this geometry to the + * nearest edge of the target geometry. Default is true. If true, + * calling distanceTo from a geometry that is wholly contained within + * the target will result in a non-zero distance. If false, whenever + * geometries intersect, calling distanceTo will return 0. If false, + * details cannot be returned. + * + * Returns: + * {Number | Object} The distance between this geometry and the target. + * If details is true, the return will be an object with distance, + * x0, y0, x1, and y1 properties. The x0 and y0 properties represent + * the coordinates of the closest point on this geometry. The x1 and y1 + * properties represent the coordinates of the closest point on the + * target geometry. + */ + distanceTo: function(geometry, options) { + var edge = !(options && options.edge === false); + var result; + // this is the case where we might not be looking for distance to edge + if(!edge && this.intersects(geometry)) { + result = 0; + } else { + result = OpenLayers.Geometry.Collection.prototype.distanceTo.apply( + this, [geometry, options] + ); + } + return result; + }, + CLASS_NAME: "OpenLayers.Geometry.Polygon" }); diff --git a/tests/Geometry.html b/tests/Geometry.html index a9b9bfcd60..f133061e04 100644 --- a/tests/Geometry.html +++ b/tests/Geometry.html @@ -250,7 +250,37 @@ t.fail(failures[f]); } } - } + } + + function test_distanceToSegment(t) { + var dist = OpenLayers.Geometry.distanceToSegment; + + var cases = [{ + got: dist({x: 0, y: 0}, {x1: 0, y1: 1, x2: 1, y2: 1}), + expected: {distance: 1, x: 0, y: 1} + }, { + got: dist({x: 0, y: 0}, {x1: -1, y1: -1, x2: 0, y2: -1}), + expected: {distance: 1, x: 0, y: -1} + }, { + got: dist({x: 0, y: 0}, {x1: -1, y1: -1, x2: 1, y2: 1}), + expected: {distance: 0, x: 0, y: 0} + }, { + got: dist({x: 1, y: 1}, {x1: 2, y1: 0, x2: 2, y2: 3}), + expected: {distance: 1, x: 2, y: 1} + }, { + got: dist({x: -1, y: -1}, {x1: -2, y1: -2, x2: -1, y2: -3}), + expected: {distance: Math.sqrt(2), x: -2, y: -2} + }, { + got: dist({x: -1, y: 1}, {x1: -3, y1: 1, x2: -1, y2: 3}), + expected: {distance: Math.sqrt(2), x: -2, y: 2} + }]; + + t.plan(cases.length); + for(var i=0; i