1 | /* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for |
---|
2 | * full list of contributors). Published under the Clear BSD license. |
---|
3 | * See http://svn.openlayers.org/trunk/openlayers/license.txt for the |
---|
4 | * full text of the license. */ |
---|
5 | |
---|
6 | /** |
---|
7 | * @requires OpenLayers/Geometry/Curve.js |
---|
8 | */ |
---|
9 | |
---|
10 | /** |
---|
11 | * Class: OpenLayers.Geometry.LineString |
---|
12 | * A LineString is a Curve which, once two points have been added to it, can |
---|
13 | * never be less than two points long. |
---|
14 | * |
---|
15 | * Inherits from: |
---|
16 | * - <OpenLayers.Geometry.Curve> |
---|
17 | */ |
---|
18 | OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, { |
---|
19 | |
---|
20 | /** |
---|
21 | * Constructor: OpenLayers.Geometry.LineString |
---|
22 | * Create a new LineString geometry |
---|
23 | * |
---|
24 | * Parameters: |
---|
25 | * points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to |
---|
26 | * generate the linestring |
---|
27 | * |
---|
28 | */ |
---|
29 | initialize: function(points) { |
---|
30 | OpenLayers.Geometry.Curve.prototype.initialize.apply(this, arguments); |
---|
31 | }, |
---|
32 | |
---|
33 | /** |
---|
34 | * APIMethod: removeComponent |
---|
35 | * Only allows removal of a point if there are three or more points in |
---|
36 | * the linestring. (otherwise the result would be just a single point) |
---|
37 | * |
---|
38 | * Parameters: |
---|
39 | * point - {<OpenLayers.Geometry.Point>} The point to be removed |
---|
40 | */ |
---|
41 | removeComponent: function(point) { |
---|
42 | if ( this.components && (this.components.length > 2)) { |
---|
43 | OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, |
---|
44 | arguments); |
---|
45 | } |
---|
46 | }, |
---|
47 | |
---|
48 | /** |
---|
49 | * APIMethod: intersects |
---|
50 | * Test for instersection between two geometries. This is a cheapo |
---|
51 | * implementation of the Bently-Ottmann algorigithm. It doesn't |
---|
52 | * really keep track of a sweep line data structure. It is closer |
---|
53 | * to the brute force method, except that segments are sorted and |
---|
54 | * potential intersections are only calculated when bounding boxes |
---|
55 | * intersect. |
---|
56 | * |
---|
57 | * Parameters: |
---|
58 | * geometry - {<OpenLayers.Geometry>} |
---|
59 | * |
---|
60 | * Returns: |
---|
61 | * {Boolean} The input geometry intersects this geometry. |
---|
62 | */ |
---|
63 | intersects: function(geometry) { |
---|
64 | var intersect = false; |
---|
65 | var type = geometry.CLASS_NAME; |
---|
66 | if(type == "OpenLayers.Geometry.LineString" || |
---|
67 | type == "OpenLayers.Geometry.LinearRing" || |
---|
68 | type == "OpenLayers.Geometry.Point") { |
---|
69 | var segs1 = this.getSortedSegments(); |
---|
70 | var segs2; |
---|
71 | if(type == "OpenLayers.Geometry.Point") { |
---|
72 | segs2 = [{ |
---|
73 | x1: geometry.x, y1: geometry.y, |
---|
74 | x2: geometry.x, y2: geometry.y |
---|
75 | }]; |
---|
76 | } else { |
---|
77 | segs2 = geometry.getSortedSegments(); |
---|
78 | } |
---|
79 | var seg1, seg1x1, seg1x2, seg1y1, seg1y2, |
---|
80 | seg2, seg2y1, seg2y2; |
---|
81 | // sweep right |
---|
82 | outer: for(var i=0, len=segs1.length; i<len; ++i) { |
---|
83 | seg1 = segs1[i]; |
---|
84 | seg1x1 = seg1.x1; |
---|
85 | seg1x2 = seg1.x2; |
---|
86 | seg1y1 = seg1.y1; |
---|
87 | seg1y2 = seg1.y2; |
---|
88 | inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) { |
---|
89 | seg2 = segs2[j]; |
---|
90 | if(seg2.x1 > seg1x2) { |
---|
91 | // seg1 still left of seg2 |
---|
92 | break; |
---|
93 | } |
---|
94 | if(seg2.x2 < seg1x1) { |
---|
95 | // seg2 still left of seg1 |
---|
96 | continue; |
---|
97 | } |
---|
98 | seg2y1 = seg2.y1; |
---|
99 | seg2y2 = seg2.y2; |
---|
100 | if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) { |
---|
101 | // seg2 above seg1 |
---|
102 | continue; |
---|
103 | } |
---|
104 | if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) { |
---|
105 | // seg2 below seg1 |
---|
106 | continue; |
---|
107 | } |
---|
108 | if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) { |
---|
109 | intersect = true; |
---|
110 | break outer; |
---|
111 | } |
---|
112 | } |
---|
113 | } |
---|
114 | } else { |
---|
115 | intersect = geometry.intersects(this); |
---|
116 | } |
---|
117 | return intersect; |
---|
118 | }, |
---|
119 | |
---|
120 | /** |
---|
121 | * Method: getSortedSegments |
---|
122 | * |
---|
123 | * Returns: |
---|
124 | * {Array} An array of segment objects. Segment objects have properties |
---|
125 | * x1, y1, x2, and y2. The start point is represented by x1 and y1. |
---|
126 | * The end point is represented by x2 and y2. Start and end are |
---|
127 | * ordered so that x1 < x2. |
---|
128 | */ |
---|
129 | getSortedSegments: function() { |
---|
130 | var numSeg = this.components.length - 1; |
---|
131 | var segments = new Array(numSeg), point1, point2; |
---|
132 | for(var i=0; i<numSeg; ++i) { |
---|
133 | point1 = this.components[i]; |
---|
134 | point2 = this.components[i + 1]; |
---|
135 | if(point1.x < point2.x) { |
---|
136 | segments[i] = { |
---|
137 | x1: point1.x, |
---|
138 | y1: point1.y, |
---|
139 | x2: point2.x, |
---|
140 | y2: point2.y |
---|
141 | }; |
---|
142 | } else { |
---|
143 | segments[i] = { |
---|
144 | x1: point2.x, |
---|
145 | y1: point2.y, |
---|
146 | x2: point1.x, |
---|
147 | y2: point1.y |
---|
148 | }; |
---|
149 | } |
---|
150 | } |
---|
151 | // more efficient to define this somewhere static |
---|
152 | function byX1(seg1, seg2) { |
---|
153 | return seg1.x1 - seg2.x1; |
---|
154 | } |
---|
155 | return segments.sort(byX1); |
---|
156 | }, |
---|
157 | |
---|
158 | /** |
---|
159 | * Method: splitWithSegment |
---|
160 | * Split this geometry with the given segment. |
---|
161 | * |
---|
162 | * Parameters: |
---|
163 | * seg - {Object} An object with x1, y1, x2, and y2 properties referencing |
---|
164 | * segment endpoint coordinates. |
---|
165 | * options - {Object} Properties of this object will be used to determine |
---|
166 | * how the split is conducted. |
---|
167 | * |
---|
168 | * Valid options: |
---|
169 | * edge - {Boolean} Allow splitting when only edges intersect. Default is |
---|
170 | * true. If false, a vertex on the source segment must be within the |
---|
171 | * tolerance distance of the intersection to be considered a split. |
---|
172 | * tolerance - {Number} If a non-null value is provided, intersections |
---|
173 | * within the tolerance distance of one of the source segment's |
---|
174 | * endpoints will be assumed to occur at the endpoint. |
---|
175 | * |
---|
176 | * Returns: |
---|
177 | * {Object} An object with *lines* and *points* properties. If the given |
---|
178 | * segment intersects this linestring, the lines array will reference |
---|
179 | * geometries that result from the split. The points array will contain |
---|
180 | * all intersection points. Intersection points are sorted along the |
---|
181 | * segment (in order from x1,y1 to x2,y2). |
---|
182 | */ |
---|
183 | splitWithSegment: function(seg, options) { |
---|
184 | var edge = !(options && options.edge === false); |
---|
185 | var tolerance = options && options.tolerance; |
---|
186 | var lines = []; |
---|
187 | var verts = this.getVertices(); |
---|
188 | var points = []; |
---|
189 | var intersections = []; |
---|
190 | var split = false; |
---|
191 | var vert1, vert2, point; |
---|
192 | var node, vertex, target; |
---|
193 | var interOptions = {point: true, tolerance: tolerance}; |
---|
194 | var result = null; |
---|
195 | for(var i=0, stop=verts.length-2; i<=stop; ++i) { |
---|
196 | vert1 = verts[i]; |
---|
197 | points.push(vert1.clone()); |
---|
198 | vert2 = verts[i+1]; |
---|
199 | target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y}; |
---|
200 | point = OpenLayers.Geometry.segmentsIntersect( |
---|
201 | seg, target, interOptions |
---|
202 | ); |
---|
203 | if(point instanceof OpenLayers.Geometry.Point) { |
---|
204 | if((point.x === seg.x1 && point.y === seg.y1) || |
---|
205 | (point.x === seg.x2 && point.y === seg.y2) || |
---|
206 | point.equals(vert1) || point.equals(vert2)) { |
---|
207 | vertex = true; |
---|
208 | } else { |
---|
209 | vertex = false; |
---|
210 | } |
---|
211 | if(vertex || edge) { |
---|
212 | // push intersections different than the previous |
---|
213 | if(!point.equals(intersections[intersections.length-1])) { |
---|
214 | intersections.push(point.clone()); |
---|
215 | } |
---|
216 | if(i === 0) { |
---|
217 | if(point.equals(vert1)) { |
---|
218 | continue; |
---|
219 | } |
---|
220 | } |
---|
221 | if(point.equals(vert2)) { |
---|
222 | continue; |
---|
223 | } |
---|
224 | split = true; |
---|
225 | if(!point.equals(vert1)) { |
---|
226 | points.push(point); |
---|
227 | } |
---|
228 | lines.push(new OpenLayers.Geometry.LineString(points)); |
---|
229 | points = [point.clone()]; |
---|
230 | } |
---|
231 | } |
---|
232 | } |
---|
233 | if(split) { |
---|
234 | points.push(vert2.clone()); |
---|
235 | lines.push(new OpenLayers.Geometry.LineString(points)); |
---|
236 | } |
---|
237 | if(intersections.length > 0) { |
---|
238 | // sort intersections along segment |
---|
239 | var xDir = seg.x1 < seg.x2 ? 1 : -1; |
---|
240 | var yDir = seg.y1 < seg.y2 ? 1 : -1; |
---|
241 | result = { |
---|
242 | lines: lines, |
---|
243 | points: intersections.sort(function(p1, p2) { |
---|
244 | return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y); |
---|
245 | }) |
---|
246 | }; |
---|
247 | } |
---|
248 | return result; |
---|
249 | }, |
---|
250 | |
---|
251 | /** |
---|
252 | * Method: split |
---|
253 | * Use this geometry (the source) to attempt to split a target geometry. |
---|
254 | * |
---|
255 | * Parameters: |
---|
256 | * target - {<OpenLayers.Geometry>} The target geometry. |
---|
257 | * options - {Object} Properties of this object will be used to determine |
---|
258 | * how the split is conducted. |
---|
259 | * |
---|
260 | * Valid options: |
---|
261 | * mutual - {Boolean} Split the source geometry in addition to the target |
---|
262 | * geometry. Default is false. |
---|
263 | * edge - {Boolean} Allow splitting when only edges intersect. Default is |
---|
264 | * true. If false, a vertex on the source must be within the tolerance |
---|
265 | * distance of the intersection to be considered a split. |
---|
266 | * tolerance - {Number} If a non-null value is provided, intersections |
---|
267 | * within the tolerance distance of an existing vertex on the source |
---|
268 | * will be assumed to occur at the vertex. |
---|
269 | * |
---|
270 | * Returns: |
---|
271 | * {Array} A list of geometries (of this same type as the target) that |
---|
272 | * result from splitting the target with the source geometry. The |
---|
273 | * source and target geometry will remain unmodified. If no split |
---|
274 | * results, null will be returned. If mutual is true and a split |
---|
275 | * results, return will be an array of two arrays - the first will be |
---|
276 | * all geometries that result from splitting the source geometry and |
---|
277 | * the second will be all geometries that result from splitting the |
---|
278 | * target geometry. |
---|
279 | */ |
---|
280 | split: function(target, options) { |
---|
281 | var results = null; |
---|
282 | var mutual = options && options.mutual; |
---|
283 | var sourceSplit, targetSplit, sourceParts, targetParts; |
---|
284 | if(target instanceof OpenLayers.Geometry.LineString) { |
---|
285 | var verts = this.getVertices(); |
---|
286 | var vert1, vert2, seg, splits, lines, point; |
---|
287 | var points = []; |
---|
288 | sourceParts = []; |
---|
289 | for(var i=0, stop=verts.length-2; i<=stop; ++i) { |
---|
290 | vert1 = verts[i]; |
---|
291 | vert2 = verts[i+1]; |
---|
292 | seg = { |
---|
293 | x1: vert1.x, y1: vert1.y, |
---|
294 | x2: vert2.x, y2: vert2.y |
---|
295 | }; |
---|
296 | targetParts = targetParts || [target]; |
---|
297 | if(mutual) { |
---|
298 | points.push(vert1.clone()); |
---|
299 | } |
---|
300 | for(var j=0; j<targetParts.length; ++j) { |
---|
301 | splits = targetParts[j].splitWithSegment(seg, options); |
---|
302 | if(splits) { |
---|
303 | // splice in new features |
---|
304 | lines = splits.lines; |
---|
305 | if(lines.length > 0) { |
---|
306 | lines.unshift(j, 1); |
---|
307 | Array.prototype.splice.apply(targetParts, lines); |
---|
308 | j += lines.length - 2; |
---|
309 | } |
---|
310 | if(mutual) { |
---|
311 | for(var k=0, len=splits.points.length; k<len; ++k) { |
---|
312 | point = splits.points[k]; |
---|
313 | if(!point.equals(vert1)) { |
---|
314 | points.push(point); |
---|
315 | sourceParts.push(new OpenLayers.Geometry.LineString(points)); |
---|
316 | if(point.equals(vert2)) { |
---|
317 | points = []; |
---|
318 | } else { |
---|
319 | points = [point.clone()]; |
---|
320 | } |
---|
321 | } |
---|
322 | } |
---|
323 | } |
---|
324 | } |
---|
325 | } |
---|
326 | } |
---|
327 | if(mutual && sourceParts.length > 0 && points.length > 0) { |
---|
328 | points.push(vert2.clone()); |
---|
329 | sourceParts.push(new OpenLayers.Geometry.LineString(points)); |
---|
330 | } |
---|
331 | } else { |
---|
332 | results = target.splitWith(this, options); |
---|
333 | } |
---|
334 | if(targetParts && targetParts.length > 1) { |
---|
335 | targetSplit = true; |
---|
336 | } else { |
---|
337 | targetParts = []; |
---|
338 | } |
---|
339 | if(sourceParts && sourceParts.length > 1) { |
---|
340 | sourceSplit = true; |
---|
341 | } else { |
---|
342 | sourceParts = []; |
---|
343 | } |
---|
344 | if(targetSplit || sourceSplit) { |
---|
345 | if(mutual) { |
---|
346 | results = [sourceParts, targetParts]; |
---|
347 | } else { |
---|
348 | results = targetParts; |
---|
349 | } |
---|
350 | } |
---|
351 | return results; |
---|
352 | }, |
---|
353 | |
---|
354 | /** |
---|
355 | * Method: splitWith |
---|
356 | * Split this geometry (the target) with the given geometry (the source). |
---|
357 | * |
---|
358 | * Parameters: |
---|
359 | * geometry - {<OpenLayers.Geometry>} A geometry used to split this |
---|
360 | * geometry (the source). |
---|
361 | * options - {Object} Properties of this object will be used to determine |
---|
362 | * how the split is conducted. |
---|
363 | * |
---|
364 | * Valid options: |
---|
365 | * mutual - {Boolean} Split the source geometry in addition to the target |
---|
366 | * geometry. Default is false. |
---|
367 | * edge - {Boolean} Allow splitting when only edges intersect. Default is |
---|
368 | * true. If false, a vertex on the source must be within the tolerance |
---|
369 | * distance of the intersection to be considered a split. |
---|
370 | * tolerance - {Number} If a non-null value is provided, intersections |
---|
371 | * within the tolerance distance of an existing vertex on the source |
---|
372 | * will be assumed to occur at the vertex. |
---|
373 | * |
---|
374 | * Returns: |
---|
375 | * {Array} A list of geometries (of this same type as the target) that |
---|
376 | * result from splitting the target with the source geometry. The |
---|
377 | * source and target geometry will remain unmodified. If no split |
---|
378 | * results, null will be returned. If mutual is true and a split |
---|
379 | * results, return will be an array of two arrays - the first will be |
---|
380 | * all geometries that result from splitting the source geometry and |
---|
381 | * the second will be all geometries that result from splitting the |
---|
382 | * target geometry. |
---|
383 | */ |
---|
384 | splitWith: function(geometry, options) { |
---|
385 | return geometry.split(this, options); |
---|
386 | |
---|
387 | }, |
---|
388 | |
---|
389 | /** |
---|
390 | * APIMethod: getVertices |
---|
391 | * Return a list of all points in this geometry. |
---|
392 | * |
---|
393 | * Parameters: |
---|
394 | * nodes - {Boolean} For lines, only return vertices that are |
---|
395 | * endpoints. If false, for lines, only vertices that are not |
---|
396 | * endpoints will be returned. If not provided, all vertices will |
---|
397 | * be returned. |
---|
398 | * |
---|
399 | * Returns: |
---|
400 | * {Array} A list of all vertices in the geometry. |
---|
401 | */ |
---|
402 | getVertices: function(nodes) { |
---|
403 | var vertices; |
---|
404 | if(nodes === true) { |
---|
405 | vertices = [ |
---|
406 | this.components[0], |
---|
407 | this.components[this.components.length-1] |
---|
408 | ]; |
---|
409 | } else if (nodes === false) { |
---|
410 | vertices = this.components.slice(1, this.components.length-1); |
---|
411 | } else { |
---|
412 | vertices = this.components.slice(); |
---|
413 | } |
---|
414 | return vertices; |
---|
415 | }, |
---|
416 | |
---|
417 | /** |
---|
418 | * APIMethod: distanceTo |
---|
419 | * Calculate the closest distance between two geometries (on the x-y plane). |
---|
420 | * |
---|
421 | * Parameters: |
---|
422 | * geometry - {<OpenLayers.Geometry>} The target geometry. |
---|
423 | * options - {Object} Optional properties for configuring the distance |
---|
424 | * calculation. |
---|
425 | * |
---|
426 | * Valid options: |
---|
427 | * details - {Boolean} Return details from the distance calculation. |
---|
428 | * Default is false. |
---|
429 | * edge - {Boolean} Calculate the distance from this geometry to the |
---|
430 | * nearest edge of the target geometry. Default is true. If true, |
---|
431 | * calling distanceTo from a geometry that is wholly contained within |
---|
432 | * the target will result in a non-zero distance. If false, whenever |
---|
433 | * geometries intersect, calling distanceTo will return 0. If false, |
---|
434 | * details cannot be returned. |
---|
435 | * |
---|
436 | * Returns: |
---|
437 | * {Number | Object} The distance between this geometry and the target. |
---|
438 | * If details is true, the return will be an object with distance, |
---|
439 | * x0, y0, x1, and x2 properties. The x0 and y0 properties represent |
---|
440 | * the coordinates of the closest point on this geometry. The x1 and y1 |
---|
441 | * properties represent the coordinates of the closest point on the |
---|
442 | * target geometry. |
---|
443 | */ |
---|
444 | distanceTo: function(geometry, options) { |
---|
445 | var edge = !(options && options.edge === false); |
---|
446 | var details = edge && options && options.details; |
---|
447 | var result, best = {}; |
---|
448 | var min = Number.POSITIVE_INFINITY; |
---|
449 | if(geometry instanceof OpenLayers.Geometry.Point) { |
---|
450 | var segs = this.getSortedSegments(); |
---|
451 | var x = geometry.x; |
---|
452 | var y = geometry.y; |
---|
453 | var seg; |
---|
454 | for(var i=0, len=segs.length; i<len; ++i) { |
---|
455 | seg = segs[i]; |
---|
456 | result = OpenLayers.Geometry.distanceToSegment(geometry, seg); |
---|
457 | if(result.distance < min) { |
---|
458 | min = result.distance; |
---|
459 | best = result; |
---|
460 | if(min === 0) { |
---|
461 | break; |
---|
462 | } |
---|
463 | } else { |
---|
464 | // if distance increases and we cross y0 to the right of x0, no need to keep looking. |
---|
465 | if(seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) { |
---|
466 | break; |
---|
467 | } |
---|
468 | } |
---|
469 | } |
---|
470 | if(details) { |
---|
471 | best = { |
---|
472 | distance: best.distance, |
---|
473 | x0: best.x, y0: best.y, |
---|
474 | x1: x, y1: y |
---|
475 | }; |
---|
476 | } else { |
---|
477 | best = best.distance; |
---|
478 | } |
---|
479 | } else if(geometry instanceof OpenLayers.Geometry.LineString) { |
---|
480 | var segs0 = this.getSortedSegments(); |
---|
481 | var segs1 = geometry.getSortedSegments(); |
---|
482 | var seg0, seg1, intersection, x0, y0; |
---|
483 | var len1 = segs1.length; |
---|
484 | var interOptions = {point: true}; |
---|
485 | outer: for(var i=0, len=segs0.length; i<len; ++i) { |
---|
486 | seg0 = segs0[i]; |
---|
487 | x0 = seg0.x1; |
---|
488 | y0 = seg0.y1; |
---|
489 | for(var j=0; j<len1; ++j) { |
---|
490 | seg1 = segs1[j]; |
---|
491 | intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions); |
---|
492 | if(intersection) { |
---|
493 | min = 0; |
---|
494 | best = { |
---|
495 | distance: 0, |
---|
496 | x0: intersection.x, y0: intersection.y, |
---|
497 | x1: intersection.x, y1: intersection.y |
---|
498 | }; |
---|
499 | break outer; |
---|
500 | } else { |
---|
501 | result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1); |
---|
502 | if(result.distance < min) { |
---|
503 | min = result.distance; |
---|
504 | best = { |
---|
505 | distance: min, |
---|
506 | x0: x0, y0: y0, |
---|
507 | x1: result.x, y1: result.y |
---|
508 | }; |
---|
509 | } |
---|
510 | } |
---|
511 | } |
---|
512 | } |
---|
513 | if(!details) { |
---|
514 | best = best.distance; |
---|
515 | } |
---|
516 | if(min !== 0) { |
---|
517 | // check the final vertex in this line's sorted segments |
---|
518 | if(seg0) { |
---|
519 | result = geometry.distanceTo( |
---|
520 | new OpenLayers.Geometry.Point(seg0.x2, seg0.y2), |
---|
521 | options |
---|
522 | ); |
---|
523 | var dist = details ? result.distance : result; |
---|
524 | if(dist < min) { |
---|
525 | if(details) { |
---|
526 | best = { |
---|
527 | distance: min, |
---|
528 | x0: result.x1, y0: result.y1, |
---|
529 | x1: result.x0, y1: result.y0 |
---|
530 | }; |
---|
531 | } else { |
---|
532 | best = dist; |
---|
533 | } |
---|
534 | } |
---|
535 | } |
---|
536 | } |
---|
537 | } else { |
---|
538 | best = geometry.distanceTo(this, options); |
---|
539 | // swap since target comes from this line |
---|
540 | if(details) { |
---|
541 | best = { |
---|
542 | distance: best.distance, |
---|
543 | x0: best.x1, y0: best.y1, |
---|
544 | x1: best.x0, y1: best.y0 |
---|
545 | }; |
---|
546 | } |
---|
547 | } |
---|
548 | return best; |
---|
549 | }, |
---|
550 | |
---|
551 | CLASS_NAME: "OpenLayers.Geometry.LineString" |
---|
552 | }); |
---|