Completed
Push — master ( cc7c2e...dbf04b )
by Marcus
02:09
created

Polygon::getReverse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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