LinearRing.js 15 KB

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