Completed
Push — cleanup/small-fixes ( a08300 )
by Marcus
01:18
created

src/Polygon.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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