LinearRing.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the 2-clause BSD license.
  3. * See license.txt in the OpenLayers distribution or repository for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/Geometry/LineString.js
  7. */
  8. /**
  9. * Class: OpenLayers.Geometry.LinearRing
  10. *
  11. * A Linear Ring is a special LineString which is closed. It closes itself
  12. * automatically on every addPoint/removePoint by adding a copy of the first
  13. * point as the last point.
  14. *
  15. * Also, as it is the first in the line family to close itself, a getArea()
  16. * function is defined to calculate the enclosed area of the linearRing
  17. *
  18. * Inherits:
  19. * - <OpenLayers.Geometry.LineString>
  20. */
  21. OpenLayers.Geometry.LinearRing = OpenLayers.Class(
  22. OpenLayers.Geometry.LineString, {
  23. /**
  24. * Property: componentTypes
  25. * {Array(String)} An array of class names representing the types of
  26. * components that the collection can include. A null
  27. * value means the component types are not restricted.
  28. */
  29. componentTypes: ["OpenLayers.Geometry.Point"],
  30. /**
  31. * Constructor: OpenLayers.Geometry.LinearRing
  32. * Linear rings are constructed with an array of points. This array
  33. * can represent a closed or open ring. If the ring is open (the last
  34. * point does not equal the first point), the constructor will close
  35. * the ring. If the ring is already closed (the last point does equal
  36. * the first point), it will be left closed.
  37. *
  38. * Parameters:
  39. * points - {Array(<OpenLayers.Geometry.Point>)} points
  40. */
  41. /**
  42. * APIMethod: addComponent
  43. * Adds a point to geometry components. If the point is to be added to
  44. * the end of the components array and it is the same as the last point
  45. * already in that array, the duplicate point is not added. This has
  46. * the effect of closing the ring if it is not already closed, and
  47. * doing the right thing if it is already closed. This behavior can
  48. * be overridden by calling the method with a non-null index as the
  49. * second argument.
  50. *
  51. * Parameters:
  52. * point - {<OpenLayers.Geometry.Point>}
  53. * index - {Integer} Index into the array to insert the component
  54. *
  55. * Returns:
  56. * {Boolean} Was the Point successfully added?
  57. */
  58. addComponent: function(point, index) {
  59. var added = false;
  60. //remove last point
  61. var lastPoint = this.components.pop();
  62. // given an index, add the point
  63. // without an index only add non-duplicate points
  64. if(index != null || !point.equals(lastPoint)) {
  65. added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
  66. arguments);
  67. }
  68. //append copy of first point
  69. var firstPoint = this.components[0];
  70. OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
  71. [firstPoint]);
  72. return added;
  73. },
  74. /**
  75. * APIMethod: removeComponent
  76. * Removes a point from geometry components.
  77. *
  78. * Parameters:
  79. * point - {<OpenLayers.Geometry.Point>}
  80. *
  81. * Returns:
  82. * {Boolean} The component was removed.
  83. */
  84. removeComponent: function(point) {
  85. var removed = this.components && (this.components.length > 3);
  86. if (removed) {
  87. //remove last point
  88. this.components.pop();
  89. //remove our point
  90. OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
  91. arguments);
  92. //append copy of first point
  93. var firstPoint = this.components[0];
  94. OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
  95. [firstPoint]);
  96. }
  97. return removed;
  98. },
  99. /**
  100. * APIMethod: move
  101. * Moves a geometry by the given displacement along positive x and y axes.
  102. * This modifies the position of the geometry and clears the cached
  103. * bounds.
  104. *
  105. * Parameters:
  106. * x - {Float} Distance to move geometry in positive x direction.
  107. * y - {Float} Distance to move geometry in positive y direction.
  108. */
  109. move: function(x, y) {
  110. for(var i = 0, len=this.components.length; i<len - 1; i++) {
  111. this.components[i].move(x, y);
  112. }
  113. },
  114. /**
  115. * APIMethod: rotate
  116. * Rotate a geometry around some origin
  117. *
  118. * Parameters:
  119. * angle - {Float} Rotation angle in degrees (measured counterclockwise
  120. * from the positive x-axis)
  121. * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
  122. */
  123. rotate: function(angle, origin) {
  124. for(var i=0, len=this.components.length; i<len - 1; ++i) {
  125. this.components[i].rotate(angle, origin);
  126. }
  127. },
  128. /**
  129. * APIMethod: resize
  130. * Resize a geometry relative to some origin. Use this method to apply
  131. * a uniform scaling to a geometry.
  132. *
  133. * Parameters:
  134. * scale - {Float} Factor by which to scale the geometry. A scale of 2
  135. * doubles the size of the geometry in each dimension
  136. * (lines, for example, will be twice as long, and polygons
  137. * will have four times the area).
  138. * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
  139. * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
  140. *
  141. * Returns:
  142. * {<OpenLayers.Geometry>} - The current geometry.
  143. */
  144. resize: function(scale, origin, ratio) {
  145. for(var i=0, len=this.components.length; i<len - 1; ++i) {
  146. this.components[i].resize(scale, origin, ratio);
  147. }
  148. return this;
  149. },
  150. /**
  151. * APIMethod: transform
  152. * Reproject the components geometry from source to dest.
  153. *
  154. * Parameters:
  155. * source - {<OpenLayers.Projection>}
  156. * dest - {<OpenLayers.Projection>}
  157. *
  158. * Returns:
  159. * {<OpenLayers.Geometry>}
  160. */
  161. transform: function(source, dest) {
  162. if (source && dest) {
  163. for (var i=0, len=this.components.length; i<len - 1; i++) {
  164. var component = this.components[i];
  165. component.transform(source, dest);
  166. }
  167. this.bounds = null;
  168. }
  169. return this;
  170. },
  171. /**
  172. * APIMethod: getCentroid
  173. *
  174. * Returns:
  175. * {<OpenLayers.Geometry.Point>} The centroid of the collection
  176. */
  177. getCentroid: function() {
  178. if (this.components) {
  179. var len = this.components.length;
  180. if (len > 0 && len <= 2) {
  181. return this.components[0].clone();
  182. } else if (len > 2) {
  183. var sumX = 0.0;
  184. var sumY = 0.0;
  185. var x0 = this.components[0].x;
  186. var y0 = this.components[0].y;
  187. var area = -1 * this.getArea();
  188. if (area != 0) {
  189. for (var i = 0; i < len - 1; i++) {
  190. var b = this.components[i];
  191. var c = this.components[i+1];
  192. sumX += (b.x + c.x - 2 * x0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
  193. sumY += (b.y + c.y - 2 * y0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
  194. }
  195. var x = x0 + sumX / (6 * area);
  196. var y = y0 + sumY / (6 * area);
  197. } else {
  198. for (var i = 0; i < len - 1; i++) {
  199. sumX += this.components[i].x;
  200. sumY += this.components[i].y;
  201. }
  202. var x = sumX / (len - 1);
  203. var y = sumY / (len - 1);
  204. }
  205. return new OpenLayers.Geometry.Point(x, y);
  206. } else {
  207. return null;
  208. }
  209. }
  210. },
  211. /**
  212. * APIMethod: getArea
  213. * Note - The area is positive if the ring is oriented CW, otherwise
  214. * it will be negative.
  215. *
  216. * Returns:
  217. * {Float} The signed area for a ring.
  218. */
  219. getArea: function() {
  220. var area = 0.0;
  221. if ( this.components && (this.components.length > 2)) {
  222. var sum = 0.0;
  223. for (var i=0, len=this.components.length; i<len - 1; i++) {
  224. var b = this.components[i];
  225. var c = this.components[i+1];
  226. sum += (b.x + c.x) * (c.y - b.y);
  227. }
  228. area = - sum / 2.0;
  229. }
  230. return area;
  231. },
  232. /**
  233. * APIMethod: getGeodesicArea
  234. * Calculate the approximate area of the polygon were it projected onto
  235. * the earth. Note that this area will be positive if ring is oriented
  236. * clockwise, otherwise it will be negative.
  237. *
  238. * Parameters:
  239. * projection - {<OpenLayers.Projection>} The spatial reference system
  240. * for the geometry coordinates. If not provided, Geographic/WGS84 is
  241. * assumed.
  242. *
  243. * Reference:
  244. * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
  245. * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
  246. * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
  247. *
  248. * Returns:
  249. * {float} The approximate signed geodesic area of the polygon in square
  250. * meters.
  251. */
  252. getGeodesicArea: function(projection) {
  253. var ring = this; // so we can work with a clone if needed
  254. if(projection) {
  255. var gg = new OpenLayers.Projection("EPSG:4326");
  256. if(!gg.equals(projection)) {
  257. ring = this.clone().transform(projection, gg);
  258. }
  259. }
  260. var area = 0.0;
  261. var len = ring.components && ring.components.length;
  262. if(len > 2) {
  263. var p1, p2;
  264. for(var i=0; i<len-1; i++) {
  265. p1 = ring.components[i];
  266. p2 = ring.components[i+1];
  267. area += OpenLayers.Util.rad(p2.x - p1.x) *
  268. (2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
  269. Math.sin(OpenLayers.Util.rad(p2.y)));
  270. }
  271. area = area * 6378137.0 * 6378137.0 / 2.0;
  272. }
  273. return area;
  274. },
  275. /**
  276. * Method: containsPoint
  277. * Test if a point is inside a linear ring. For the case where a point
  278. * is coincident with a linear ring edge, returns 1. Otherwise,
  279. * returns boolean.
  280. *
  281. * Parameters:
  282. * point - {<OpenLayers.Geometry.Point>}
  283. *
  284. * Returns:
  285. * {Boolean | Number} The point is inside the linear ring. Returns 1 if
  286. * the point is coincident with an edge. Returns boolean otherwise.
  287. */
  288. containsPoint: function(point) {
  289. var approx = OpenLayers.Number.limitSigDigs;
  290. var digs = 14;
  291. var px = approx(point.x, digs);
  292. var py = approx(point.y, digs);
  293. function getX(y, x1, y1, x2, y2) {
  294. return (y - y2) * ((x2 - x1) / (y2 - y1)) + x2;
  295. }
  296. var numSeg = this.components.length - 1;
  297. var start, end, x1, y1, x2, y2, cx, cy;
  298. var crosses = 0;
  299. for(var i=0; i<numSeg; ++i) {
  300. start = this.components[i];
  301. x1 = approx(start.x, digs);
  302. y1 = approx(start.y, digs);
  303. end = this.components[i + 1];
  304. x2 = approx(end.x, digs);
  305. y2 = approx(end.y, digs);
  306. /**
  307. * The following conditions enforce five edge-crossing rules:
  308. * 1. points coincident with edges are considered contained;
  309. * 2. an upward edge includes its starting endpoint, and
  310. * excludes its final endpoint;
  311. * 3. a downward edge excludes its starting endpoint, and
  312. * includes its final endpoint;
  313. * 4. horizontal edges are excluded; and
  314. * 5. the edge-ray intersection point must be strictly right
  315. * of the point P.
  316. */
  317. if(y1 == y2) {
  318. // horizontal edge
  319. if(py == y1) {
  320. // point on horizontal line
  321. if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
  322. x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
  323. // point on edge
  324. crosses = -1;
  325. break;
  326. }
  327. }
  328. // ignore other horizontal edges
  329. continue;
  330. }
  331. cx = approx(getX(py, x1, y1, x2, y2), digs);
  332. if(cx == px) {
  333. // point on line
  334. if(y1 < y2 && (py >= y1 && py <= y2) || // upward
  335. y1 > y2 && (py <= y1 && py >= y2)) { // downward
  336. // point on edge
  337. crosses = -1;
  338. break;
  339. }
  340. }
  341. if(cx <= px) {
  342. // no crossing to the right
  343. continue;
  344. }
  345. if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
  346. // no crossing
  347. continue;
  348. }
  349. if(y1 < y2 && (py >= y1 && py < y2) || // upward
  350. y1 > y2 && (py < y1 && py >= y2)) { // downward
  351. ++crosses;
  352. }
  353. }
  354. var contained = (crosses == -1) ?
  355. // on edge
  356. 1 :
  357. // even (out) or odd (in)
  358. !!(crosses & 1);
  359. return contained;
  360. },
  361. /**
  362. * APIMethod: intersects
  363. * Determine if the input geometry intersects this one.
  364. *
  365. * Parameters:
  366. * geometry - {<OpenLayers.Geometry>} Any type of geometry.
  367. *
  368. * Returns:
  369. * {Boolean} The input geometry intersects this one.
  370. */
  371. intersects: function(geometry) {
  372. var intersect = false;
  373. if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
  374. intersect = this.containsPoint(geometry);
  375. } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
  376. intersect = geometry.intersects(this);
  377. } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
  378. intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply(
  379. this, [geometry]
  380. );
  381. } else {
  382. // check for component intersections
  383. for(var i=0, len=geometry.components.length; i<len; ++ i) {
  384. intersect = geometry.components[i].intersects(this);
  385. if(intersect) {
  386. break;
  387. }
  388. }
  389. }
  390. return intersect;
  391. },
  392. /**
  393. * APIMethod: getVertices
  394. * Return a list of all points in this geometry.
  395. *
  396. * Parameters:
  397. * nodes - {Boolean} For lines, only return vertices that are
  398. * endpoints. If false, for lines, only vertices that are not
  399. * endpoints will be returned. If not provided, all vertices will
  400. * be returned.
  401. *
  402. * Returns:
  403. * {Array} A list of all vertices in the geometry.
  404. */
  405. getVertices: function(nodes) {
  406. return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
  407. },
  408. CLASS_NAME: "OpenLayers.Geometry.LinearRing"
  409. });