Completed
Pull Request — develop (#16)
by Jimmy
02:53 queued 01:33
created

GeometryProxy::getRawGeometry()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Spinen\Geometry\Support;
4
5
use Geometry;
6
use InvalidArgumentException;
7
use RuntimeException;
8
9
/**
10
 * Class GeometryProxy
11
 *
12
 * Proxy class to "wrap" the geoPHP classes into class that we can add functionality.
13
 *
14
 * @package Spinen\Geometry
15
 *
16
 * @method mixed toEwkb() Returns the geometry in EWKB format.
17
 * @method mixed toEwkt() Returns the geometry in EWKT format.
18
 * @method mixed toGeoHash() Returns the geometry in GeoHash format.
19
 * @method mixed toGeoJson() Returns the geometry in GeoJSON format.
20
 * @method mixed toGeoRss() Returns the geometry in GeoRSS format.
21
 * @method mixed toGoogleGeocode() Returns the geometry in GoogleGeocode format.
22
 * @method mixed toGpx() Returns the geometry in GPX format.
23
 * @method mixed toJson() Returns the geometry in GeoJSON format.
24
 * @method mixed toKml() Returns the geometry in KML format.
25
 * @method mixed toWkb() Returns the geometry in WKB format.
26
 * @method mixed toWkt() Returns the geometry in WKT format.
27
 * @property float acres The acres with in +/-1%.
28
 * @property array coordinates The points that define the shape.
29
 * @property float square_meters The square meters with in +/-1%.
30
 */
31
class GeometryProxy
32
{
33
    /**
34
     * Cache the area to not have to loop through the calculations each time that it is needed.
35
     *
36
     * @var float
37
     */
38
    protected $cached_area = null;
39
40
    /**
41
     * The geometry to proxy.
42
     *
43
     * @var Geometry
44
     */
45
    protected $geometry;
46
47
    /**
48
     * Cached array version of the geometry.
49
     *
50
     * @var array | null
51
     */
52
    protected $geometry_array = null;
53
54
    /**
55
     * Instance of TypeMapper.
56
     *
57
     * @var TypeMapper
58
     */
59
    protected $mapper;
60
61
    /**
62
     * Polygon constructor.
63
     *
64
     * @param mixed      $geometry
65
     * @param TypeMapper $mapper
66
     */
67
    public function __construct($geometry, TypeMapper $mapper)
68
    {
69
        $this->geometry = $geometry;
70
        $this->mapper = $mapper;
71
    }
72
73
    /**
74
     * Magic method to allow methods that are not specifically defined.
75
     *
76
     * This is where we look to see if the class that we are proxing has the method that is being called, and if it
77
     * does, then pass the call to the class under proxy.  If a method is defined in our class, then it gets called
78
     * first, so you can "extend" the classes by defining methods that overwrite the "parent" there.
79
     *
80
     * @param string $name
81
     * @param array  $arguments
82
     *
83
     * @return mixed
84
     * @throws InvalidArgumentException
85
     */
86
    public function __call($name, $arguments)
87
    {
88
        // Sugar to make to<Format>() work
89 View Code Duplication
        if (preg_match("/^to([A-Z][A-z]*)/u", $name, $parts) && 0 === count($arguments)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
90
            return $this->geometry->out($this->mapper->map($parts[1]));
91
        }
92
93
        // Call the method on the class being proxied
94
        if (method_exists($this->geometry, $name)) {
95
            return call_user_func_array(
96
                [$this->geometry, $name], array_map([$this, 'exposeRawIfAvailable'], $arguments)
97
            );
98
        }
99
100
        throw new RuntimeException(sprintf("Call to undefined method %s::%s().", __CLASS__, $name));
101
    }
102
103
    /**
104
     * @param string $name
105
     *
106
     * @return mixed
107
     */
108
    public function __get($name)
109
    {
110
        // Properties on the geometry
111
        if (isset($this->toArray()[$name])) {
112
            return $this->toArray()[$name];
113
        }
114
115
        // Shortcut to the getters
116
        if (method_exists($this, 'get' . studly_case($name))) {
117
            return $this->{'get' . studly_case($name)}();
118
        }
119
120
        throw new RuntimeException(sprintf("Undefined property: %s", $name));
121
    }
122
123
    /**
124
     * If using the object as a string, just return the json.
125
     *
126
     * @return string
127
     */
128
    public function __toString()
129
    {
130
        return $this->toJson();
131
    }
132
133
    /**
134
     * Figure out what index to use in the ringArea calculation
135
     *
136
     * @param int $index
137
     * @param int $length
138
     *
139
     * @return array
140
     */
141
    private function determineCoordinateIndices($index, $length)
142
    {
143
        // i = N-2
144 View Code Duplication
        if ($index === ($length - 2)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
145
            return [$length - 2, $length - 1, 0];
146
        }
147
148
        // i = N-1
149 View Code Duplication
        if ($index === ($length - 1)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
150
            return [$length - 1, 0, 1];
151
        }
152
153
        // i = 0 to N-3
154
        return [$index, $index + 1, $index + 2];
155
    }
156
157
    /**
158
     * If the object passed in has a getRawGeometry, call it
159
     *
160
     * @param $argument
161
     *
162
     * @return mixed
163
     */
164
    protected function exposeRawIfAvailable($argument)
165
    {
166
        if (method_exists($argument, 'getRawGeometry')) {
167
            return $argument->getRawGeometry();
168
        }
169
170
        return $argument;
171
    }
172
173
    /**
174
     * Calculate the acres
175
     *
176
     * @return float
177
     */
178
    public function getAcres()
179
    {
180
        return $this->square_meters * 0.000247105381;
181
    }
182
183
    /**
184
     * Expose the underlying Geometry object
185
     *
186
     * @return Geometry
187
     */
188
    public function getRawGeometry()
189
    {
190
        return $this->geometry;
191
    }
192
193
    /**
194
     * Calculate the square meters
195
     *
196
     * @return float
197
     */
198
    public function getSquareMeters()
199
    {
200
        if (!is_null($this->cached_area)) {
201
            return $this->cached_area;
202
        }
203
204
        $this->cached_area = 0.0;
205
206
        foreach ($this->coordinates as $coordinate) {
207
            $this->cached_area += $this->ringArea($coordinate);
208
        }
209
210
        return $this->cached_area;
211
    }
212
213
    /**
214
     * Convert degrees to radians
215
     *
216
     * I know that there is a built in function, but I read that it was very slow & to use this.
217
     *
218
     * @param $degrees
219
     *
220
     * @return float
221
     */
222
    private function radians($degrees)
223
    {
224
        return $degrees * M_PI / 180;
225
    }
226
227
    /**
228
     * Estimate the area of a ring
229
     *
230
     * Calculate the approximate area of the polygon were it projected onto
231
     *     the earth.  Note that this area will be positive if ring is oriented
232
     *     clockwise, otherwise it will be negative.
233
     *
234
     * Reference:
235
     * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
236
     *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
237
     *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
238
     *
239
     * @return float
240
     * @see https://github.com/mapbox/geojson-area/blob/master/index.js#L55
241
     */
242
    public function ringArea($coordinates)
243
    {
244
        $area = 0.0;
245
246
        $length = count($coordinates);
247
248
        if ($length <= 2) {
249
            return $area;
250
        }
251
252
        for ($i = 0; $i < $length; $i ++) {
253
            list($lower_index, $middle_index, $upper_index) = $this->determineCoordinateIndices($i, $length);
254
255
            $point1 = $coordinates[$lower_index];
256
            $point2 = $coordinates[$middle_index];
257
            $point3 = $coordinates[$upper_index];
258
259
            $area += ($this->radians($point3[0]) - $this->radians($point1[0])) * sin($this->radians($point2[1]));
260
        }
261
262
        return $area * 6378137 * 6378137 / 2;
263
    }
264
265
    /**
266
     * Build array of the object
267
     *
268
     * Cache the result, so that we don't decode it on every call.
269
     *
270
     * @return array
271
     */
272
    public function toArray()
273
    {
274
        if (is_null($this->geometry_array)) {
275
            $this->geometry_array = (array)json_decode($this->toJson(), true);
276
        }
277
278
        return $this->geometry_array;
279
    }
280
}
281