Add split method for linestring and multilinestring geometries. Use for splitting one geometry with another. Optionally performs a mutual split. r=ahocevar (closes #1929)

git-svn-id: http://svn.openlayers.org/trunk/openlayers@8974 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
Tim Schaub
2009-03-09 22:19:07 +00:00
parent 837efd0b9c
commit a10b319413
5 changed files with 806 additions and 3 deletions
+66 -1
View File
@@ -287,11 +287,23 @@ OpenLayers.Geometry.fromWKT = function(wkt) {
* seg2 - {Object} Object representing a segment with 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.
* options - {Object} Optional properties for calculating the intersection.
*
* Valid options:
* point - {Boolean} Return the intersection point. If false, the actual
* intersection point will not be calculated. If true and the segments
* intersect, the intersection point will be returned. If true and
* the segments do not intersect, false will be returned. If true and
* the segments are coincident, true will be returned.
* tolerance - {Number} If a non-null value is provided, if the segments are
* within the tolerance distance, this will be considered an intersection.
* In addition, if the point option is true and the calculated intersection
* is within the tolerance distance of an end point, the endpoint will be
* returned instead of the calculated intersection. Further, if the
* intersection is within the tolerance of endpoints on both segments, or
* if two segment endpoints are within the tolerance distance of eachother
* (but no intersection is otherwise calculated), an endpoint on the
* first segment provided will be returned.
*
* Returns:
* {Boolean | <OpenLayers.Geometry.Point>} The two segments intersect.
@@ -300,7 +312,9 @@ OpenLayers.Geometry.fromWKT = function(wkt) {
* are coincident, return will be true (and the instersection is equal
* to the shorter segment).
*/
OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, point) {
OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) {
var point = options && options.point;
var tolerance = options && options.tolerance;
var intersection = false;
var x11_21 = seg1.x1 - seg2.x1;
var y11_21 = seg1.y1 - seg2.y1;
@@ -332,6 +346,57 @@ OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, point) {
}
}
}
if(tolerance) {
var dist;
if(intersection) {
if(point) {
var segs = [seg1, seg2];
var seg, x, y;
// check segment endpoints for proximity to intersection
// set intersection to first endpoint within the tolerance
outer: for(var i=0; i<2; ++i) {
seg = segs[i];
for(var j=1; j<3; ++j) {
x = seg["x" + j];
y = seg["y" + j];
dist = Math.sqrt(
Math.pow(x - intersection.x, 2) +
Math.pow(y - intersection.y, 2)
)
if(dist < tolerance) {
intersection.x = x;
intersection.y = y;
break outer;
}
}
}
}
} else {
// no calculated intersection, but segments could be within
// the tolerance of one another
var segs = [seg1, seg2];
var source, target, x, y, p, result;
// check segment endpoints for proximity to intersection
// set intersection to first endpoint within the tolerance
outer: for(var i=0; i<2; ++i) {
source = segs[i];
target = segs[(i+1)%2];
for(var j=1; j<3; ++j) {
p = {x: source["x"+j], y: source["y"+j]};
result = OpenLayers.Geometry.distanceToSegment(p, target);
if(result.distance < tolerance) {
if(point) {
intersection = new OpenLayers.Geometry.Point(p.x, p.y);
} else {
intersection = true;
}
break outer;
}
}
}
}
}
return intersection;
};
+234 -2
View File
@@ -153,6 +153,237 @@ OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
}
return segments.sort(byX1);
},
/**
* Method: splitWithSegment
* Split this geometry with the given segment.
*
* Parameters:
* seg - {Object} An object with x1, y1, x2, and y2 properties referencing
* segment endpoint coordinates.
* options - {Object} Properties of this object will be used to determine
* how the split is conducted.
*
* Valid options:
* edge - {Boolean} Allow splitting when only edges intersect. Default is
* true. If false, a vertex on the source segment must be within the
* tolerance distance of the intersection to be considered a split.
* tolerance - {Number} If a non-null value is provided, intersections
* within the tolerance distance of one of the source segment's
* endpoints will be assumed to occur at the endpoint.
*
* Returns:
* {Object} An object with *lines* and *points* properties. If the given
* segment intersects this linestring, the lines array will reference
* geometries that result from the split. The points array will contain
* all intersection points. Intersection points are sorted along the
* segment (in order from x1,y1 to x2,y2).
*/
splitWithSegment: function(seg, options) {
var edge = !(options && options.edge === false);
var tolerance = options && options.tolerance;
var lines = [];
var verts = this.getVertices();
var points = [];
var intersections = [];
var split = false;
var vert1, vert2, point;
var node, vertex, target;
var interOptions = {point: true, tolerance: tolerance};
var result = null;
for(var i=0, stop=verts.length-2; i<=stop; ++i) {
vert1 = verts[i];
points.push(vert1.clone());
vert2 = verts[i+1];
target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
point = OpenLayers.Geometry.segmentsIntersect(
seg, target, interOptions
);
if(point instanceof OpenLayers.Geometry.Point) {
if((point.x === seg.x1 && point.y === seg.y1) ||
(point.x === seg.x2 && point.y === seg.y2) ||
point.equals(vert1) || point.equals(vert2)) {
vertex = true;
} else {
vertex = false;
}
if(vertex || edge) {
// push intersections different than the previous
if(!point.equals(intersections[intersections.length-1])) {
intersections.push(point.clone());
}
if(i === 0) {
if(point.equals(vert1)) {
continue;
}
}
if(point.equals(vert2)) {
continue;
}
split = true;
if(!point.equals(vert1)) {
points.push(point);
}
lines.push(new OpenLayers.Geometry.LineString(points));
points = [point.clone()];
}
}
}
if(split) {
points.push(vert2.clone());
lines.push(new OpenLayers.Geometry.LineString(points));
}
if(intersections.length > 0) {
// sort intersections along segment
var xDir = seg.x1 < seg.x2 ? 1 : -1;
var yDir = seg.y1 < seg.y2 ? 1 : -1;
result = {
lines: lines,
points: intersections.sort(function(p1, p2) {
return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
})
};
}
return result;
},
/**
* Method: split
* Use this geometry (the source) to attempt to split a target geometry.
*
* Parameters:
* target - {<OpenLayers.Geometry>} The target geometry.
* options - {Object} Properties of this object will be used to determine
* how the split is conducted.
*
* Valid options:
* mutual - {Boolean} Split the source geometry in addition to the target
* geometry. Default is false.
* edge - {Boolean} Allow splitting when only edges intersect. Default is
* true. If false, a vertex on the source must be within the tolerance
* distance of the intersection to be considered a split.
* tolerance - {Number} If a non-null value is provided, intersections
* within the tolerance distance of an existing vertex on the source
* will be assumed to occur at the vertex.
*
* Returns:
* {Array} A list of geometries (of this same type as the target) that
* result from splitting the target with the source geometry. The
* source and target geometry will remain unmodified. If no split
* results, null will be returned. If mutual is true and a split
* results, return will be an array of two arrays - the first will be
* all geometries that result from splitting the source geometry and
* the second will be all geometries that result from splitting the
* target geometry.
*/
split: function(target, options) {
var results = null;
var mutual = options && options.mutual;
var sourceSplit, targetSplit, sourceParts, targetParts;
if(target instanceof OpenLayers.Geometry.LineString) {
var verts = this.getVertices();
var vert1, vert2, seg, splits, lines, point;
var points = [];
sourceParts = [];
for(var i=0, stop=verts.length-2; i<=stop; ++i) {
vert1 = verts[i];
vert2 = verts[i+1];
seg = {
x1: vert1.x, y1: vert1.y,
x2: vert2.x, y2: vert2.y
};
targetParts = targetParts || [target];
if(mutual) {
points.push(vert1.clone());
}
for(var j=0; j<targetParts.length; ++j) {
splits = targetParts[j].splitWithSegment(seg, options);
if(splits) {
// splice in new features
lines = splits.lines;
if(lines.length > 0) {
lines.unshift(j, 1);
Array.prototype.splice.apply(targetParts, lines);
j += lines.length - 2;
}
if(mutual) {
for(var k=0, len=splits.points.length; k<len; ++k) {
point = splits.points[k];
if(!point.equals(vert1)) {
points.push(point);
sourceParts.push(new OpenLayers.Geometry.LineString(points));
if(point.equals(vert2)) {
points = [];
} else {
points = [point.clone()];
}
}
}
}
}
}
}
if(mutual && sourceParts.length > 0 && points.length > 0) {
points.push(vert2.clone());
sourceParts.push(new OpenLayers.Geometry.LineString(points));
}
} else {
results = target.splitWith(this, options);
}
if(targetParts && targetParts.length > 1) {
targetSplit = true;
} else {
targetParts = [];
}
if(sourceParts && sourceParts.length > 1) {
sourceSplit = true;
} else {
sourceParts = [];
}
if(targetSplit || sourceSplit) {
if(mutual) {
results = [sourceParts, targetParts];
} else {
results = targetParts;
}
}
return results;
},
/**
* Method: splitWith
* Split this geometry (the target) with the given geometry (the source).
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} A geometry used to split this
* geometry (the source).
* options - {Object} Properties of this object will be used to determine
* how the split is conducted.
*
* Valid options:
* mutual - {Boolean} Split the source geometry in addition to the target
* geometry. Default is false.
* edge - {Boolean} Allow splitting when only edges intersect. Default is
* true. If false, a vertex on the source must be within the tolerance
* distance of the intersection to be considered a split.
* tolerance - {Number} If a non-null value is provided, intersections
* within the tolerance distance of an existing vertex on the source
* will be assumed to occur at the vertex.
*
* Returns:
* {Array} A list of geometries (of this same type as the target) that
* result from splitting the target with the source geometry. The
* source and target geometry will remain unmodified. If no split
* results, null will be returned. If mutual is true and a split
* results, return will be an array of two arrays - the first will be
* all geometries that result from splitting the source geometry and
* the second will be all geometries that result from splitting the
* target geometry.
*/
splitWith: function(geometry, options) {
return geometry.split(this, options);
},
/**
* APIMethod: getVertices
@@ -225,7 +456,7 @@ OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
if(result.distance < min) {
min = result.distance;
best = result;
if(min == 0) {
if(min === 0) {
break;
}
} else {
@@ -249,13 +480,14 @@ OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
var segs1 = geometry.getSortedSegments();
var seg0, seg1, intersection, x0, y0;
var len1 = segs1.length;
var interOptions = {point: true};
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);
intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);
if(intersection) {
min = 0;
best = {
+217
View File
@@ -39,6 +39,223 @@ OpenLayers.Geometry.MultiLineString = OpenLayers.Class(
OpenLayers.Geometry.Collection.prototype.initialize.apply(this,
arguments);
},
/**
* Method: split
* Use this geometry (the source) to attempt to split a target geometry.
*
* Parameters:
* target - {<OpenLayers.Geometry>} The target geometry.
* options - {Object} Properties of this object will be used to determine
* how the split is conducted.
*
* Valid options:
* mutual - {Boolean} Split the source geometry in addition to the target
* geometry. Default is false.
* edge - {Boolean} Allow splitting when only edges intersect. Default is
* true. If false, a vertex on the source must be within the tolerance
* distance of the intersection to be considered a split.
* tolerance - {Number} If a non-null value is provided, intersections
* within the tolerance distance of an existing vertex on the source
* will be assumed to occur at the vertex.
*
* Returns:
* {Array} A list of geometries (of this same type as the target) that
* result from splitting the target with the source geometry. The
* source and target geometry will remain unmodified. If no split
* results, null will be returned. If mutual is true and a split
* results, return will be an array of two arrays - the first will be
* all geometries that result from splitting the source geometry and
* the second will be all geometries that result from splitting the
* target geometry.
*/
split: function(geometry, options) {
var results = null;
var mutual = options && options.mutual;
var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
var sourceParts = [];
var targetParts = [geometry];
for(var i=0, len=this.components.length; i<len; ++i) {
sourceLine = this.components[i];
sourceSplit = false;
for(var j=0; j < targetParts.length; ++j) {
splits = sourceLine.split(targetParts[j], options);
if(splits) {
if(mutual) {
sourceLines = splits[0];
for(var k=0, klen=sourceLines.length; k<klen; ++k) {
if(k===0 && sourceParts.length) {
sourceParts[sourceParts.length-1].addComponent(
sourceLines[k]
);
} else {
sourceParts.push(
new OpenLayers.Geometry.MultiLineString([
sourceLines[k]
])
);
}
}
sourceSplit = true;
splits = splits[1];
}
if(splits.length) {
// splice in new target parts
splits.unshift(j, 1);
Array.prototype.splice.apply(targetParts, splits);
break;
}
}
}
if(!sourceSplit) {
// source line was not hit
if(sourceParts.length) {
// add line to existing multi
sourceParts[sourceParts.length-1].addComponent(
sourceLine.clone()
);
} else {
// create a fresh multi
sourceParts = [
new OpenLayers.Geometry.MultiLineString(
sourceLine.clone()
)
];
}
}
}
if(sourceParts && sourceParts.length > 1) {
sourceSplit = true;
} else {
sourceParts = [];
}
if(targetParts && targetParts.length > 1) {
targetSplit = true;
} else {
targetParts = [];
}
if(sourceSplit || targetSplit) {
if(mutual) {
results = [sourceParts, targetParts];
} else {
results = targetParts;
}
}
return results;
},
/**
* Method: splitWith
* Split this geometry (the target) with the given geometry (the source).
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} A geometry used to split this
* geometry (the source).
* options - {Object} Properties of this object will be used to determine
* how the split is conducted.
*
* Valid options:
* mutual - {Boolean} Split the source geometry in addition to the target
* geometry. Default is false.
* edge - {Boolean} Allow splitting when only edges intersect. Default is
* true. If false, a vertex on the source must be within the tolerance
* distance of the intersection to be considered a split.
* tolerance - {Number} If a non-null value is provided, intersections
* within the tolerance distance of an existing vertex on the source
* will be assumed to occur at the vertex.
*
* Returns:
* {Array} A list of geometries (of this same type as the target) that
* result from splitting the target with the source geometry. The
* source and target geometry will remain unmodified. If no split
* results, null will be returned. If mutual is true and a split
* results, return will be an array of two arrays - the first will be
* all geometries that result from splitting the source geometry and
* the second will be all geometries that result from splitting the
* target geometry.
*/
splitWith: function(geometry, options) {
var results = null;
var mutual = options && options.mutual;
var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
if(geometry instanceof OpenLayers.Geometry.LineString) {
targetParts = [];
sourceParts = [geometry];
for(var i=0, len=this.components.length; i<len; ++i) {
targetSplit = false;
targetLine = this.components[i];
for(var j=0; j<sourceParts.length; ++j) {
splits = sourceParts[j].split(targetLine, options);
if(splits) {
if(mutual) {
sourceLines = splits[0];
if(sourceLines.length) {
// splice in new source parts
sourceLines.unshift(j, 1);
Array.prototype.splice.apply(sourceParts, sourceLines);
j += sourceLines.length - 2;
}
splits = splits[1];
if(splits.length === 0) {
splits = [targetLine.clone()];
}
}
for(var k=0, klen=splits.length; k<klen; ++k) {
if(k===0 && targetParts.length) {
targetParts[targetParts.length-1].addComponent(
splits[k]
);
} else {
targetParts.push(
new OpenLayers.Geometry.MultiLineString([
splits[k]
])
);
}
}
targetSplit = true;
}
}
if(!targetSplit) {
// target component was not hit
if(targetParts.length) {
// add it to any existing multi-line
targetParts[targetParts.length-1].addComponent(
targetLine.clone()
);
} else {
// or start with a fresh multi-line
targetParts = [
new OpenLayers.Geometry.MultiLineString([
targetLine.clone()
])
];
}
}
}
} else {
results = geometry.split(this);
}
if(sourceParts && sourceParts.length > 1) {
sourceSplit = true;
} else {
sourceParts = [];
}
if(targetParts && targetParts.length > 1) {
targetSplit = true;
} else {
targetParts = [];
}
if(sourceSplit || targetSplit) {
if(mutual) {
results = [sourceParts, targetParts];
} else {
results = targetParts;
}
}
return results;
},
CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
});