Completed
Push — master ( b734e4...75b20e )
by Marcus
01:08
created

Line::getIntermediatePoint()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 9.424
c 0
b 0
f 0
cc 3
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Location;
6
7
use Location\Bearing\BearingInterface;
8
use Location\Distance\DistanceInterface;
9
use Location\Utility\Cartesian;
10
use RuntimeException;
11
12
/**
13
 * Line Implementation
14
 *
15
 * @author Marcus Jaschen <[email protected]>
16
 */
17
class Line implements GeometryInterface
18
{
19
    use GetBoundsTrait;
20
21
    /**
22
     * @var Coordinate
23
     */
24
    protected $point1;
25
26
    /**
27
     * @var Coordinate
28
     */
29
    protected $point2;
30
31
    /**
32
     * @param Coordinate $point1
33
     * @param Coordinate $point2
34
     */
35
    public function __construct(Coordinate $point1, Coordinate $point2)
36
    {
37
        $this->point1 = $point1;
38
        $this->point2 = $point2;
39
    }
40
41
    /**
42
     * @param Coordinate $point1
43
     *
44
     * @return void
45
     */
46
    public function setPoint1(Coordinate $point1)
47
    {
48
        $this->point1 = $point1;
49
    }
50
51
    /**
52
     * @return Coordinate
53
     */
54
    public function getPoint1(): Coordinate
55
    {
56
        return $this->point1;
57
    }
58
59
    /**
60
     * @param Coordinate $point2
61
     *
62
     * @return void
63
     */
64
    public function setPoint2(Coordinate $point2)
65
    {
66
        $this->point2 = $point2;
67
    }
68
69
    /**
70
     * @return Coordinate
71
     */
72
    public function getPoint2(): Coordinate
73
    {
74
        return $this->point2;
75
    }
76
77
    /**
78
     * Returns an array containing the two points.
79
     *
80
     * @return Coordinate[]
81
     */
82
    public function getPoints(): array
83
    {
84
        return [$this->point1, $this->point2];
85
    }
86
87
    /**
88
     * Calculates the length of the line (distance between the two
89
     * coordinates).
90
     *
91
     * @param DistanceInterface $calculator instance of distance calculation class
92
     *
93
     * @return float
94
     */
95
    public function getLength(DistanceInterface $calculator): float
96
    {
97
        return $calculator->getDistance($this->point1, $this->point2);
98
    }
99
100
    /**
101
     * @param BearingInterface $bearingCalculator
102
     *
103
     * @return float
104
     */
105
    public function getBearing(BearingInterface $bearingCalculator): float
106
    {
107
        return $bearingCalculator->calculateBearing($this->point1, $this->point2);
108
    }
109
110
    /**
111
     * @param BearingInterface $bearingCalculator
112
     *
113
     * @return float
114
     */
115
    public function getFinalBearing(BearingInterface $bearingCalculator): float
116
    {
117
        return $bearingCalculator->calculateFinalBearing($this->point1, $this->point2);
118
    }
119
120
    /**
121
     * Create a new instance with reversed point order, i. e. reversed direction.
122
     *
123
     * @return Line
124
     */
125
    public function getReverse(): Line
126
    {
127
        return new static($this->point2, $this->point1);
128
    }
129
130
    /**
131
     * Get the midpoint of a Line segment
132
     *
133
     * @see http://www.movable-type.co.uk/scripts/latlong.html#midpoint
134
     *
135
     * @return Coordinate
136
     */
137
    public function getMidpoint() : Coordinate
138
    {
139
        $lat1 = deg2rad($this->point1->getLat());
140
        $lng1 = deg2rad($this->point1->getLng());
141
        $lat2 = deg2rad($this->point2->getLat());
142
        $lng2 = deg2rad($this->point2->getLng());
143
        $deltaLng = $lng2 - $lng1;
144
145
        $A = new Cartesian(cos($lat1), 0, sin($lat1));
146
        $B = new Cartesian(cos($lat2) * cos($deltaLng), cos($lat2) * sin($deltaLng), sin($lat2));
147
        $C = $A->add($B);
148
149
        $latMid = atan2($C->getZ(), sqrt($C->getX() ** 2 + $C->getY() ** 2));
150
        $lngMid = $lng1 + atan2($C->getY(), $C->getX());
151
152
        return new Coordinate(rad2deg($latMid), rad2deg($lngMid));
153
    }
154
155
    /**
156
     * Returns the point which is located on the line at the
157
     * given fraction (starting at point 1).
158
     *
159
     * @see http://www.movable-type.co.uk/scripts/latlong.html#intermediate-point
160
     * @see http://www.edwilliams.org/avform.htm#Intermediate
161
     *
162
     * @param float $fraction 0.0 ... 1.0 (smaller or larger values work too)
163
     *
164
     * @return Coordinate
165
     *
166
     * @throws RuntimeException
167
     */
168
    public function getIntermediatePoint(float $fraction): Coordinate
169
    {
170
        $lat1 = deg2rad($this->point1->getLat());
171
        $lng1 = deg2rad($this->point1->getLng());
172
        $lat2 = deg2rad($this->point2->getLat());
173
        $lng2 = deg2rad($this->point2->getLng());
174
        $deltaLat = $lat2 - $lat1;
175
        $deltaLng = $lng2 - $lng1;
176
177
        if ($lat1 + $lat2 == 0.0 && abs($lng1 - $lng2) == M_PI) {
178
            throw new RuntimeException(
179
                'Start and end points are antipodes, route is therefore undefined.',
180
                5382449689
181
            );
182
        }
183
184
        $a = sin($deltaLat / 2) ** 2 + cos($lat1) * cos($lat2) * sin($deltaLng / 2) ** 2;
185
        $delta = 2 * atan2(sqrt($a), sqrt(1 - $a));
186
187
        $A = sin((1 - $fraction) * $delta) / sin($delta);
188
        $B = sin($fraction * $delta) / sin($delta);
189
190
        $x = $A * cos($lat1) * cos($lng1) + $B * cos($lat2) * cos($lng2);
191
        $y = $A * cos($lat1) * sin($lng1) + $B * cos($lat2) * sin($lng2);
192
        $z = $A * sin($lat1) + $B * sin($lat2);
193
194
        $lat = atan2($z, sqrt($x ** 2 + $y ** 2));
195
        $lng = atan2($y, $x);
196
197
        return new Coordinate(rad2deg($lat), rad2deg($lng));
198
    }
199
}
200