Collection.class.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <?php
  2. /**
  3. * Collection: Abstract class for compound geometries
  4. *
  5. * A geometry is a collection if it is made up of other
  6. * component geometries. Therefore everything but a Point
  7. * is a Collection. For example a LingString is a collection
  8. * of Points. A Polygon is a collection of LineStrings etc.
  9. */
  10. abstract class Collection extends Geometry
  11. {
  12. public $components = array();
  13. /**
  14. * Constructor: Checks and sets component geometries
  15. *
  16. * @param array $components array of geometries
  17. */
  18. public function __construct($components = array()) {
  19. if (!is_array($components)) {
  20. throw new Exception("Component geometries must be passed as an array");
  21. }
  22. foreach ($components as $component) {
  23. if ($component instanceof Geometry) {
  24. $this->components[] = $component;
  25. }
  26. else {
  27. throw new Exception("Cannot create a collection with non-geometries");
  28. }
  29. }
  30. }
  31. /**
  32. * Returns Collection component geometries
  33. *
  34. * @return array
  35. */
  36. public function getComponents() {
  37. return $this->components;
  38. }
  39. public function centroid() {
  40. if ($this->isEmpty()) return NULL;
  41. if ($this->geos()) {
  42. $geos_centroid = $this->geos()->centroid();
  43. if ($geos_centroid->typeName() == 'Point') {
  44. return geoPHP::geosToGeometry($this->geos()->centroid());
  45. }
  46. }
  47. // As a rough estimate, we say that the centroid of a colletion is the centroid of it's envelope
  48. // @@TODO: Make this the centroid of the convexHull
  49. // Note: Outside of polygons, geometryCollections and the trivial case of points, there is no standard on what a "centroid" is
  50. $centroid = $this->envelope()->centroid();
  51. return $centroid;
  52. }
  53. public function getBBox() {
  54. if ($this->isEmpty()) return NULL;
  55. if ($this->geos()) {
  56. $envelope = $this->geos()->envelope();
  57. if ($envelope->typeName() == 'Point') {
  58. return geoPHP::geosToGeometry($envelope)->getBBOX();
  59. }
  60. $geos_ring = $envelope->exteriorRing();
  61. return array(
  62. 'maxy' => $geos_ring->pointN(3)->getY(),
  63. 'miny' => $geos_ring->pointN(1)->getY(),
  64. 'maxx' => $geos_ring->pointN(1)->getX(),
  65. 'minx' => $geos_ring->pointN(3)->getX(),
  66. );
  67. }
  68. // Go through each component and get the max and min x and y
  69. $i = 0;
  70. foreach ($this->components as $component) {
  71. $component_bbox = $component->getBBox();
  72. // On the first run through, set the bbox to the component bbox
  73. if ($i == 0) {
  74. $maxx = $component_bbox['maxx'];
  75. $maxy = $component_bbox['maxy'];
  76. $minx = $component_bbox['minx'];
  77. $miny = $component_bbox['miny'];
  78. }
  79. // Do a check and replace on each boundary, slowly growing the bbox
  80. $maxx = $component_bbox['maxx'] > $maxx ? $component_bbox['maxx'] : $maxx;
  81. $maxy = $component_bbox['maxy'] > $maxy ? $component_bbox['maxy'] : $maxy;
  82. $minx = $component_bbox['minx'] < $minx ? $component_bbox['minx'] : $minx;
  83. $miny = $component_bbox['miny'] < $miny ? $component_bbox['miny'] : $miny;
  84. $i++;
  85. }
  86. return array(
  87. 'maxy' => $maxy,
  88. 'miny' => $miny,
  89. 'maxx' => $maxx,
  90. 'minx' => $minx,
  91. );
  92. }
  93. public function asArray() {
  94. $array = array();
  95. foreach ($this->components as $component) {
  96. $array[] = $component->asArray();
  97. }
  98. return $array;
  99. }
  100. public function area() {
  101. if ($this->geos()) {
  102. return $this->geos()->area();
  103. }
  104. $area = 0;
  105. foreach ($this->components as $component) {
  106. $area += $component->area();
  107. }
  108. return $area;
  109. }
  110. // By default, the boundary of a collection is the boundary of it's components
  111. public function boundary() {
  112. if ($this->isEmpty()) return new LineString();
  113. if ($this->geos()) {
  114. return $this->geos()->boundary();
  115. }
  116. $components_boundaries = array();
  117. foreach ($this->components as $component) {
  118. $components_boundaries[] = $component->boundary();
  119. }
  120. return geoPHP::geometryReduce($components_boundaries);
  121. }
  122. public function numGeometries() {
  123. return count($this->components);
  124. }
  125. // Note that the standard is 1 based indexing
  126. public function geometryN($n) {
  127. $n = intval($n);
  128. if (array_key_exists($n-1, $this->components)) {
  129. return $this->components[$n-1];
  130. }
  131. else {
  132. return NULL;
  133. }
  134. }
  135. public function length() {
  136. $length = 0;
  137. foreach ($this->components as $delta => $component) {
  138. $length += $component->length();
  139. }
  140. return $length;
  141. }
  142. public function greatCircleLength($radius = 6378137) {
  143. $length = 0;
  144. foreach ($this->components as $component) {
  145. $length += $component->greatCircleLength($radius);
  146. }
  147. return $length;
  148. }
  149. public function haversineLength() {
  150. $length = 0;
  151. foreach ($this->components as $component) {
  152. $length += $component->haversineLength();
  153. }
  154. return $length;
  155. }
  156. public function dimension() {
  157. $dimension = 0;
  158. foreach ($this->components as $component) {
  159. if ($component->dimension() > $dimension) {
  160. $dimension = $component->dimension();
  161. }
  162. }
  163. return $dimension;
  164. }
  165. // A collection is empty if it has no components OR all it's components are empty
  166. public function isEmpty() {
  167. if (!count($this->components)) {
  168. return TRUE;
  169. }
  170. else {
  171. foreach ($this->components as $component) {
  172. if (!$component->isEmpty()) return FALSE;
  173. }
  174. return TRUE;
  175. }
  176. }
  177. public function numPoints() {
  178. $num = 0;
  179. foreach ($this->components as $component) {
  180. $num += $component->numPoints();
  181. }
  182. return $num;
  183. }
  184. public function getPoints() {
  185. $points = array();
  186. foreach ($this->components as $component) {
  187. $points = array_merge($points, $component->getPoints());
  188. }
  189. return $points;
  190. }
  191. public function equals($geometry) {
  192. if ($this->geos()) {
  193. return $this->geos()->equals($geometry->geos());
  194. }
  195. // To test for equality we check to make sure that there is a matching point
  196. // in the other geometry for every point in this geometry.
  197. // This is slightly more strict than the standard, which
  198. // uses Within(A,B) = true and Within(B,A) = true
  199. // @@TODO: Eventually we could fix this by using some sort of simplification
  200. // method that strips redundant vertices (that are all in a row)
  201. $this_points = $this->getPoints();
  202. $other_points = $geometry->getPoints();
  203. // First do a check to make sure they have the same number of vertices
  204. if (count($this_points) != count($other_points)) {
  205. return FALSE;
  206. }
  207. foreach ($this_points as $point) {
  208. $found_match = FALSE;
  209. foreach ($other_points as $key => $test_point) {
  210. if ($point->equals($test_point)) {
  211. $found_match = TRUE;
  212. unset($other_points[$key]);
  213. break;
  214. }
  215. }
  216. if (!$found_match) {
  217. return FALSE;
  218. }
  219. }
  220. // All points match, return TRUE
  221. return TRUE;
  222. }
  223. public function isSimple() {
  224. if ($this->geos()) {
  225. return $this->geos()->isSimple();
  226. }
  227. // A collection is simple if all it's components are simple
  228. foreach ($this->components as $component) {
  229. if (!$component->isSimple()) return FALSE;
  230. }
  231. return TRUE;
  232. }
  233. public function explode() {
  234. $parts = array();
  235. foreach ($this->components as $component) {
  236. $parts = array_merge($parts, $component->explode());
  237. }
  238. return $parts;
  239. }
  240. // Not valid for this geometry type
  241. // --------------------------------
  242. public function x() { return NULL; }
  243. public function y() { return NULL; }
  244. public function startPoint() { return NULL; }
  245. public function endPoint() { return NULL; }
  246. public function isRing() { return NULL; }
  247. public function isClosed() { return NULL; }
  248. public function pointN($n) { return NULL; }
  249. public function exteriorRing() { return NULL; }
  250. public function numInteriorRings() { return NULL; }
  251. public function interiorRingN($n) { return NULL; }
  252. public function pointOnSurface() { return NULL; }
  253. }