Geo::setRadius()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
namespace MatthiasMullie\Geo;
3
4
/**
5
 * Please report bugs on https://github.com/matthiasmullie/geo/issues
6
 *
7
 * @author Matthias Mullie <[email protected]>
8
 *
9
 * @copyright Copyright (c) 2013, Matthias Mullie. All rights reserved.
10
 * @license MIT License
11
 */
12
class Geo
13
{
14
    /**
15
     * Earth's mean radii.
16
     * Note that earth is not exactly round and so calculations will always be
17
     * slightly off.
18
     *
19
     * @see http://en.wikipedia.org/wiki/Earth_radius
20
     * @var float[]
21
     */
22
    protected $radii = array(
23
        // metric
24
        'km' => 6371,
25
        'hm' => 63710, // km * 10
26
        'dam' => 637100, // km * 100
27
        'm' => 6371000, // km * 1000
28
        'dm' => 63710000, // km * 10000
29
        'cm' => 637100000, // km * 100000
30
        'mm' => 6371000000, // km * 1000000
31
32
        // imperial (https://en.wikipedia.org/wiki/Imperial_units)
33
        'mi' => 3959,
34
        'fur' => 31672, // mi * 8
35
        'ch' => 316720, // mi * 80
36
        'yd' => 6967840, // mi * 1760
37
        'ft' => 20903520, // mi * 5280
38
        'in' => 250842240, // mi * 63360
39
    );
40
41
    /**
42
     * @var string
43
     */
44
    protected $unit;
45
46
    /**
47
     * @var float
48
     */
49
    protected $radius;
50
51
    /**
52
     * @param  string[optional] $unit e.g. km (kilometers) or mi (miles)
53
     * @throws Exception
54
     */
55
    public function __construct($unit = 'km')
56
    {
57
        $unit = strtolower($unit);
58
59
        // doublecheck if given unit is valid
60
        $units = array_keys($this->radii);
61
        if (!in_array($unit, $units)) {
62
            throw new Exception("Distance unit $unit is invalid. Valid units: ".implode(', ', $units));
63
        }
64
65
        $this->unit = $unit;
66
        $this->setRadius();
67
    }
68
69
    /**
70
     * Allows to override radius.
71
     * If null, radius will be reset to the default for the given unit.
72
     *
73
     * @param float[optional] $radius
74
     */
75
    public function setRadius($radius = null)
76
    {
77
        if ($radius === null) {
78
            $radius = $this->radii[$this->unit];
79
        }
80
81
        $this->radius = $radius;
82
    }
83
84
    /**
85
     * @see http://en.wikipedia.org/wiki/Great-circle_distance
86
     * @param  Coordinate $coord1 First coordinate
87
     * @param  Coordinate $coord2 Second coordinate
88
     * @return float      Actual distance in human readable format (e.g. km or mi)
89
     */
90
    public function distance(Coordinate $coord1, Coordinate $coord2)
91
    {
92
        // convert latitude/longitude degrees for both coordinates
93
        // to radians: radian = degree * π / 180
94
        $lat1 = deg2rad($coord1->latitude);
95
        $lng1 = deg2rad($coord1->longitude);
96
        $lat2 = deg2rad($coord2->latitude);
97
        $lng2 = deg2rad($coord2->longitude);
98
99
        // calculate great-circle distance
100
        $distance = acos(sin($lat1) * sin($lat2) + cos($lat1) * cos($lat2) * cos($lng1 - $lng2));
101
102
        // distance in given format
103
        return $this->radius * $distance;
104
    }
105
106
    /**
107
     * This calculates the boundary at $distance north, east, south & west from
108
     * $coord.
109
     *
110
     * This can be used to easily query a database for coordinate within certain
111
     * boundaries, like this:
112
     *     SELECT *
113
     *     FROM coordinates
114
     *     WHERE
115
     *         lat BETWEEN :swlat AND :nelat
116
     *         lng BETWEEN :swlng AND :nelng
117
     *
118
     * :swlat being $bounds->sw->latitude
119
     * :swlng being $bounds->sw->longitude
120
     * :nelat being $bounds->ne->latitude
121
     * :nelng being $bounds->ne->longitude
122
     *
123
     * We only need 2 opposite corners in a rectangle to know all 4 boundaries,
124
     * in this case the northeast & southwest coordinate. The northwest
125
     * coordinate, for example, will have the same latitude as the southwest
126
     * coordinate, and the same latitude as the northeast coordinate.
127
     *
128
     * @param  Coordinate $coord    Coordinate to generate bounds for
129
     * @param  float      $distance Distance in human readable format (e.g. km or mi)
130
     * @return Bounds
131
     */
132
    public function bounds(Coordinate $coord, $distance)
133
    {
134
        // simple sanity check: distance cannot be greater than radius
135
        if ($distance > $this->radius) {
136
            $distance = $this->radius;
137
        }
138
139
        // latitude boundaries
140
        $neLat = $coord->latitude + rad2deg($distance / $this->radius);
141
        $swLat = $coord->latitude - rad2deg($distance / $this->radius);
142
143
        // longitude boundaries (longitude gets smaller when latitude increases)
144
        $neLng = $coord->longitude + rad2deg($distance / $this->radius / cos(deg2rad($coord->latitude)));
145
        $swLng = $coord->longitude - rad2deg($distance / $this->radius / cos(deg2rad($coord->latitude)));
146
147
        return new Bounds(
148
            new Coordinate($neLat, $neLng),
149
            new Coordinate($swLat, $swLng)
150
        );
151
    }
152
}
153