Passed
Branch master (b57997)
by Doug
12:41
created

BoundingArea::createFromExtentCodes()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 10
nc 2
nop 1
dl 0
loc 16
ccs 11
cts 11
cp 1
crap 4
rs 9.9332
c 1
b 0
f 0
1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord\Geometry;
10
11
use function array_merge;
12
use function class_exists;
13
use function count;
14
use function implode;
15
use PHPCoord\CoordinateOperation\GeographicValue;
16
use PHPCoord\UnitOfMeasure\Angle\Angle;
17
use PHPCoord\UnitOfMeasure\Angle\Degree;
18
19
class BoundingArea
20
{
21
    /**
22
     * Vertices in GeoJSON-type format (an array of polygons, which is an array of rings which is an array of long,lat points).
23
     * @var array<array<array<array<float, float>>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array<array<array<float, float>>> at position 13 could not be parsed: Expected '>' at position 13, but found '>'.
Loading history...
24
     */
25
    protected array $vertices;
26
27
    protected bool $longitudeExtendsFurtherThanMinus180 = false;
28
29
    protected bool $longitudeExtendsFurtherThanPlus180 = false;
30
31
    private static array $cachedObjects = [];
32
33 621
    protected function __construct(array $vertices)
34
    {
35 621
        $this->vertices = $vertices;
36 621
        foreach ($this->vertices as $polygon) {
37 621
            foreach ($polygon as $ring) {
38 621
                foreach ($ring as $vertex) {
39 621
                    if ($vertex[0] > 180) {
40 99
                        $this->longitudeExtendsFurtherThanPlus180 = true;
41
                    }
42 621
                    if ($vertex[0] < -180) {
43 36
                        $this->longitudeExtendsFurtherThanMinus180 = true;
44
                    }
45
                }
46
            }
47
        }
48 621
    }
49
50
    /**
51
     * @param array<array<array<array<float, float>>> $vertices [[[long,lat], [long,lat]...]]
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array<array<array<float, float>>> at position 13 could not be parsed: Expected '>' at position 13, but found '>'.
Loading history...
52
     */
53 594
    public static function createFromArray(array $vertices): self
54
    {
55 594
        return new static($vertices);
56
    }
57
58 54
    public static function createWorld(): self
59
    {
60 54
        return new static([[[[-180, -90], [-180, 90], [180, 90], [180, -90], [-180, -90]]]]);
61
    }
62
63
    /**
64
     * @internal
65
     */
66 648
    public static function createFromExtentCodes(array $extentCodes): self
67
    {
68 648
        $cacheKey = implode('', $extentCodes);
69 648
        if (!isset(self::$cachedObjects[$cacheKey])) {
70 477
            $extents = [];
71 477
            foreach ($extentCodes as $extentCode) {
72 477
                $fullExtent = "PHPCoord\\Geometry\\Extents\\Extent{$extentCode}";
73 477
                $basicExtent = "PHPCoord\\Geometry\\Extents\\BoundingBoxOnly\\Extent{$extentCode}";
74 477
                $extentClass = class_exists($fullExtent) ? new $fullExtent() : new $basicExtent();
75 477
                $extents = array_merge($extents, $extentClass());
76
            }
77
78 477
            self::$cachedObjects[$cacheKey] = self::createFromArray($extents);
79
        }
80
81 648
        return self::$cachedObjects[$cacheKey];
82
    }
83
84 234
    public function containsPoint(GeographicValue $point): bool
85
    {
86
        $pointsToCheck = [
87
            [
88 234
                $point->getLongitude()->asDegrees()->getValue(),
89 234
                $point->getLatitude()->asDegrees()->getValue(),
90
            ],
91
        ];
92
93 234
        if ($this->longitudeExtendsFurtherThanMinus180) {
94 9
            $pointsToCheck[] = [
95 9
                $point->getLongitude()->asDegrees()->getValue() - 360,
96 9
                $point->getLatitude()->asDegrees()->getValue(),
97
            ];
98
        }
99
100 234
        if ($this->longitudeExtendsFurtherThanPlus180) {
101 36
            $pointsToCheck[] = [
102 36
                $point->getLongitude()->asDegrees()->getValue() + 360,
103 36
                $point->getLatitude()->asDegrees()->getValue(),
104
            ];
105
        }
106
107
        /*
108
         * @see https://observablehq.com/@tmcw/understanding-point-in-polygon
109
         */
110 234
        foreach ($pointsToCheck as $pointToCheck) {
111 234
            [$x, $y] = $pointToCheck;
112 234
            foreach ($this->vertices as $polygon) {
113 234
                $vertices = [];
114 234
                foreach ($polygon as $ring) {
115 234
                    $vertices = array_merge($vertices, $ring);
116
                }
117
118 234
                $n = count($vertices);
119 234
                $inside = false;
120 234
                for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
121 234
                    $xi = $vertices[$i][0];
122 234
                    $yi = $vertices[$i][1];
123 234
                    $xj = $vertices[$j][0];
124 234
                    $yj = $vertices[$j][1];
125
126 234
                    $intersect = (($yi > $y) !== ($yj > $y)) // horizontal ray from $y, intersects if vertices are on opposite sides of it
127 234
                        && ($x < ($xj - $xi) * ($y - $yi) / ($yj - $yi) + $xi);
128 234
                    if ($intersect) {
129 225
                        $inside = !$inside;
0 ignored issues
show
introduced by
The condition $inside is always false.
Loading history...
130
                    }
131
                }
132
133 234
                if ($inside) {
134 225
                    return true;
135
                }
136
            }
137
        }
138
139 99
        return false;
140
    }
141
142
    /**
143
     * @internal used for testing
144
     * @return array<Angle,Angle>
145
     */
146 27
    public function getPointInside(): array
147
    {
148
        // Calculates the "centre" (centroid) of a polygon.
149 27
        $vertices = $this->vertices[0][0]; // first polygon, outer ring
150 27
        $n = count($vertices);
151 27
        $area = 0;
152
153 27
        for ($i = 0; $i < ($n - 1); ++$i) {
154 27
            $area += $vertices[$i][0] * $vertices[$i + 1][1];
155
        }
156 27
        $area += $vertices[$n - 1][0] * $vertices[0][1];
157
158 27
        for ($i = 0; $i < ($n - 1); ++$i) {
159 27
            $area -= $vertices[$i + 1][0] * $vertices[$i][1];
160
        }
161 27
        $area -= $vertices[0][0] * $vertices[$n - 1][1];
162 27
        $area /= 2;
163
164 27
        $latitude = 0;
165 27
        $longitude = 0;
166
167 27
        for ($i = 0; $i < ($n - 1); ++$i) {
168 27
            $latitude += ($vertices[$i][1] + $vertices[$i + 1][1]) * ($vertices[$i][0] * $vertices[$i + 1][1] - $vertices[$i + 1][0] * $vertices[$i][1]);
169 27
            $longitude += ($vertices[$i][0] + $vertices[$i + 1][0]) * ($vertices[$i][0] * $vertices[$i + 1][1] - $vertices[$i + 1][0] * $vertices[$i][1]);
170
        }
171 27
        $latitude = new Degree($latitude / 6 / $area);
172 27
        $longitude = new Degree($longitude / 6 / $area);
173
174 27
        return [$latitude, $longitude];
175
    }
176
}
177