Completed
Push — master ( 8f9c53...27058f )
by Stephen
02:24 queued 01:11
created

GeometryProxy::getSquareMeters()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 14
c 0
b 0
f 0
ccs 7
cts 7
cp 1
rs 9.7998
cc 3
nc 3
nop 0
crap 3
1
<?php
2
3
namespace Spinen\Geometry\Support;
4
5
use Geometry;
6
use Illuminate\Support\Str;
7
use InvalidArgumentException;
8
use RuntimeException;
9
10
/**
11
 * Class GeometryProxy
12
 *
13
 * Proxy class to "wrap" the geoPHP classes into class that we can add functionality.
14
 *
15
 * @package Spinen\Geometry
16
 *
17
 * @method mixed toEwkb() Returns the geometry in EWKB format.
18
 * @method mixed toEwkt() Returns the geometry in EWKT format.
19
 * @method mixed toGeoHash() Returns the geometry in GeoHash format.
20
 * @method mixed toGeoJson() Returns the geometry in GeoJSON format.
21
 * @method mixed toGeoRss() Returns the geometry in GeoRSS format.
22
 * @method mixed toGoogleGeocode() Returns the geometry in GoogleGeocode format.
23
 * @method mixed toGpx() Returns the geometry in GPX format.
24
 * @method mixed toJson() Returns the geometry in GeoJSON format.
25
 * @method mixed toKml() Returns the geometry in KML format.
26
 * @method mixed toWkb() Returns the geometry in WKB format.
27
 * @method mixed toWkt() Returns the geometry in WKT format.
28
 * @property float acres The acres with in +/-1%.
29
 * @property array coordinates The points that define the shape.
30
 * @property float square_meters The square meters with in +/-1%.
31
 */
32
class GeometryProxy
33
{
34
    /**
35
     * Cache the area to not have to loop through the calculations each time that it is needed.
36
     *
37
     * @var float
38
     */
39
    protected $cached_area = null;
40
41
    /**
42
     * The geometry to proxy.
43
     *
44
     * @var Geometry
45
     */
46
    protected $geometry;
47
48
    /**
49
     * Cached array version of the geometry.
50
     *
51
     * @var array | null
52
     */
53
    protected $geometry_array = null;
54
55
    /**
56
     * Instance of TypeMapper.
57
     *
58
     * @var TypeMapper
59
     */
60
    protected $mapper;
61
62
    /**
63
     * Polygon constructor.
64
     *
65
     * @param mixed      $geometry
66
     * @param TypeMapper $mapper
67
     */
68 27
    public function __construct($geometry, TypeMapper $mapper)
69
    {
70 27
        $this->geometry = $geometry;
71 27
        $this->mapper = $mapper;
72 27
    }
73
74
    /**
75
     * Magic method to allow methods that are not specifically defined.
76
     *
77
     * This is where we look to see if the class that we are proxing has the method that is being called, and if it
78
     * does, then pass the call to the class under proxy.  If a method is defined in our class, then it gets called
79
     * first, so you can "extend" the classes by defining methods that overwrite the "parent" there.
80
     *
81
     * @param string $name
82
     * @param array  $arguments
83
     *
84
     * @return mixed
85
     * @throws InvalidArgumentException
86
     */
87 10
    public function __call($name, $arguments)
88
    {
89
        // Sugar to make to<Format>() work
90 10 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...
91 7
            return $this->geometry->out($this->mapper->map($parts[1]));
92
        }
93
94
        // Call the method on the class being proxied
95 3
        if (method_exists($this->geometry, $name)) {
96 2
            return call_user_func_array(
97 2
                [$this->geometry, $name], array_map([$this, 'exposeRawIfAvailable'], $arguments)
98
            );
99
        }
100
101 1
        throw new RuntimeException(sprintf("Call to undefined method %s::%s().", __CLASS__, $name));
102
    }
103
104
    /**
105
     * @param string $name
106
     *
107
     * @return mixed
108
     */
109 4
    public function __get($name)
110
    {
111
        // Properties on the geometry
112 4
        if (isset($this->toArray()[$name])) {
113 3
            return $this->toArray()[$name];
114
        }
115
116
        // Shortcut to the getters
117 3
        if (method_exists($this, 'get' . Str::studly($name))) {
118 2
            return $this->{'get' . Str::studly($name)}();
119
        }
120
121 1
        throw new RuntimeException(sprintf("Undefined property: %s", $name));
122
    }
123
124
    /**
125
     * If using the object as a string, just return the json.
126
     *
127
     * @return string
128
     */
129 1
    public function __toString()
130
    {
131 1
        return $this->toJson();
132
    }
133
134
    /**
135
     * Figure out what index to use in the ringArea calculation
136
     *
137
     * @param int $index
138
     * @param int $length
139
     *
140
     * @return array
141
     */
142 1
    private function determineCoordinateIndices($index, $length)
143
    {
144
        // i = N-2
145 1 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...
146 1
            return [$length - 2, $length - 1, 0];
147
        }
148
149
        // i = N-1
150 1 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...
151 1
            return [$length - 1, 0, 1];
152
        }
153
154
        // i = 0 to N-3
155 1
        return [$index, $index + 1, $index + 2];
156
    }
157
158
    /**
159
     * If the object passed in has a getRawGeometry, call it
160
     *
161
     * @param $argument
162
     *
163
     * @return mixed
164
     */
165 1
    protected function exposeRawIfAvailable($argument)
166
    {
167 1
        return (method_exists($argument, 'getRawGeometry'))
168 1
            ? $argument->getRawGeometry()
169 1
            : $argument;
170
    }
171
172
    /**
173
     * Calculate the acres
174
     *
175
     * @return float
176
     */
177 2
    public function getAcres()
178
    {
179 2
        return $this->square_meters * 0.000247105381;
180
    }
181
182
    /**
183
     * Expose the underlying Geometry object
184
     *
185
     * @return Geometry
186
     */
187 2
    public function getRawGeometry()
188
    {
189 2
        return $this->geometry;
190
    }
191
192
    /**
193
     * Calculate the square meters
194
     *
195
     * @return float
196
     */
197 2
    public function getSquareMeters()
198
    {
199 2
        if (!is_null($this->cached_area)) {
200 1
            return $this->cached_area;
201
        }
202
203 2
        $this->cached_area = 0.0;
204
205 2
        foreach ($this->coordinates as $coordinate) {
206 2
            $this->cached_area += $this->ringArea($coordinate);
207
        }
208
209 2
        return $this->cached_area;
210
    }
211
212
    /**
213
     * Convert degrees to radians
214
     *
215
     * I know that there is a built in function, but I read that it was very slow & to use this.
216
     *
217
     * @param $degrees
218
     *
219
     * @return float
220
     */
221 1
    private function radians($degrees)
222
    {
223 1
        return $degrees * M_PI / 180;
224
    }
225
226
    /**
227
     * Estimate the area of a ring
228
     *
229
     * Calculate the approximate area of the polygon were it projected onto
230
     *     the earth.  Note that this area will be positive if ring is oriented
231
     *     clockwise, otherwise it will be negative.
232
     *
233
     * Reference:
234
     * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
235
     *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
236
     *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
237
     *
238
     * @return float
239
     * @see https://github.com/mapbox/geojson-area/blob/master/index.js#L55
240
     */
241 2
    public function ringArea($coordinates)
242
    {
243 2
        $area = 0.0;
244
245 2
        $length = count($coordinates);
246
247 2
        if ($length <= 2) {
248 1
            return $area;
249
        }
250
251 1
        for ($i = 0; $i < $length; $i ++) {
252 1
            list($lower_index, $middle_index, $upper_index) = $this->determineCoordinateIndices($i, $length);
253
254 1
            $point1 = $coordinates[$lower_index];
255 1
            $point2 = $coordinates[$middle_index];
256 1
            $point3 = $coordinates[$upper_index];
257
258 1
            $area += ($this->radians($point3[0]) - $this->radians($point1[0])) * sin($this->radians($point2[1]));
259
        }
260
261 1
        return $area * 6378137 * 6378137 / 2;
262
    }
263
264
    /**
265
     * Build array of the object
266
     *
267
     * Cache the result, so that we don't decode it on every call.
268
     *
269
     * @return array
270
     */
271 5
    public function toArray()
272
    {
273 5
        if (is_null($this->geometry_array)) {
274 5
            $this->geometry_array = (array)json_decode($this->toJson(), true);
275
        }
276
277 5
        return $this->geometry_array;
278
    }
279
}
280