Passed
Push — master ( 4319a4...7d1517 )
by Jan-Marten
05:44 queued 10s
created

DistanceCalculator::calculate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 8
rs 9.4285
c 1
b 0
f 0
1
<?php
2
3
namespace ZeroConfig\GeoDistance;
4
5
use Measurements\Exceptions\UnitException;
6
use Measurements\Measurement;
7
use Measurements\Quantities\Angle;
8
use Measurements\Quantities\Length;
9
use Measurements\Units\UnitAngle;
10
use Measurements\Units\UnitLength;
11
use ZeroConfig\GeoDistance\Sphere\SphereInterface;
12
13
/**
14
 * @see https://en.wikipedia.org/wiki/Haversine_formula
15
 */
16
class DistanceCalculator implements DistanceCalculatorInterface
17
{
18
    /** @var SphereInterface */
19
    private $sphere;
20
21
    /**
22
     * Constructor.
23
     *
24
     * @param SphereInterface $sphere
25
     */
26
    public function __construct(SphereInterface $sphere)
27
    {
28
        $this->sphere = $sphere;
29
    }
30
31
    /**
32
     * Calculate the distance between two positions.
33
     *
34
     * @param PositionInterface $start
35
     * @param PositionInterface $end
36
     *
37
     * @return Measurement
38
     * @throws UnitException UnitException When part of the distance is
39
     *   incalculable.
40
     */
41
    public function calculate(
42
        PositionInterface $start,
43
        PositionInterface $end
44
    ): Measurement {
45
        return (new Length(
46
            $this->calculateRadianLength($start, $end),
47
            UnitLength::meters()
48
        ))->multiplyBy($this->sphere->getRadius());
49
    }
50
51
    /**
52
     * Calculate the radian length between start and end.
53
     *
54
     * @param PositionInterface $start
55
     * @param PositionInterface $end
56
     *
57
     * @return float
58
     * @throws UnitException UnitException When part of the length is
59
     *  incalculable.
60
     */
61
    private function calculateRadianLength(
62
        PositionInterface $start,
63
        PositionInterface $end
64
    ): float {
65
        $startLatitude  = $this->getRadianValue($start->getLatitude());
66
        $endLatitude    = $this->getRadianValue($end->getLatitude());
67
        $deltaLatitude  = $this->getRadianValue(
68
            $start
69
                ->getLatitude()
70
                ->subtract($end->getLatitude())
71
        );
72
        $deltaLongitude = $this->getRadianValue(
73
            $start
74
                ->getLongitude()
75
                ->subtract($end->getLongitude())
76
        );
77
78
        return asin(
79
            sqrt(
80
                pow(
81
                    sin($deltaLatitude * 0.5),
82
                    2
83
                )
84
                + cos($startLatitude)
85
                * cos($endLatitude)
86
                * pow(
87
                    sin($deltaLongitude * 0.5),
88
                    2
89
                )
90
            )
91
        ) * 2;
92
    }
93
94
    /**
95
     * Get the radion value for the supplied angle.
96
     *
97
     * @param Measurement $angle
98
     *
99
     * @return float
100
     * @throws UnitException UnitException When part of the angle is
101
     *   incalculable.
102
     */
103
    private function getRadianValue(Measurement $angle): float
104
    {
105
        return $angle->convertTo(UnitAngle::radians())->value();
106
    }
107
}
108