123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- <?php
- /**
- * Collection: Abstract class for compound geometries
- *
- * A geometry is a collection if it is made up of other
- * component geometries. Therefore everything but a Point
- * is a Collection. For example a LingString is a collection
- * of Points. A Polygon is a collection of LineStrings etc.
- */
- abstract class Collection extends Geometry
- {
- public $components = array();
- /**
- * Constructor: Checks and sets component geometries
- *
- * @param array $components array of geometries
- */
- public function __construct($components = array()) {
- if (!is_array($components)) {
- throw new Exception("Component geometries must be passed as an array");
- }
- foreach ($components as $component) {
- if ($component instanceof Geometry) {
- $this->components[] = $component;
- }
- else {
- throw new Exception("Cannot create a collection with non-geometries");
- }
- }
- }
- /**
- * Returns Collection component geometries
- *
- * @return array
- */
- public function getComponents() {
- return $this->components;
- }
- public function centroid() {
- if ($this->isEmpty()) return NULL;
- if ($this->geos()) {
- $geos_centroid = $this->geos()->centroid();
- if ($geos_centroid->typeName() == 'Point') {
- return geoPHP::geosToGeometry($this->geos()->centroid());
- }
- }
- // As a rough estimate, we say that the centroid of a colletion is the centroid of it's envelope
- // @@TODO: Make this the centroid of the convexHull
- // Note: Outside of polygons, geometryCollections and the trivial case of points, there is no standard on what a "centroid" is
- $centroid = $this->envelope()->centroid();
- return $centroid;
- }
- public function getBBox() {
- if ($this->isEmpty()) return NULL;
- if ($this->geos()) {
- $envelope = $this->geos()->envelope();
- if ($envelope->typeName() == 'Point') {
- return geoPHP::geosToGeometry($envelope)->getBBOX();
- }
- $geos_ring = $envelope->exteriorRing();
- return array(
- 'maxy' => $geos_ring->pointN(3)->getY(),
- 'miny' => $geos_ring->pointN(1)->getY(),
- 'maxx' => $geos_ring->pointN(1)->getX(),
- 'minx' => $geos_ring->pointN(3)->getX(),
- );
- }
- // Go through each component and get the max and min x and y
- $i = 0;
- foreach ($this->components as $component) {
- $component_bbox = $component->getBBox();
- // On the first run through, set the bbox to the component bbox
- if ($i == 0) {
- $maxx = $component_bbox['maxx'];
- $maxy = $component_bbox['maxy'];
- $minx = $component_bbox['minx'];
- $miny = $component_bbox['miny'];
- }
- // Do a check and replace on each boundary, slowly growing the bbox
- $maxx = $component_bbox['maxx'] > $maxx ? $component_bbox['maxx'] : $maxx;
- $maxy = $component_bbox['maxy'] > $maxy ? $component_bbox['maxy'] : $maxy;
- $minx = $component_bbox['minx'] < $minx ? $component_bbox['minx'] : $minx;
- $miny = $component_bbox['miny'] < $miny ? $component_bbox['miny'] : $miny;
- $i++;
- }
- return array(
- 'maxy' => $maxy,
- 'miny' => $miny,
- 'maxx' => $maxx,
- 'minx' => $minx,
- );
- }
- public function asArray() {
- $array = array();
- foreach ($this->components as $component) {
- $array[] = $component->asArray();
- }
- return $array;
- }
- public function area() {
- if ($this->geos()) {
- return $this->geos()->area();
- }
- $area = 0;
- foreach ($this->components as $component) {
- $area += $component->area();
- }
- return $area;
- }
- // By default, the boundary of a collection is the boundary of it's components
- public function boundary() {
- if ($this->isEmpty()) return new LineString();
- if ($this->geos()) {
- return $this->geos()->boundary();
- }
- $components_boundaries = array();
- foreach ($this->components as $component) {
- $components_boundaries[] = $component->boundary();
- }
- return geoPHP::geometryReduce($components_boundaries);
- }
- public function numGeometries() {
- return count($this->components);
- }
- // Note that the standard is 1 based indexing
- public function geometryN($n) {
- $n = intval($n);
- if (array_key_exists($n-1, $this->components)) {
- return $this->components[$n-1];
- }
- else {
- return NULL;
- }
- }
- public function length() {
- $length = 0;
- foreach ($this->components as $delta => $component) {
- $length += $component->length();
- }
- return $length;
- }
- public function greatCircleLength($radius = 6378137) {
- $length = 0;
- foreach ($this->components as $component) {
- $length += $component->greatCircleLength($radius);
- }
- return $length;
- }
- public function haversineLength() {
- $length = 0;
- foreach ($this->components as $component) {
- $length += $component->haversineLength();
- }
- return $length;
- }
- public function dimension() {
- $dimension = 0;
- foreach ($this->components as $component) {
- if ($component->dimension() > $dimension) {
- $dimension = $component->dimension();
- }
- }
- return $dimension;
- }
- // A collection is empty if it has no components OR all it's components are empty
- public function isEmpty() {
- if (!count($this->components)) {
- return TRUE;
- }
- else {
- foreach ($this->components as $component) {
- if (!$component->isEmpty()) return FALSE;
- }
- return TRUE;
- }
- }
- public function numPoints() {
- $num = 0;
- foreach ($this->components as $component) {
- $num += $component->numPoints();
- }
- return $num;
- }
- public function getPoints() {
- $points = array();
- foreach ($this->components as $component) {
- $points = array_merge($points, $component->getPoints());
- }
- return $points;
- }
- public function equals($geometry) {
- if ($this->geos()) {
- return $this->geos()->equals($geometry->geos());
- }
- // To test for equality we check to make sure that there is a matching point
- // in the other geometry for every point in this geometry.
- // This is slightly more strict than the standard, which
- // uses Within(A,B) = true and Within(B,A) = true
- // @@TODO: Eventually we could fix this by using some sort of simplification
- // method that strips redundant vertices (that are all in a row)
- $this_points = $this->getPoints();
- $other_points = $geometry->getPoints();
- // First do a check to make sure they have the same number of vertices
- if (count($this_points) != count($other_points)) {
- return FALSE;
- }
- foreach ($this_points as $point) {
- $found_match = FALSE;
- foreach ($other_points as $key => $test_point) {
- if ($point->equals($test_point)) {
- $found_match = TRUE;
- unset($other_points[$key]);
- break;
- }
- }
- if (!$found_match) {
- return FALSE;
- }
- }
- // All points match, return TRUE
- return TRUE;
- }
- public function isSimple() {
- if ($this->geos()) {
- return $this->geos()->isSimple();
- }
- // A collection is simple if all it's components are simple
- foreach ($this->components as $component) {
- if (!$component->isSimple()) return FALSE;
- }
- return TRUE;
- }
- public function explode() {
- $parts = array();
- foreach ($this->components as $component) {
- $parts = array_merge($parts, $component->explode());
- }
- return $parts;
- }
- // Not valid for this geometry type
- // --------------------------------
- public function x() { return NULL; }
- public function y() { return NULL; }
- public function startPoint() { return NULL; }
- public function endPoint() { return NULL; }
- public function isRing() { return NULL; }
- public function isClosed() { return NULL; }
- public function pointN($n) { return NULL; }
- public function exteriorRing() { return NULL; }
- public function numInteriorRings() { return NULL; }
- public function interiorRingN($n) { return NULL; }
- public function pointOnSurface() { return NULL; }
- }
|