Collection.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  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.js
  7. */
  8. /**
  9. * Class: OpenLayers.Geometry.Collection
  10. * A Collection is exactly what it sounds like: A collection of different
  11. * Geometries. These are stored in the local parameter <components> (which
  12. * can be passed as a parameter to the constructor).
  13. *
  14. * As new geometries are added to the collection, they are NOT cloned.
  15. * When removing geometries, they need to be specified by reference (ie you
  16. * have to pass in the *exact* geometry to be removed).
  17. *
  18. * The <getArea> and <getLength> functions here merely iterate through
  19. * the components, summing their respective areas and lengths.
  20. *
  21. * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
  22. *
  23. * Inerhits from:
  24. * - <OpenLayers.Geometry>
  25. */
  26. OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
  27. /**
  28. * APIProperty: components
  29. * {Array(<OpenLayers.Geometry>)} The component parts of this geometry
  30. */
  31. components: null,
  32. /**
  33. * Property: componentTypes
  34. * {Array(String)} An array of class names representing the types of
  35. * components that the collection can include. A null value means the
  36. * component types are not restricted.
  37. */
  38. componentTypes: null,
  39. /**
  40. * Constructor: OpenLayers.Geometry.Collection
  41. * Creates a Geometry Collection -- a list of geoms.
  42. *
  43. * Parameters:
  44. * components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
  45. *
  46. */
  47. initialize: function (components) {
  48. OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
  49. this.components = [];
  50. if (components != null) {
  51. this.addComponents(components);
  52. }
  53. },
  54. /**
  55. * APIMethod: destroy
  56. * Destroy this geometry.
  57. */
  58. destroy: function () {
  59. this.components.length = 0;
  60. this.components = null;
  61. OpenLayers.Geometry.prototype.destroy.apply(this, arguments);
  62. },
  63. /**
  64. * APIMethod: clone
  65. * Clone this geometry.
  66. *
  67. * Returns:
  68. * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
  69. */
  70. clone: function() {
  71. var geometry = eval("new " + this.CLASS_NAME + "()");
  72. for(var i=0, len=this.components.length; i<len; i++) {
  73. geometry.addComponent(this.components[i].clone());
  74. }
  75. // catch any randomly tagged-on properties
  76. OpenLayers.Util.applyDefaults(geometry, this);
  77. return geometry;
  78. },
  79. /**
  80. * Method: getComponentsString
  81. * Get a string representing the components for this collection
  82. *
  83. * Returns:
  84. * {String} A string representation of the components of this geometry
  85. */
  86. getComponentsString: function(){
  87. var strings = [];
  88. for(var i=0, len=this.components.length; i<len; i++) {
  89. strings.push(this.components[i].toShortString());
  90. }
  91. return strings.join(",");
  92. },
  93. /**
  94. * APIMethod: calculateBounds
  95. * Recalculate the bounds by iterating through the components and
  96. * calling calling extendBounds() on each item.
  97. */
  98. calculateBounds: function() {
  99. this.bounds = null;
  100. var bounds = new OpenLayers.Bounds();
  101. var components = this.components;
  102. if (components) {
  103. for (var i=0, len=components.length; i<len; i++) {
  104. bounds.extend(components[i].getBounds());
  105. }
  106. }
  107. // to preserve old behavior, we only set bounds if non-null
  108. // in the future, we could add bounds.isEmpty()
  109. if (bounds.left != null && bounds.bottom != null &&
  110. bounds.right != null && bounds.top != null) {
  111. this.setBounds(bounds);
  112. }
  113. },
  114. /**
  115. * APIMethod: addComponents
  116. * Add components to this geometry.
  117. *
  118. * Parameters:
  119. * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
  120. */
  121. addComponents: function(components){
  122. if(!(OpenLayers.Util.isArray(components))) {
  123. components = [components];
  124. }
  125. for(var i=0, len=components.length; i<len; i++) {
  126. this.addComponent(components[i]);
  127. }
  128. },
  129. /**
  130. * Method: addComponent
  131. * Add a new component (geometry) to the collection. If this.componentTypes
  132. * is set, then the component class name must be in the componentTypes array.
  133. *
  134. * The bounds cache is reset.
  135. *
  136. * Parameters:
  137. * component - {<OpenLayers.Geometry>} A geometry to add
  138. * index - {int} Optional index into the array to insert the component
  139. *
  140. * Returns:
  141. * {Boolean} The component geometry was successfully added
  142. */
  143. addComponent: function(component, index) {
  144. var added = false;
  145. if(component) {
  146. if(this.componentTypes == null ||
  147. (OpenLayers.Util.indexOf(this.componentTypes,
  148. component.CLASS_NAME) > -1)) {
  149. if(index != null && (index < this.components.length)) {
  150. var components1 = this.components.slice(0, index);
  151. var components2 = this.components.slice(index,
  152. this.components.length);
  153. components1.push(component);
  154. this.components = components1.concat(components2);
  155. } else {
  156. this.components.push(component);
  157. }
  158. component.parent = this;
  159. this.clearBounds();
  160. added = true;
  161. }
  162. }
  163. return added;
  164. },
  165. /**
  166. * APIMethod: removeComponents
  167. * Remove components from this geometry.
  168. *
  169. * Parameters:
  170. * components - {Array(<OpenLayers.Geometry>)} The components to be removed
  171. *
  172. * Returns:
  173. * {Boolean} A component was removed.
  174. */
  175. removeComponents: function(components) {
  176. var removed = false;
  177. if(!(OpenLayers.Util.isArray(components))) {
  178. components = [components];
  179. }
  180. for(var i=components.length-1; i>=0; --i) {
  181. removed = this.removeComponent(components[i]) || removed;
  182. }
  183. return removed;
  184. },
  185. /**
  186. * Method: removeComponent
  187. * Remove a component from this geometry.
  188. *
  189. * Parameters:
  190. * component - {<OpenLayers.Geometry>}
  191. *
  192. * Returns:
  193. * {Boolean} The component was removed.
  194. */
  195. removeComponent: function(component) {
  196. OpenLayers.Util.removeItem(this.components, component);
  197. // clearBounds() so that it gets recalculated on the next call
  198. // to this.getBounds();
  199. this.clearBounds();
  200. return true;
  201. },
  202. /**
  203. * APIMethod: getLength
  204. * Calculate the length of this geometry
  205. *
  206. * Returns:
  207. * {Float} The length of the geometry
  208. */
  209. getLength: function() {
  210. var length = 0.0;
  211. for (var i=0, len=this.components.length; i<len; i++) {
  212. length += this.components[i].getLength();
  213. }
  214. return length;
  215. },
  216. /**
  217. * APIMethod: getArea
  218. * Calculate the area of this geometry. Note how this function is overridden
  219. * in <OpenLayers.Geometry.Polygon>.
  220. *
  221. * Returns:
  222. * {Float} The area of the collection by summing its parts
  223. */
  224. getArea: function() {
  225. var area = 0.0;
  226. for (var i=0, len=this.components.length; i<len; i++) {
  227. area += this.components[i].getArea();
  228. }
  229. return area;
  230. },
  231. /**
  232. * APIMethod: getGeodesicArea
  233. * Calculate the approximate area of the polygon were it projected onto
  234. * the earth.
  235. *
  236. * Parameters:
  237. * projection - {<OpenLayers.Projection>} The spatial reference system
  238. * for the geometry coordinates. If not provided, Geographic/WGS84 is
  239. * assumed.
  240. *
  241. * Reference:
  242. * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
  243. * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
  244. * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
  245. *
  246. * Returns:
  247. * {float} The approximate geodesic area of the geometry in square meters.
  248. */
  249. getGeodesicArea: function(projection) {
  250. var area = 0.0;
  251. for(var i=0, len=this.components.length; i<len; i++) {
  252. area += this.components[i].getGeodesicArea(projection);
  253. }
  254. return area;
  255. },
  256. /**
  257. * APIMethod: getCentroid
  258. *
  259. * Compute the centroid for this geometry collection.
  260. *
  261. * Parameters:
  262. * weighted - {Boolean} Perform the getCentroid computation recursively,
  263. * returning an area weighted average of all geometries in this collection.
  264. *
  265. * Returns:
  266. * {<OpenLayers.Geometry.Point>} The centroid of the collection
  267. */
  268. getCentroid: function(weighted) {
  269. if (!weighted) {
  270. return this.components.length && this.components[0].getCentroid();
  271. }
  272. var len = this.components.length;
  273. if (!len) {
  274. return false;
  275. }
  276. var areas = [];
  277. var centroids = [];
  278. var areaSum = 0;
  279. var minArea = Number.MAX_VALUE;
  280. var component;
  281. for (var i=0; i<len; ++i) {
  282. component = this.components[i];
  283. var area = component.getArea();
  284. var centroid = component.getCentroid(true);
  285. if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
  286. continue;
  287. }
  288. areas.push(area);
  289. areaSum += area;
  290. minArea = (area < minArea && area > 0) ? area : minArea;
  291. centroids.push(centroid);
  292. }
  293. len = areas.length;
  294. if (areaSum === 0) {
  295. // all the components in this collection have 0 area
  296. // probably a collection of points -- weight all the points the same
  297. for (var i=0; i<len; ++i) {
  298. areas[i] = 1;
  299. }
  300. areaSum = areas.length;
  301. } else {
  302. // normalize all the areas where the smallest area will get
  303. // a value of 1
  304. for (var i=0; i<len; ++i) {
  305. areas[i] /= minArea;
  306. }
  307. areaSum /= minArea;
  308. }
  309. var xSum = 0, ySum = 0, centroid, area;
  310. for (var i=0; i<len; ++i) {
  311. centroid = centroids[i];
  312. area = areas[i];
  313. xSum += centroid.x * area;
  314. ySum += centroid.y * area;
  315. }
  316. return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
  317. },
  318. /**
  319. * APIMethod: getGeodesicLength
  320. * Calculate the approximate length of the geometry were it projected onto
  321. * the earth.
  322. *
  323. * projection - {<OpenLayers.Projection>} The spatial reference system
  324. * for the geometry coordinates. If not provided, Geographic/WGS84 is
  325. * assumed.
  326. *
  327. * Returns:
  328. * {Float} The appoximate geodesic length of the geometry in meters.
  329. */
  330. getGeodesicLength: function(projection) {
  331. var length = 0.0;
  332. for(var i=0, len=this.components.length; i<len; i++) {
  333. length += this.components[i].getGeodesicLength(projection);
  334. }
  335. return length;
  336. },
  337. /**
  338. * APIMethod: move
  339. * Moves a geometry by the given displacement along positive x and y axes.
  340. * This modifies the position of the geometry and clears the cached
  341. * bounds.
  342. *
  343. * Parameters:
  344. * x - {Float} Distance to move geometry in positive x direction.
  345. * y - {Float} Distance to move geometry in positive y direction.
  346. */
  347. move: function(x, y) {
  348. for(var i=0, len=this.components.length; i<len; i++) {
  349. this.components[i].move(x, y);
  350. }
  351. },
  352. /**
  353. * APIMethod: rotate
  354. * Rotate a geometry around some origin
  355. *
  356. * Parameters:
  357. * angle - {Float} Rotation angle in degrees (measured counterclockwise
  358. * from the positive x-axis)
  359. * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
  360. */
  361. rotate: function(angle, origin) {
  362. for(var i=0, len=this.components.length; i<len; ++i) {
  363. this.components[i].rotate(angle, origin);
  364. }
  365. },
  366. /**
  367. * APIMethod: resize
  368. * Resize a geometry relative to some origin. Use this method to apply
  369. * a uniform scaling to a geometry.
  370. *
  371. * Parameters:
  372. * scale - {Float} Factor by which to scale the geometry. A scale of 2
  373. * doubles the size of the geometry in each dimension
  374. * (lines, for example, will be twice as long, and polygons
  375. * will have four times the area).
  376. * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
  377. * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
  378. *
  379. * Returns:
  380. * {OpenLayers.Geometry} - The current geometry.
  381. */
  382. resize: function(scale, origin, ratio) {
  383. for(var i=0; i<this.components.length; ++i) {
  384. this.components[i].resize(scale, origin, ratio);
  385. }
  386. return this;
  387. },
  388. /**
  389. * APIMethod: distanceTo
  390. * Calculate the closest distance between two geometries (on the x-y plane).
  391. *
  392. * Parameters:
  393. * geometry - {<OpenLayers.Geometry>} The target geometry.
  394. * options - {Object} Optional properties for configuring the distance
  395. * calculation.
  396. *
  397. * Valid options:
  398. * details - {Boolean} Return details from the distance calculation.
  399. * Default is false.
  400. * edge - {Boolean} Calculate the distance from this geometry to the
  401. * nearest edge of the target geometry. Default is true. If true,
  402. * calling distanceTo from a geometry that is wholly contained within
  403. * the target will result in a non-zero distance. If false, whenever
  404. * geometries intersect, calling distanceTo will return 0. If false,
  405. * details cannot be returned.
  406. *
  407. * Returns:
  408. * {Number | Object} The distance between this geometry and the target.
  409. * If details is true, the return will be an object with distance,
  410. * x0, y0, x1, and y1 properties. The x0 and y0 properties represent
  411. * the coordinates of the closest point on this geometry. The x1 and y1
  412. * properties represent the coordinates of the closest point on the
  413. * target geometry.
  414. */
  415. distanceTo: function(geometry, options) {
  416. var edge = !(options && options.edge === false);
  417. var details = edge && options && options.details;
  418. var result, best, distance;
  419. var min = Number.POSITIVE_INFINITY;
  420. for(var i=0, len=this.components.length; i<len; ++i) {
  421. result = this.components[i].distanceTo(geometry, options);
  422. distance = details ? result.distance : result;
  423. if(distance < min) {
  424. min = distance;
  425. best = result;
  426. if(min == 0) {
  427. break;
  428. }
  429. }
  430. }
  431. return best;
  432. },
  433. /**
  434. * APIMethod: equals
  435. * Determine whether another geometry is equivalent to this one. Geometries
  436. * are considered equivalent if all components have the same coordinates.
  437. *
  438. * Parameters:
  439. * geom - {<OpenLayers.Geometry>} The geometry to test.
  440. *
  441. * Returns:
  442. * {Boolean} The supplied geometry is equivalent to this geometry.
  443. */
  444. equals: function(geometry) {
  445. var equivalent = true;
  446. if(!geometry || !geometry.CLASS_NAME ||
  447. (this.CLASS_NAME != geometry.CLASS_NAME)) {
  448. equivalent = false;
  449. } else if(!(OpenLayers.Util.isArray(geometry.components)) ||
  450. (geometry.components.length != this.components.length)) {
  451. equivalent = false;
  452. } else {
  453. for(var i=0, len=this.components.length; i<len; ++i) {
  454. if(!this.components[i].equals(geometry.components[i])) {
  455. equivalent = false;
  456. break;
  457. }
  458. }
  459. }
  460. return equivalent;
  461. },
  462. /**
  463. * APIMethod: transform
  464. * Reproject the components geometry from source to dest.
  465. *
  466. * Parameters:
  467. * source - {<OpenLayers.Projection>}
  468. * dest - {<OpenLayers.Projection>}
  469. *
  470. * Returns:
  471. * {<OpenLayers.Geometry>}
  472. */
  473. transform: function(source, dest) {
  474. if (source && dest) {
  475. for (var i=0, len=this.components.length; i<len; i++) {
  476. var component = this.components[i];
  477. component.transform(source, dest);
  478. }
  479. this.bounds = null;
  480. }
  481. return this;
  482. },
  483. /**
  484. * APIMethod: intersects
  485. * Determine if the input geometry intersects this one.
  486. *
  487. * Parameters:
  488. * geometry - {<OpenLayers.Geometry>} Any type of geometry.
  489. *
  490. * Returns:
  491. * {Boolean} The input geometry intersects this one.
  492. */
  493. intersects: function(geometry) {
  494. var intersect = false;
  495. for(var i=0, len=this.components.length; i<len; ++ i) {
  496. intersect = geometry.intersects(this.components[i]);
  497. if(intersect) {
  498. break;
  499. }
  500. }
  501. return intersect;
  502. },
  503. /**
  504. * APIMethod: getVertices
  505. * Return a list of all points in this geometry.
  506. *
  507. * Parameters:
  508. * nodes - {Boolean} For lines, only return vertices that are
  509. * endpoints. If false, for lines, only vertices that are not
  510. * endpoints will be returned. If not provided, all vertices will
  511. * be returned.
  512. *
  513. * Returns:
  514. * {Array} A list of all vertices in the geometry.
  515. */
  516. getVertices: function(nodes) {
  517. var vertices = [];
  518. for(var i=0, len=this.components.length; i<len; ++i) {
  519. Array.prototype.push.apply(
  520. vertices, this.components[i].getVertices(nodes)
  521. );
  522. }
  523. return vertices;
  524. },
  525. CLASS_NAME: "OpenLayers.Geometry.Collection"
  526. });