1<?php
2
3namespace geoPHP\Geometry;
4
5use geoPHP\geoPHP;
6
7/**
8 * GeometryCollection: A heterogeneous collection of geometries
9 */
10class GeometryCollection extends MultiGeometry
11{
12
13    /**
14     * @param Geometry[] $components Array of geometries. Components of GeometryCollection can be
15     *     any of valid Geometry types, including empty geometry
16     *
17     * @throws \Exception
18     */
19    public function __construct($components = [])
20    {
21        parent::__construct($components, true);
22    }
23
24    public function geometryType()
25    {
26        return Geometry::GEOMETRY_COLLECTION;
27    }
28
29    /**
30     * @return int Returns the highest spatial dimension of components
31     */
32    public function dimension()
33    {
34        $dimension = 0;
35        foreach ($this->getComponents() as $component) {
36            if ($component->dimension() > $dimension) {
37                $dimension = $component->dimension();
38            }
39        }
40        return $dimension;
41    }
42
43    /**
44     * Not valid for this geometry type
45     * @return null
46     */
47    public function isSimple()
48    {
49        return null;
50    }
51
52    /**
53     * In a GeometryCollection, the centroid is equal to the centroid of
54     * the set of component Geometries of highest dimension
55     * (since the lower-dimension geometries contribute zero "weight" to the centroid).
56     *
57     * @return Point
58     * @throws \Exception
59     */
60    public function centroid()
61    {
62        if ($this->isEmpty()) {
63            return new Point();
64        }
65
66        if ($this->getGeos()) {
67            // @codeCoverageIgnoreStart
68            /** @noinspection PhpUndefinedMethodInspection */
69            return geoPHP::geosToGeometry($this->getGeos()->centroid());
70            // @codeCoverageIgnoreEnd
71        }
72
73        $geometries = $this->explodeGeometries();
74
75        $highestDimension = 0;
76        foreach ($geometries as $geometry) {
77            if ($geometry->dimension() > $highestDimension) {
78                $highestDimension = $geometry->dimension();
79            }
80            if ($highestDimension === 2) {
81                break;
82            }
83        }
84
85        $highestDimensionGeometries = [];
86        foreach ($geometries as $geometry) {
87            if ($geometry->dimension() === $highestDimension) {
88                $highestDimensionGeometries[] = $geometry;
89            }
90        }
91
92        $reducedGeometry = geoPHP::geometryReduce($highestDimensionGeometries);
93        if ($reducedGeometry->geometryType() === Geometry::GEOMETRY_COLLECTION) {
94            throw new \Exception('Internal error: GeometryCollection->centroid() calculation failed.');
95        }
96        return $reducedGeometry->centroid();
97    }
98
99    /**
100     * Returns every sub-geometry as a multidimensional array
101     *
102     * Because geometryCollections are heterogeneous we need to specify which type of geometries they contain.
103     * We need to do this because, for example, there would be no way to tell the difference between a
104     * MultiPoint or a LineString, since they share the same structure (collection
105     * of points). So we need to call out the type explicitly.
106     *
107     * @return array
108     */
109    public function asArray()
110    {
111        $array = [];
112        foreach ($this->getComponents() as $component) {
113            $array[] = [
114                    'type'       => $component->geometryType(),
115                    'components' => $component->asArray(),
116            ];
117        }
118        return $array;
119    }
120
121    /**
122     * @return Geometry[]|Collection[]
123     */
124    public function explodeGeometries()
125    {
126        $geometries = [];
127        foreach ($this->components as $component) {
128            if ($component->geometryType() === Geometry::GEOMETRY_COLLECTION) {
129                /** @var GeometryCollection $component */
130                $geometries = array_merge($geometries, $component->explodeGeometries());
131            } else {
132                $geometries[] = $component;
133            }
134        }
135        return $geometries;
136    }
137
138    // Not valid for this geometry
139    public function boundary()
140    {
141        return null;
142    }
143}
144