LineString.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. /* Copyright (c) 2006-2011 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. * @requires OpenLayers/Geometry/Curve.js
  7. */
  8. /**
  9. * Class: OpenLayers.Geometry.LineString
  10. * A LineString is a Curve which, once two points have been added to it, can
  11. * never be less than two points long.
  12. *
  13. * Inherits from:
  14. * - <OpenLayers.Geometry.Curve>
  15. */
  16. OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
  17. /**
  18. * Constructor: OpenLayers.Geometry.LineString
  19. * Create a new LineString geometry
  20. *
  21. * Parameters:
  22. * points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to
  23. * generate the linestring
  24. *
  25. */
  26. initialize: function(points) {
  27. OpenLayers.Geometry.Curve.prototype.initialize.apply(this, arguments);
  28. },
  29. /**
  30. * APIMethod: removeComponent
  31. * Only allows removal of a point if there are three or more points in
  32. * the linestring. (otherwise the result would be just a single point)
  33. *
  34. * Parameters:
  35. * point - {<OpenLayers.Geometry.Point>} The point to be removed
  36. *
  37. * Returns:
  38. * {Boolean} The component was removed.
  39. */
  40. removeComponent: function(point) {
  41. var removed = this.components && (this.components.length > 2);
  42. if (removed) {
  43. OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
  44. arguments);
  45. }
  46. return removed;
  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. * Method: getSortedSegments
  121. *
  122. * Returns:
  123. * {Array} An array of segment objects. Segment objects have properties
  124. * x1, y1, x2, and y2. The start point is represented by x1 and y1.
  125. * The end point is represented by x2 and y2. Start and end are
  126. * ordered so that x1 < x2.
  127. */
  128. getSortedSegments: function() {
  129. var numSeg = this.components.length - 1;
  130. var segments = new Array(numSeg), point1, point2;
  131. for(var i=0; i<numSeg; ++i) {
  132. point1 = this.components[i];
  133. point2 = this.components[i + 1];
  134. if(point1.x < point2.x) {
  135. segments[i] = {
  136. x1: point1.x,
  137. y1: point1.y,
  138. x2: point2.x,
  139. y2: point2.y
  140. };
  141. } else {
  142. segments[i] = {
  143. x1: point2.x,
  144. y1: point2.y,
  145. x2: point1.x,
  146. y2: point1.y
  147. };
  148. }
  149. }
  150. // more efficient to define this somewhere static
  151. function byX1(seg1, seg2) {
  152. return seg1.x1 - seg2.x1;
  153. }
  154. return segments.sort(byX1);
  155. },
  156. /**
  157. * Method: splitWithSegment
  158. * Split this geometry with the given segment.
  159. *
  160. * Parameters:
  161. * seg - {Object} An object with x1, y1, x2, and y2 properties referencing
  162. * segment endpoint coordinates.
  163. * options - {Object} Properties of this object will be used to determine
  164. * how the split is conducted.
  165. *
  166. * Valid options:
  167. * edge - {Boolean} Allow splitting when only edges intersect. Default is
  168. * true. If false, a vertex on the source segment must be within the
  169. * tolerance distance of the intersection to be considered a split.
  170. * tolerance - {Number} If a non-null value is provided, intersections
  171. * within the tolerance distance of one of the source segment's
  172. * endpoints will be assumed to occur at the endpoint.
  173. *
  174. * Returns:
  175. * {Object} An object with *lines* and *points* properties. If the given
  176. * segment intersects this linestring, the lines array will reference
  177. * geometries that result from the split. The points array will contain
  178. * all intersection points. Intersection points are sorted along the
  179. * segment (in order from x1,y1 to x2,y2).
  180. */
  181. splitWithSegment: function(seg, options) {
  182. var edge = !(options && options.edge === false);
  183. var tolerance = options && options.tolerance;
  184. var lines = [];
  185. var verts = this.getVertices();
  186. var points = [];
  187. var intersections = [];
  188. var split = false;
  189. var vert1, vert2, point;
  190. var node, vertex, target;
  191. var interOptions = {point: true, tolerance: tolerance};
  192. var result = null;
  193. for(var i=0, stop=verts.length-2; i<=stop; ++i) {
  194. vert1 = verts[i];
  195. points.push(vert1.clone());
  196. vert2 = verts[i+1];
  197. target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
  198. point = OpenLayers.Geometry.segmentsIntersect(
  199. seg, target, interOptions
  200. );
  201. if(point instanceof OpenLayers.Geometry.Point) {
  202. if((point.x === seg.x1 && point.y === seg.y1) ||
  203. (point.x === seg.x2 && point.y === seg.y2) ||
  204. point.equals(vert1) || point.equals(vert2)) {
  205. vertex = true;
  206. } else {
  207. vertex = false;
  208. }
  209. if(vertex || edge) {
  210. // push intersections different than the previous
  211. if(!point.equals(intersections[intersections.length-1])) {
  212. intersections.push(point.clone());
  213. }
  214. if(i === 0) {
  215. if(point.equals(vert1)) {
  216. continue;
  217. }
  218. }
  219. if(point.equals(vert2)) {
  220. continue;
  221. }
  222. split = true;
  223. if(!point.equals(vert1)) {
  224. points.push(point);
  225. }
  226. lines.push(new OpenLayers.Geometry.LineString(points));
  227. points = [point.clone()];
  228. }
  229. }
  230. }
  231. if(split) {
  232. points.push(vert2.clone());
  233. lines.push(new OpenLayers.Geometry.LineString(points));
  234. }
  235. if(intersections.length > 0) {
  236. // sort intersections along segment
  237. var xDir = seg.x1 < seg.x2 ? 1 : -1;
  238. var yDir = seg.y1 < seg.y2 ? 1 : -1;
  239. result = {
  240. lines: lines,
  241. points: intersections.sort(function(p1, p2) {
  242. return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
  243. })
  244. };
  245. }
  246. return result;
  247. },
  248. /**
  249. * Method: split
  250. * Use this geometry (the source) to attempt to split a target geometry.
  251. *
  252. * Parameters:
  253. * target - {<OpenLayers.Geometry>} The target geometry.
  254. * options - {Object} Properties of this object will be used to determine
  255. * how the split is conducted.
  256. *
  257. * Valid options:
  258. * mutual - {Boolean} Split the source geometry in addition to the target
  259. * geometry. Default is false.
  260. * edge - {Boolean} Allow splitting when only edges intersect. Default is
  261. * true. If false, a vertex on the source must be within the tolerance
  262. * distance of the intersection to be considered a split.
  263. * tolerance - {Number} If a non-null value is provided, intersections
  264. * within the tolerance distance of an existing vertex on the source
  265. * will be assumed to occur at the vertex.
  266. *
  267. * Returns:
  268. * {Array} A list of geometries (of this same type as the target) that
  269. * result from splitting the target with the source geometry. The
  270. * source and target geometry will remain unmodified. If no split
  271. * results, null will be returned. If mutual is true and a split
  272. * results, return will be an array of two arrays - the first will be
  273. * all geometries that result from splitting the source geometry and
  274. * the second will be all geometries that result from splitting the
  275. * target geometry.
  276. */
  277. split: function(target, options) {
  278. var results = null;
  279. var mutual = options && options.mutual;
  280. var sourceSplit, targetSplit, sourceParts, targetParts;
  281. if(target instanceof OpenLayers.Geometry.LineString) {
  282. var verts = this.getVertices();
  283. var vert1, vert2, seg, splits, lines, point;
  284. var points = [];
  285. sourceParts = [];
  286. for(var i=0, stop=verts.length-2; i<=stop; ++i) {
  287. vert1 = verts[i];
  288. vert2 = verts[i+1];
  289. seg = {
  290. x1: vert1.x, y1: vert1.y,
  291. x2: vert2.x, y2: vert2.y
  292. };
  293. targetParts = targetParts || [target];
  294. if(mutual) {
  295. points.push(vert1.clone());
  296. }
  297. for(var j=0; j<targetParts.length; ++j) {
  298. splits = targetParts[j].splitWithSegment(seg, options);
  299. if(splits) {
  300. // splice in new features
  301. lines = splits.lines;
  302. if(lines.length > 0) {
  303. lines.unshift(j, 1);
  304. Array.prototype.splice.apply(targetParts, lines);
  305. j += lines.length - 2;
  306. }
  307. if(mutual) {
  308. for(var k=0, len=splits.points.length; k<len; ++k) {
  309. point = splits.points[k];
  310. if(!point.equals(vert1)) {
  311. points.push(point);
  312. sourceParts.push(new OpenLayers.Geometry.LineString(points));
  313. if(point.equals(vert2)) {
  314. points = [];
  315. } else {
  316. points = [point.clone()];
  317. }
  318. }
  319. }
  320. }
  321. }
  322. }
  323. }
  324. if(mutual && sourceParts.length > 0 && points.length > 0) {
  325. points.push(vert2.clone());
  326. sourceParts.push(new OpenLayers.Geometry.LineString(points));
  327. }
  328. } else {
  329. results = target.splitWith(this, options);
  330. }
  331. if(targetParts && targetParts.length > 1) {
  332. targetSplit = true;
  333. } else {
  334. targetParts = [];
  335. }
  336. if(sourceParts && sourceParts.length > 1) {
  337. sourceSplit = true;
  338. } else {
  339. sourceParts = [];
  340. }
  341. if(targetSplit || sourceSplit) {
  342. if(mutual) {
  343. results = [sourceParts, targetParts];
  344. } else {
  345. results = targetParts;
  346. }
  347. }
  348. return results;
  349. },
  350. /**
  351. * Method: splitWith
  352. * Split this geometry (the target) with the given geometry (the source).
  353. *
  354. * Parameters:
  355. * geometry - {<OpenLayers.Geometry>} A geometry used to split this
  356. * geometry (the source).
  357. * options - {Object} Properties of this object will be used to determine
  358. * how the split is conducted.
  359. *
  360. * Valid options:
  361. * mutual - {Boolean} Split the source geometry in addition to the target
  362. * geometry. Default is false.
  363. * edge - {Boolean} Allow splitting when only edges intersect. Default is
  364. * true. If false, a vertex on the source must be within the tolerance
  365. * distance of the intersection to be considered a split.
  366. * tolerance - {Number} If a non-null value is provided, intersections
  367. * within the tolerance distance of an existing vertex on the source
  368. * will be assumed to occur at the vertex.
  369. *
  370. * Returns:
  371. * {Array} A list of geometries (of this same type as the target) that
  372. * result from splitting the target with the source geometry. The
  373. * source and target geometry will remain unmodified. If no split
  374. * results, null will be returned. If mutual is true and a split
  375. * results, return will be an array of two arrays - the first will be
  376. * all geometries that result from splitting the source geometry and
  377. * the second will be all geometries that result from splitting the
  378. * target geometry.
  379. */
  380. splitWith: function(geometry, options) {
  381. return geometry.split(this, options);
  382. },
  383. /**
  384. * APIMethod: getVertices
  385. * Return a list of all points in this geometry.
  386. *
  387. * Parameters:
  388. * nodes - {Boolean} For lines, only return vertices that are
  389. * endpoints. If false, for lines, only vertices that are not
  390. * endpoints will be returned. If not provided, all vertices will
  391. * be returned.
  392. *
  393. * Returns:
  394. * {Array} A list of all vertices in the geometry.
  395. */
  396. getVertices: function(nodes) {
  397. var vertices;
  398. if(nodes === true) {
  399. vertices = [
  400. this.components[0],
  401. this.components[this.components.length-1]
  402. ];
  403. } else if (nodes === false) {
  404. vertices = this.components.slice(1, this.components.length-1);
  405. } else {
  406. vertices = this.components.slice();
  407. }
  408. return vertices;
  409. },
  410. /**
  411. * APIMethod: distanceTo
  412. * Calculate the closest distance between two geometries (on the x-y plane).
  413. *
  414. * Parameters:
  415. * geometry - {<OpenLayers.Geometry>} The target geometry.
  416. * options - {Object} Optional properties for configuring the distance
  417. * calculation.
  418. *
  419. * Valid options:
  420. * details - {Boolean} Return details from the distance calculation.
  421. * Default is false.
  422. * edge - {Boolean} Calculate the distance from this geometry to the
  423. * nearest edge of the target geometry. Default is true. If true,
  424. * calling distanceTo from a geometry that is wholly contained within
  425. * the target will result in a non-zero distance. If false, whenever
  426. * geometries intersect, calling distanceTo will return 0. If false,
  427. * details cannot be returned.
  428. *
  429. * Returns:
  430. * {Number | Object} The distance between this geometry and the target.
  431. * If details is true, the return will be an object with distance,
  432. * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
  433. * the coordinates of the closest point on this geometry. The x1 and y1
  434. * properties represent the coordinates of the closest point on the
  435. * target geometry.
  436. */
  437. distanceTo: function(geometry, options) {
  438. var edge = !(options && options.edge === false);
  439. var details = edge && options && options.details;
  440. var result, best = {};
  441. var min = Number.POSITIVE_INFINITY;
  442. if(geometry instanceof OpenLayers.Geometry.Point) {
  443. var segs = this.getSortedSegments();
  444. var x = geometry.x;
  445. var y = geometry.y;
  446. var seg;
  447. for(var i=0, len=segs.length; i<len; ++i) {
  448. seg = segs[i];
  449. result = OpenLayers.Geometry.distanceToSegment(geometry, seg);
  450. if(result.distance < min) {
  451. min = result.distance;
  452. best = result;
  453. if(min === 0) {
  454. break;
  455. }
  456. } else {
  457. // if distance increases and we cross y0 to the right of x0, no need to keep looking.
  458. if(seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) {
  459. break;
  460. }
  461. }
  462. }
  463. if(details) {
  464. best = {
  465. distance: best.distance,
  466. x0: best.x, y0: best.y,
  467. x1: x, y1: y
  468. };
  469. } else {
  470. best = best.distance;
  471. }
  472. } else if(geometry instanceof OpenLayers.Geometry.LineString) {
  473. var segs0 = this.getSortedSegments();
  474. var segs1 = geometry.getSortedSegments();
  475. var seg0, seg1, intersection, x0, y0;
  476. var len1 = segs1.length;
  477. var interOptions = {point: true};
  478. outer: for(var i=0, len=segs0.length; i<len; ++i) {
  479. seg0 = segs0[i];
  480. x0 = seg0.x1;
  481. y0 = seg0.y1;
  482. for(var j=0; j<len1; ++j) {
  483. seg1 = segs1[j];
  484. intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);
  485. if(intersection) {
  486. min = 0;
  487. best = {
  488. distance: 0,
  489. x0: intersection.x, y0: intersection.y,
  490. x1: intersection.x, y1: intersection.y
  491. };
  492. break outer;
  493. } else {
  494. result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
  495. if(result.distance < min) {
  496. min = result.distance;
  497. best = {
  498. distance: min,
  499. x0: x0, y0: y0,
  500. x1: result.x, y1: result.y
  501. };
  502. }
  503. }
  504. }
  505. }
  506. if(!details) {
  507. best = best.distance;
  508. }
  509. if(min !== 0) {
  510. // check the final vertex in this line's sorted segments
  511. if(seg0) {
  512. result = geometry.distanceTo(
  513. new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),
  514. options
  515. );
  516. var dist = details ? result.distance : result;
  517. if(dist < min) {
  518. if(details) {
  519. best = {
  520. distance: min,
  521. x0: result.x1, y0: result.y1,
  522. x1: result.x0, y1: result.y0
  523. };
  524. } else {
  525. best = dist;
  526. }
  527. }
  528. }
  529. }
  530. } else {
  531. best = geometry.distanceTo(this, options);
  532. // swap since target comes from this line
  533. if(details) {
  534. best = {
  535. distance: best.distance,
  536. x0: best.x1, y0: best.y1,
  537. x1: best.x0, y1: best.y0
  538. };
  539. }
  540. }
  541. return best;
  542. },
  543. /**
  544. * APIMethod: simplify
  545. * This function will return a simplified LineString.
  546. * Simplification is based on the Douglas-Peucker algorithm.
  547. *
  548. *
  549. * Parameters:
  550. * tolerance - {number} threshhold for simplification in map units
  551. *
  552. * Returns:
  553. * {OpenLayers.Geometry.LineString} the simplified LineString
  554. */
  555. simplify: function(tolerance){
  556. if (this && this !== null) {
  557. var points = this.getVertices();
  558. if (points.length < 3) {
  559. return this;
  560. }
  561. var compareNumbers = function(a, b){
  562. return (a-b);
  563. };
  564. /**
  565. * Private function doing the Douglas-Peucker reduction
  566. */
  567. var douglasPeuckerReduction = function(points, firstPoint, lastPoint, tolerance){
  568. var maxDistance = 0;
  569. var indexFarthest = 0;
  570. for (var index = firstPoint, distance; index < lastPoint; index++) {
  571. distance = perpendicularDistance(points[firstPoint], points[lastPoint], points[index]);
  572. if (distance > maxDistance) {
  573. maxDistance = distance;
  574. indexFarthest = index;
  575. }
  576. }
  577. if (maxDistance > tolerance && indexFarthest != firstPoint) {
  578. //Add the largest point that exceeds the tolerance
  579. pointIndexsToKeep.push(indexFarthest);
  580. douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance);
  581. douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance);
  582. }
  583. };
  584. /**
  585. * Private function calculating the perpendicular distance
  586. * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower
  587. */
  588. var perpendicularDistance = function(point1, point2, point){
  589. //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle
  590. //Base = v((x1-x2)²+(x1-x2)²) *Base of Triangle*
  591. //Area = .5*Base*H *Solve for height
  592. //Height = Area/.5/Base
  593. var area = Math.abs(0.5 * (point1.x * point2.y + point2.x * point.y + point.x * point1.y - point2.x * point1.y - point.x * point2.y - point1.x * point.y));
  594. var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
  595. var height = area / bottom * 2;
  596. return height;
  597. };
  598. var firstPoint = 0;
  599. var lastPoint = points.length - 1;
  600. var pointIndexsToKeep = [];
  601. //Add the first and last index to the keepers
  602. pointIndexsToKeep.push(firstPoint);
  603. pointIndexsToKeep.push(lastPoint);
  604. //The first and the last point cannot be the same
  605. while (points[firstPoint].equals(points[lastPoint])) {
  606. lastPoint--;
  607. //Addition: the first point not equal to first point in the LineString is kept as well
  608. pointIndexsToKeep.push(lastPoint);
  609. }
  610. douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance);
  611. var returnPoints = [];
  612. pointIndexsToKeep.sort(compareNumbers);
  613. for (var index = 0; index < pointIndexsToKeep.length; index++) {
  614. returnPoints.push(points[pointIndexsToKeep[index]]);
  615. }
  616. return new OpenLayers.Geometry.LineString(returnPoints);
  617. }
  618. else {
  619. return this;
  620. }
  621. },
  622. CLASS_NAME: "OpenLayers.Geometry.LineString"
  623. });