diff --git a/lib/OpenLayers/Geometry.js b/lib/OpenLayers/Geometry.js index 0764ede615..8d091405a3 100644 --- a/lib/OpenLayers/Geometry.js +++ b/lib/OpenLayers/Geometry.js @@ -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 | } 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; }; diff --git a/lib/OpenLayers/Geometry/LineString.js b/lib/OpenLayers/Geometry/LineString.js index 9e753db534..56148a2b40 100644 --- a/lib/OpenLayers/Geometry/LineString.js +++ b/lib/OpenLayers/Geometry/LineString.js @@ -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 - {} 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 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 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 - {} 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} 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 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 - {} 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 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" }); diff --git a/tests/Geometry/LineString.html b/tests/Geometry/LineString.html index 5b7b5daa9e..8877100091 100644 --- a/tests/Geometry/LineString.html +++ b/tests/Geometry/LineString.html @@ -149,6 +149,105 @@ } + function test_split(t) { + var wkt = OpenLayers.Geometry.fromWKT; + + var cases = [{ + msg: "no intersection", + g1: "LINESTRING(0 0, 0 1)", + g2: "LINESTRING(1 0, 1 1)", + exp: null + } , { + msg: "intersection at midpoint", + g1: "LINESTRING(0 0, 1 1)", + g2: "LINESTRING(1 0, 0 1)", + exp: ["LINESTRING(1 0, 0.5 0.5)", "LINESTRING(0.5 0.5, 0 1)"] + }, { + msg: "intersection at midpoint (reverse source/target)", + g1: "LINESTRING(1 0, 0 1)", + g2: "LINESTRING(0 0, 1 1)", + exp: ["LINESTRING(0 0, 0.5 0.5)", "LINESTRING(0.5 0.5, 1 1)"] + }, { + msg: "intersection at endpoint", + g1: "LINESTRING(0 0, 1 1)", + g2: "LINESTRING(1 0, 1 1)", + exp: null + }, { + msg: "midpoint intersection, no options", + g1: "LINESTRING(0 0, 2 2)", + g2: "LINESTRING(0 2, 2 0)", + exp: ["LINESTRING(0 2, 1 1)", "LINESTRING(1 1, 2 0)"] + }, { + msg: "midpoint intersection, edge false", + opt: {edge: false}, + g1: "LINESTRING(0 0, 2 2)", + g2: "LINESTRING(0 2, 2 0)", + exp: null + }, { + msg: "midpoint intersection, mutual", + opt: {mutual: true}, + g1: "LINESTRING(0 0, 2 2)", + g2: "LINESTRING(0 2, 2 0)", + exp: [["LINESTRING(0 0, 1 1)", "LINESTRING(1 1, 2 2)"], ["LINESTRING(0 2, 1 1)", "LINESTRING(1 1, 2 0)"]] + }, { + msg: "close intersection, no tolerance", + g1: "LINESTRING(0 0, 0.9 0.9)", + g2: "LINESTRING(0 2, 2 0)", + exp: null + }, { + msg: "close intersection, within tolerance", + opt: {tolerance: 0.2}, + g1: "LINESTRING(0 0, 0.9 0.9)", + g2: "LINESTRING(0 2, 2 0)", + exp: ["LINESTRING(0 2, 0.9 0.9)", "LINESTRING(0.9 0.9, 2 0)"] + }]; + + t.plan(cases.length); + var c, parts, part, midparts; + for(var i=0; i