Files
openlayers/lib/OpenLayers/Geometry/LineString.js

316 lines
11 KiB
JavaScript

/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Geometry/Curve.js
*/
/**
* Class: OpenLayers.Geometry.LineString
* A LineString is a Curve which, once two points have been added to it, can
* never be less than two points long.
*
* Inherits from:
* - <OpenLayers.Geometry.Curve>
*/
OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
/**
* Constructor: OpenLayers.Geometry.LineString
* Create a new LineString geometry
*
* Parameters:
* points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to
* generate the linestring
*
*/
initialize: function(points) {
OpenLayers.Geometry.Curve.prototype.initialize.apply(this, arguments);
},
/**
* APIMethod: removeComponent
* Only allows removal of a point if there are three or more points in
* the linestring. (otherwise the result would be just a single point)
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>} The point to be removed
*/
removeComponent: function(point) {
if ( this.components && (this.components.length > 2)) {
OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
arguments);
}
},
/**
* APIMethod: intersects
* Test for instersection between two geometries. This is a cheapo
* implementation of the Bently-Ottmann algorigithm. It doesn't
* really keep track of a sweep line data structure. It is closer
* to the brute force method, except that segments are sorted and
* potential intersections are only calculated when bounding boxes
* intersect.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {Boolean} The input geometry intersects this geometry.
*/
intersects: function(geometry) {
var intersect = false;
var type = geometry.CLASS_NAME;
if(type == "OpenLayers.Geometry.LineString" ||
type == "OpenLayers.Geometry.LinearRing" ||
type == "OpenLayers.Geometry.Point") {
var segs1 = this.getSortedSegments();
var segs2;
if(type == "OpenLayers.Geometry.Point") {
segs2 = [{
x1: geometry.x, y1: geometry.y,
x2: geometry.x, y2: geometry.y
}];
} else {
segs2 = geometry.getSortedSegments();
}
var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
seg2, seg2y1, seg2y2;
// sweep right
outer: for(var i=0, len=segs1.length; i<len; ++i) {
seg1 = segs1[i];
seg1x1 = seg1.x1;
seg1x2 = seg1.x2;
seg1y1 = seg1.y1;
seg1y2 = seg1.y2;
inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
seg2 = segs2[j];
if(seg2.x1 > seg1x2) {
// seg1 still left of seg2
break;
}
if(seg2.x2 < seg1x1) {
// seg2 still left of seg1
continue;
}
seg2y1 = seg2.y1;
seg2y2 = seg2.y2;
if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) {
// seg2 above seg1
continue;
}
if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) {
// seg2 below seg1
continue;
}
if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) {
intersect = true;
break outer;
}
}
}
} else {
intersect = geometry.intersects(this);
}
return intersect;
},
/**
* Method: getSortedSegments
*
* Returns:
* {Array} An array of segment objects. Segment objects have properties
* x1, y1, x2, and y2. The start point is represented by x1 and y1.
* The end point is represented by x2 and y2. Start and end are
* ordered so that x1 < x2.
*/
getSortedSegments: function() {
var numSeg = this.components.length - 1;
var segments = new Array(numSeg);
for(var i=0; i<numSeg; ++i) {
point1 = this.components[i];
point2 = this.components[i + 1];
if(point1.x < point2.x) {
segments[i] = {
x1: point1.x,
y1: point1.y,
x2: point2.x,
y2: point2.y
};
} else {
segments[i] = {
x1: point2.x,
y1: point2.y,
x2: point1.x,
y2: point1.y
};
}
}
// more efficient to define this somewhere static
function byX1(seg1, seg2) {
return seg1.x1 - seg2.x1;
}
return segments.sort(byX1);
},
/**
* APIMethod: getVertices
* Return a list of all points in this geometry.
*
* Parameters:
* nodesOnly - {Boolean} For lines, only return vertices that are
* endpoints.
*
* Returns:
* {Array} A list of all vertices in the geometry.
*/
getVertices: function(nodesOnly) {
var vertices;
if(nodesOnly) {
vertices = [
this.components[0],
this.components[this.components.length-1]
];
} else {
vertices = this.components;
}
return vertices;
},
/**
* APIMethod: distanceTo
* Calculate the closest distance between two geometries.
*
* Parameters:
* geometry - {<OpenLayers.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(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<len; ++i) {
seg = segs[i];
result = OpenLayers.Geometry.distanceToSegment(geometry, seg);
if(result.distance < min) {
min = result.distance;
best = result;
if(min == 0) {
break;
}
} else {
// if distance increases and we cross y0 to the right of x0, no need to keep looking.
if(seg.x2 > 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<len; ++i) {
seg0 = segs0[i];
x0 = seg0.x1;
y0 = seg0.y1;
for(var j=0; j<len1; ++j) {
seg1 = segs1[j];
intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, true);
if(intersection) {
min = 0;
best = {
distance: 0,
x0: intersection.x, y0: intersection.y,
x1: intersection.x, y1: intersection.y
};
break outer;
} else {
result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
if(result.distance < min) {
min = result.distance;
best = {
distance: min,
x0: x0, y0: y0,
x1: result.x, y1: result.y
};
}
}
}
}
if(!details) {
best = best.distance;
}
if(min !== 0) {
// check the final vertex in this line's sorted segments
if(seg0) {
result = geometry.distanceTo(
new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),
options
);
var dist = details ? result.distance : result;
if(dist < min) {
if(details) {
best = {
distance: min,
x0: result.x1, y0: result.y1,
x1: result.x0, y1: result.y0
};
} else {
best = dist;
}
}
}
}
} else {
best = geometry.distanceTo(this, options);
// swap since target comes from this line
if(details) {
best = {
distance: best.distance,
x0: best.x1, y0: best.y1,
x1: best.x0, y1: best.y0
};
}
}
return best;
},
CLASS_NAME: "OpenLayers.Geometry.LineString"
});