Completed
Push — master ( cfa155...9cc70e )
by Marcus
01:59
created

Polygon::getArea()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 1
Metric Value
dl 0
loc 32
rs 8.8571
c 1
b 1
f 1
cc 3
eloc 18
nc 3
nop 0
1
<?php
2
/**
3
 * Polygon Implementation
4
 *
5
 * PHP version 5
6
 *
7
 * @author    Paul Vidal <[email protected]>
8
 * @author    Marcus Jaschen <[email protected]>
9
 * @license   https://opensource.org/licenses/GPL-3.0 GPL
10
 * @link      https://github.com/mjaschen/phpgeo
11
 */
12
13
namespace Location;
14
15
use Location\Distance\DistanceInterface;
16
use Location\Formatter\Polygon\FormatterInterface;
17
18
/**
19
 * Polygon Implementation
20
 *
21
 * @author   Paul Vidal <[email protected]>
22
 * @author   Marcus Jaschen <[email protected]>
23
 * @license  https://opensource.org/licenses/GPL-3.0 GPL
24
 * @link     https://github.com/mjaschen/phpgeo
25
 */
26
class Polygon implements GeometryInterface
27
{
28
    /**
29
     * @var array
30
     */
31
    protected $points = [];
32
33
    /**
34
     * @param Coordinate $point
35
     */
36
    public function addPoint(Coordinate $point)
37
    {
38
        $this->points[] = $point;
39
    }
40
41
    /**
42
     * @return array
43
     */
44
    public function getPoints()
45
    {
46
        return $this->points;
47
    }
48
49
    /**
50
     * Return all polygon point's latitudes.
51
     *
52
     * @return float[]
53
     */
54
    public function getLats()
55
    {
56
        $lats = [];
57
58
        foreach ($this->points as $point) {
59
            $lats[] = $point->getLat();
60
        }
61
62
        return $lats;
63
    }
64
65
    /**
66
     * Return all polygon point's longitudes.
67
     *
68
     * @return float[]
69
     */
70
    public function getLngs()
71
    {
72
        $lngs = [];
73
74
        foreach ($this->points as $point) {
75
            $lngs[] = $point->getLng();
76
        }
77
78
        return $lngs;
79
    }
80
81
    /**
82
     * @return int
83
     */
84
    public function getNumberOfPoints()
85
    {
86
        return count($this->points);
87
    }
88
89
    /**
90
     * @param FormatterInterface $formatter
91
     *
92
     * @return mixed
93
     */
94
    public function format(FormatterInterface $formatter)
95
    {
96
        return $formatter->format($this);
97
    }
98
99
    /**
100
     * @return array
101
     */
102
    public function getSegments()
103
    {
104
        $segments = [];
105
106
        if (count($this->points) <= 1) {
107
            return $segments;
108
        }
109
110
        $previousPoint = reset($this->points);
111
112 View Code Duplication
        while ($point = next($this->points)) {
0 ignored issues
show
Duplication introduced by Paul Vidal
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...
113
            $segments[]    = new Line($previousPoint, $point);
114
            $previousPoint = $point;
115
        }
116
117
        // to close the polygon we have to add the final segment between
118
        // the last point and the first point
119
        $segments[] = new Line(end($this->points), reset($this->points));
120
121
        return $segments;
122
    }
123
124
    /**
125
     * Determine if given geometry is contained inside the polygon. This is
126
     * assumed to be true, if each point of the geometry is inside the polygon.
127
     *
128
     * Edge cases:
129
     *
130
     * - it's not detected when a line between two points is outside the polygon
131
     * - @see contains() for more restrictions
132
     *
133
     * @param GeometryInterface $geometry
134
     *
135
     * @return boolean
136
     */
137
    public function containsGeometry(GeometryInterface $geometry)
138
    {
139
        $geometryInPolygon = true;
140
141
        /** @var Coordinate $point */
142
        foreach ($geometry->getPoints() as $point) {
143
            $geometryInPolygon = $geometryInPolygon && $this->contains($point);
144
        }
145
146
        return $geometryInPolygon;
147
    }
148
149
    /**
150
     * Determine if given point is contained inside the polygon. Uses the PNPOLY
151
     * algorithm by W. Randolph Franklin. Therfore some edge cases may not give the
152
     * expected results, e. g. if the point resides on the polygon boundary.
153
     *
154
     * @see http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
155
     *
156
     * For special cases this calculation leads to wrong results:
157
     *
158
     * - if the polygons spans over the longitude boundaries at 180/-180 degrees
159
     *
160
     * @param Coordinate $point
161
     *
162
     * @return boolean
163
     */
164
    public function contains(Coordinate $point)
165
    {
166
        $numberOfPoints = $this->getNumberOfPoints();
167
        $polygonLats    = $this->getLats();
168
        $polygonLngs    = $this->getLngs();
169
170
        $polygonContainsPoint = false;
171
172
        for ($node = 0, $altNode = ($numberOfPoints - 1); $node < $numberOfPoints; $altNode = $node ++) {
173
            if (($polygonLngs[$node] > $point->getLng() != ($polygonLngs[$altNode] > $point->getLng()))
174
                && ($point->getLat() < ($polygonLats[$altNode] - $polygonLats[$node])
175
                                       * ($point->getLng() - $polygonLngs[$node])
176
                                       / ($polygonLngs[$altNode] - $polygonLngs[$node])
177
                                       + $polygonLats[$node]
178
                )
179
            ) {
180
                $polygonContainsPoint = ! $polygonContainsPoint;
181
            }
182
        }
183
184
        return $polygonContainsPoint;
185
    }
186
187
    /**
188
     * Calculates the polygon perimeter.
189
     *
190
     * @param DistanceInterface $calculator instance of distance calculation class
191
     *
192
     * @return float
193
     */
194 View Code Duplication
    public function getPerimeter(DistanceInterface $calculator)
0 ignored issues
show
Duplication introduced by Paul Vidal
This method seems to be duplicated in 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...
195
    {
196
        $perimeter = 0.0;
197
198
        if (count($this->points) < 2) {
199
            return $perimeter;
200
        }
201
202
        foreach ($this->getSegments() as $segment) {
203
            $perimeter += $segment->getLength($calculator);
204
        }
205
206
        return $perimeter;
207
    }
208
209
    /**
210
     * Calculates the polygon area.
211
     *
212
     * This algorithm gives inaccurate results as it ignores
213
     * ellipsoid parameters other than to arithmetic mean radius.
214
     * The error should be < 1 % for small areas.
215
     *
216
     * @return float
217
     */
218
    public function getArea()
219
    {
220
        $area = 0;
221
222
        if ($this->getNumberOfPoints() <= 2) {
223
            return $area;
224
        }
225
226
        $referencePoint = $this->points[0];
227
        $radius = $referencePoint->getEllipsoid()->getArithmeticMeanRadius();
228
        $segments = $this->getSegments();
229
230
        foreach ($segments as $segment) {
231
            /** @var Coordinate $point1 */
232
            $point1 = $segment->getPoint1();
233
            /** @var Coordinate $point2 */
234
            $point2 = $segment->getPoint2();
235
236
            $x1 = deg2rad($point1->getLng() - $referencePoint->getLng()) * cos(deg2rad($point1->getLat()));
237
            $y1 = deg2rad($point1->getLat() - $referencePoint->getLat());
238
239
            $x2 = deg2rad($point2->getLng() - $referencePoint->getLng()) * cos(deg2rad($point2->getLat()));
240
            $y2 = deg2rad($point2->getLat() - $referencePoint->getLat());
241
242
            $area += ($x2 * $y1 - $x1 * $y2);
243
        }
244
245
        $area *= 0.5 * pow($radius, 2);
246
        $area = abs($area);
247
248
        return $area;
249
    }
250
251
    /**
252
     * Create a new polygon with reversed order of points, i. e. reversed
253
     * polygon direction.
254
     *
255
     * @return Polygon
256
     */
257
    public function getReverse()
258
    {
259
        $reversed = new static();
260
261
        foreach (array_reverse($this->points) as $point) {
262
            $reversed->addPoint($point);
263
        }
264
265
        return $reversed;
266
    }
267
}
268