OpenLayers OpenLayers

Changeset 5458

Show
Ignore:
Timestamp:
12/17/07 01:05:35 (1 year ago)
Author:
tschaub
Message:

add geometry.intersects method for all geometry types (closes #1072)

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/openlayers/lib/OpenLayers/Geometry.js

    r4985 r5458  
    201201    CLASS_NAME: "OpenLayers.Geometry" 
    202202}); 
     203 
     204     
     205/** 
     206 * Method: OpenLayers.Geometry.segmentsIntersect 
     207 * Determine whether two line segments intersect.  Optionally calculates 
     208 *     and returns the intersection point.  This function is optimized for 
     209 *     cases where seg1.x2 >= seg2.x1 || seg2.x2 >= seg1.x1.  In those 
     210 *     obvious cases where there is no intersection, the function should 
     211 *     not be called. 
     212 * 
     213 * Parameters: 
     214 * seg1 - {Object} Object representing a segment with properties x1, y1, x2, 
     215 *     and y2.  The start point is represented by x1 and y1.  The end point 
     216 *     is represented by x2 and y2.  Start and end are ordered so that x1 < x2. 
     217 * seg2 - {Object} Object representing a segment with properties x1, y1, x2, 
     218 *     and y2.  The start point is represented by x1 and y1.  The end point 
     219 *     is represented by x2 and y2.  Start and end are ordered so that x1 < x2. 
     220 * point - {Boolean} Return the intersection point.  If false, the actual 
     221 *     intersection point will not be calculated.  If true and the segments 
     222 *     intersect, the intersection point will be returned.  If true and 
     223 *     the segments do not intersect, false will be returned.  If true and 
     224 *     the segments are coincident, true will be returned. 
     225 * 
     226 * Returns: 
     227 * {Boolean | <OpenLayers.Geometry.Point>}  The two segments intersect. 
     228 *     If the point argument is true, the return will be the intersection 
     229 *     point or false if none exists.  If point is true and the segments 
     230 *     are coincident, return will be true (and the instersection is equal 
     231 *     to the shorter segment). 
     232 */ 
     233OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, point) { 
     234    var intersection = false; 
     235    var x11_21 = seg1.x1 - seg2.x1; 
     236    var y11_21 = seg1.y1 - seg2.y1; 
     237    var x12_11 = seg1.x2 - seg1.x1; 
     238    var y12_11 = seg1.y2 - seg1.y1; 
     239    var y22_21 = seg2.y2 - seg2.y1; 
     240    var x22_21 = seg2.x2 - seg2.x1; 
     241    var d = (y22_21 * x12_11) - (x22_21 * y12_11); 
     242    var n1 = (x22_21 * y11_21) - (y22_21 * x11_21); 
     243    var n2 = (x12_11 * y11_21) - (y12_11 * x11_21); 
     244    if(d == 0) { 
     245        // parallel 
     246        if(n1 == 0 && n2 == 0) { 
     247            // coincident 
     248            intersection = true; 
     249        } 
     250    } else { 
     251        var along1 = n1 / d; 
     252        var along2 = n2 / d; 
     253        if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) { 
     254            // intersect 
     255            if(!point) { 
     256                intersection = true; 
     257            } else { 
     258                // calculate the intersection point 
     259                var x = seg1.x1 + (along1 * x12_11); 
     260                var y = seg1.y1 + (along1 * y12_11); 
     261                intersection = new OpenLayers.Geometry.Point(x, y); 
     262            } 
     263        } 
     264    } 
     265    return intersection; 
     266}; 
  • trunk/openlayers/lib/OpenLayers/Geometry/Collection.js

    r5347 r5458  
    307307    }, 
    308308 
     309    /** 
     310     * APIMethod: intersects 
     311     * Determine if the input geometry intersects this one. 
     312     * 
     313     * Parameters: 
     314     * geometry - {<OpenLayers.Geometry>} Any type of geometry. 
     315     * 
     316     * Returns: 
     317     * {Boolean} The input geometry intersects this one. 
     318     */ 
     319    intersects: function(geometry) { 
     320        var intersect = false; 
     321        for(var i=0; i<this.components.length; ++ i) { 
     322            intersect = geometry.intersects(this.components[i]); 
     323            if(intersect) { 
     324                break; 
     325            } 
     326        } 
     327        return intersect; 
     328    }, 
     329 
     330    /** @final @type String */ 
    309331    CLASS_NAME: "OpenLayers.Geometry.Collection" 
    310332}); 
  • trunk/openlayers/lib/OpenLayers/Geometry/LineString.js

    r4985 r5458  
    4242        } 
    4343    }, 
     44     
     45    /** 
     46     * APIMethod: intersects 
     47     * Test for instersection between two geometries.  This is a cheapo 
     48     *     implementation of the Bently-Ottmann algorigithm.  It doesn't 
     49     *     really keep track of a sweep line data structure.  It is closer 
     50     *     to the brute force method, except that segments are sorted and 
     51     *     potential intersections are only calculated when bounding boxes 
     52     *     intersect. 
     53     * 
     54     * Parameters: 
     55     * geometry - {<OpenLayers.Geometry>} 
     56     * 
     57     * Returns: 
     58     * {Boolean} The input geometry intersects this geometry. 
     59     */ 
     60    intersects: function(geometry) { 
     61        var intersect = false; 
     62        var type = geometry.CLASS_NAME; 
     63        if(type == "OpenLayers.Geometry.LineString" || 
     64           type == "OpenLayers.Geometry.LinearRing" || 
     65           type == "OpenLayers.Geometry.Point") { 
     66            var segs1 = this.getSortedSegments(); 
     67            var segs2; 
     68            if(type == "OpenLayers.Geometry.Point") { 
     69                segs2 = [{ 
     70                    x1: geometry.x, y1: geometry.y, 
     71                    x2: geometry.x, y2: geometry.y 
     72                }]; 
     73            } else { 
     74                segs2 = geometry.getSortedSegments(); 
     75            } 
     76            var seg1, seg1x1, seg1x2, seg1y1, seg1y2, 
     77                seg2, seg2y1, seg2y2; 
     78            // sweep right 
     79            outer: for(var i=0; i<segs1.length; ++i) { 
     80                seg1 = segs1[i]; 
     81                seg1x1 = seg1.x1; 
     82                seg1x2 = seg1.x2; 
     83                seg1y1 = seg1.y1; 
     84                seg1y2 = seg1.y2; 
     85                inner: for(var j=0; j<segs2.length; ++j) { 
     86                    seg2 = segs2[j]; 
     87                    if(seg2.x1 > seg1x2) { 
     88                        // seg1 still left of seg2 
     89                        break; 
     90                    } 
     91                    if(seg2.x2 < seg1x1) { 
     92                        // seg2 still left of seg1 
     93                        continue; 
     94                    } 
     95                    seg2y1 = seg2.y1; 
     96                    seg2y2 = seg2.y2; 
     97                    if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) { 
     98                        // seg2 above seg1 
     99                        continue; 
     100                    } 
     101                    if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) { 
     102                        // seg2 below seg1 
     103                        continue; 
     104                    } 
     105                    if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) { 
     106                        intersect = true; 
     107                        break outer; 
     108                    } 
     109                } 
     110            } 
     111        } else { 
     112            intersect = geometry.intersects(this); 
     113        } 
     114        return intersect; 
     115    }, 
     116     
     117    /** 
     118     * Method: getSortedSegments 
     119     * 
     120     * Returns: 
     121     * {Array} An array of segment objects.  Segment objects have properties 
     122     *     x1, y1, x2, and y2.  The start point is represented by x1 and y1. 
     123     *     The end point is represented by x2 and y2.  Start and end are 
     124     *     ordered so that x1 < x2. 
     125     */ 
     126    getSortedSegments: function() { 
     127        var numSeg = this.components.length - 1; 
     128        var segments = new Array(numSeg); 
     129        for(var i=0; i<numSeg; ++i) { 
     130            point1 = this.components[i]; 
     131            point2 = this.components[i + 1]; 
     132            if(point1.x < point2.x) { 
     133                segments[i] = { 
     134                    x1: point1.x, 
     135                    y1: point1.y, 
     136                    x2: point2.x, 
     137                    y2: point2.y 
     138                } 
     139            } else { 
     140                segments[i] = { 
     141                    x1: point2.x, 
     142                    y1: point2.y, 
     143                    x2: point1.x, 
     144                    y2: point1.y 
     145                } 
     146            } 
     147        } 
     148        // more efficient to define this somewhere static 
     149        function byX1(seg1, seg2) { 
     150            return seg1.x1 - seg2.x1; 
     151        } 
     152        return segments.sort(byX1); 
     153    }, 
    44154 
    45155    CLASS_NAME: "OpenLayers.Geometry.LineString" 
  • trunk/openlayers/lib/OpenLayers/Geometry/LinearRing.js

    r5200 r5458  
    175175        return area; 
    176176    }, 
     177     
     178    /** 
     179     * Method: containsPoint 
     180     * Test if a point is inside a linear ring.  For the case where a point 
     181     *     is coincident with a linear ring edge, returns 1.  Otherwise, 
     182     *     returns boolean. 
     183     * 
     184     * Parameters: 
     185     * point - {<OpenLayers.Geometry.Point>} 
     186     * 
     187     * Returns: 
     188     * {Boolean | Number} The point is inside the linear ring.  Returns 1 if 
     189     *     the point is coincident with an edge.  Returns boolean otherwise. 
     190     */ 
     191    containsPoint: function(point) { 
     192        var px = point.x; 
     193        var py = point.y; 
     194        function getX(y, x1, y1, x2, y2) { 
     195            return (((x1 - x2) * y) + ((x2 * y1) - (x1 * y2))) / (y1 - y2); 
     196        } 
     197        var numSeg = this.components.length - 1; 
     198        var start, end, x1, y1, x2, y2, cx, cy; 
     199        var crosses = 0; 
     200        for(var i=0; i<numSeg; ++i) { 
     201            start = this.components[i]; 
     202            x1 = start.x; 
     203            y1 = start.y; 
     204            end = this.components[i + 1]; 
     205            x2 = end.x; 
     206            y2 = end.y; 
     207             
     208            /** 
     209             * The following conditions enforce five edge-crossing rules: 
     210             *    1. points coincident with edges are considered contained; 
     211             *    2. an upward edge includes its starting endpoint, and 
     212             *    excludes its final endpoint; 
     213             *    3. a downward edge excludes its starting endpoint, and 
     214             *    includes its final endpoint; 
     215             *    4. horizontal edges are excluded; and 
     216             *    5. the edge-ray intersection point must be strictly right 
     217             *    of the point P. 
     218             */ 
     219            if(y1 == y2) { 
     220                // horizontal edge 
     221                if(py == y1) { 
     222                    // point on horizontal line 
     223                    if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert 
     224                       x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert 
     225                        // point on edge 
     226                        crosses = -1; 
     227                        break; 
     228                    } 
     229                } 
     230                // ignore other horizontal edges 
     231                continue; 
     232            } 
     233            cx = getX(py, x1, y1, x2, y2); 
     234            if(cx == px) { 
     235                // point on line 
     236                if(y1 < y2 && (py >= y1 && py <= y2) || // upward 
     237                   y1 > y2 && (py <= y1 && py >= y2)) { // downward 
     238                    // point on edge 
     239                    crosses = -1; 
     240                    break; 
     241                } 
     242            } 
     243            if(cx <= px) { 
     244                // no crossing to the right 
     245                continue; 
     246            } 
     247            if(cx < Math.min(x1, x2) || cx > Math.max(x1, x2)) { 
     248                // no crossing 
     249                continue; 
     250            } 
     251            if(y1 < y2 && (py >= y1 && py < y2) || // upward 
     252               y1 > y2 && (py < y1 && py >= y2)) { // downward 
     253                ++crosses; 
     254            } 
     255        } 
     256        var contained = (crosses == -1) ? 
     257            // on edge 
     258            1 : 
     259            // even (out) or odd (in) 
     260            !!(crosses & 1); 
     261 
     262        return contained; 
     263    }, 
     264 
     265    /** 
     266     * APIMethod: intersects 
     267     * Determine if the input geometry intersects this one. 
     268     * 
     269     * Parameters: 
     270     * geometry - {<OpenLayers.Geometry>} Any type of geometry. 
     271     * 
     272     * Returns: 
     273     * {Boolean} The input geometry intersects this one. 
     274     */ 
     275    intersects: function(geometry) { 
     276        var intersect = false; 
     277        if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { 
     278            intersect = this.containsPoint(geometry); 
     279        } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") { 
     280            intersect = geometry.intersects(this); 
     281        } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") { 
     282            intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply( 
     283                this, [geometry] 
     284            ); 
     285        } else { 
     286            // check for component intersections 
     287            for(var i=0; i<geometry.components.length; ++ i) { 
     288                intersect = geometry.components[i].intersects(this); 
     289                if(intersect) { 
     290                    break; 
     291                } 
     292            } 
     293        } 
     294        return intersect; 
     295    }, 
    177296 
    178297    CLASS_NAME: "OpenLayers.Geometry.LinearRing" 
  • trunk/openlayers/lib/OpenLayers/Geometry/Point.js

    r5200 r5458  
    169169        this.clearBounds(); 
    170170    }, 
     171     
     172    /** 
     173     * APIMethod: intersects 
     174     * Determine if the input geometry intersects this one. 
     175     * 
     176     * Parameters: 
     177     * geometry - {<OpenLayers.Geometry>} Any type of geometry. 
     178     * 
     179     * Returns: 
     180     * {Boolean} The input geometry intersects this one. 
     181     */ 
     182    intersects: function(geometry) { 
     183        var intersect = false; 
     184        if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { 
     185            intersect = this.equals(geometry); 
     186        } else { 
     187            intersect = geometry.intersects(this); 
     188        } 
     189        return intersect; 
     190    }, 
    171191 
    172192    CLASS_NAME: "OpenLayers.Geometry.Point" 
  • trunk/openlayers/lib/OpenLayers/Geometry/Polygon.js

    r5439 r5458  
    5858    }, 
    5959 
     60    /** 
     61     * Method: containsPoint 
     62     * Test if a point is inside a polygon.  Points on a polygon edge are 
     63     *     considered inside. 
     64     * 
     65     * Parameters: 
     66     * point - {<OpenLayers.Geometry.Point>} 
     67     * 
     68     * Returns: 
     69     * {Boolean | Number} The point is inside the polygon.  Returns 1 if the 
     70     *     point is on an edge.  Returns boolean otherwise. 
     71     */ 
     72    containsPoint: function(point) { 
     73        var numRings = this.components.length; 
     74        var contained = false; 
     75        if(numRings > 0) { 
     76            // check exterior ring - 1 means on edge, boolean otherwise 
     77            contained = this.components[0].containsPoint(point); 
     78            if(contained !== 1) { 
     79                if(contained && numRings > 1) { 
     80                    // check interior rings 
     81                    var hole; 
     82                    for(var i=1; i<numRings; ++i) { 
     83                        hole = this.components[i].containsPoint(point); 
     84                        if(hole) { 
     85                            if(hole === 1) { 
     86                                // on edge 
     87                                contained = 1; 
     88                            } else { 
     89                                // in hole 
     90                                contained = false; 
     91                            }                             
     92                            break; 
     93                        } 
     94                    } 
     95                } 
     96            } 
     97        } 
     98        return contained; 
     99    }, 
     100 
     101    /** 
     102     * APIMethod: intersects 
     103     * Determine if the input geometry intersects this one. 
     104     * 
     105     * Parameters: 
     106     * geometry - {<OpenLayers.Geometry>} Any type of geometry. 
     107     * 
     108     * Returns: 
     109     * {Boolean} The input geometry intersects this one. 
     110     */ 
     111    intersects: function(geometry) { 
     112        var intersect = false; 
     113        var i; 
     114        if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { 
     115            intersect = this.containsPoint(geometry); 
     116        } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" || 
     117                  geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") { 
     118            // check if rings/linestrings intersect 
     119            for(i=0; i<this.components.length; ++i) { 
     120                intersect = geometry.intersects(this.components[i]); 
     121                if(intersect) { 
     122                    break; 
     123                } 
     124            } 
     125            if(!intersect) { 
     126                // check if this poly contains points of the ring/linestring 
     127                for(i=0; i<geometry.components.length; ++i) { 
     128                    intersect = this.containsPoint(geometry.components[i]); 
     129                    if(intersect) { 
     130                        break; 
     131                    } 
     132                } 
     133            } 
     134        } else { 
     135            for(i=0; i<geometry.components.length; ++ i) { 
     136                intersect = this.intersects(geometry.components[i]); 
     137                if(intersect) { 
     138                    break; 
     139                } 
     140            } 
     141        } 
     142        // check case where this poly is wholly contained by another 
     143        if(!intersect && geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") { 
     144            // exterior ring points will be contained in the other geometry 
     145            var ring = this.components[0]; 
     146            for(i=0; i<ring.components.length; ++i) { 
     147                intersect = geometry.containsPoint(ring.components[i]); 
     148                if(intersect) { 
     149                    break; 
     150                } 
     151            } 
     152        } 
     153        return intersect; 
     154    }, 
     155 
    60156    CLASS_NAME: "OpenLayers.Geometry.Polygon" 
    61157}); 
  • trunk/openlayers/tests/test_Geometry.html

    r4302 r5458  
    22<head> 
    33  <script src="../lib/OpenLayers.js"></script> 
     4  <script src="data/geos_wkt_intersects.js"></script> 
    45  <script type="text/javascript"> 
    56    var map;  
     
    218219    } 
    219220 
    220  
     221    function test_Geometry_intersects_geos_wkt(t) { 
     222        var wkt = new OpenLayers.Format.WKT(); 
     223        var failures = []; 
     224        var intersect12, intersect21, msg; 
     225        for (var i = 0; i < geos_test_data.length; i++) { 
     226            var testcase = geos_test_data[i]; 
     227            f1 = wkt.read(testcase['wkt1']); 
     228            f2 = wkt.read(testcase['wkt2']); 
     229            intersect12 = f1.geometry.intersects(f2.geometry); 
     230            intersect21 = f2.geometry.intersects(f1.geometry); 
     231            if(intersect12 != testcase.result) { 
     232                msg = "f1 should " + (testcase.result ? "" : "not ") + 
     233                      "intersect f2: f1 = '" + testcase['wkt1'] + "' " + 
     234                      "f2 = '" + testcase['wkt2'] + "'"; 
     235                failures.push(msg); 
     236            } 
     237            if(intersect21 != testcase.result) { 
     238                msg = "f2 should " + (testcase.result ? "" : "not ") + 
     239                      "intersect f1: f1 = '" + testcase['wkt1'] + "' " + 
     240                      "f2 = '" + testcase['wkt2'] + "'"; 
     241                failures.push(msg); 
     242            } 
     243        } 
     244        if(failures.length == 0) { 
     245            t.plan(1); 
     246            t.ok(true, "all " + geos_test_data.length + " geos tests pass"); 
     247        } else { 
     248            t.plan(failures.length); 
     249            for(var f=0; f<failures.length; ++f) { 
     250                t.fail(failures[f]); 
     251            } 
     252        } 
     253    }     
    221254 
    222255  </script>