Completed
Push — milestone/4.0 ( 4a7219...1803cd )
by Marcus
01:48
created

Polygon::getPerimeter()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 14
Ratio 100 %

Importance

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