Convert::parseCoordinate()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
ccs 10
cts 10
cp 1
rs 9.7666
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
/*
4
 * This file is part of the Geotools library.
5
 *
6
 * (c) Antoine Corcy <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace League\Geotools\Convert;
13
14
use League\Geotools\Coordinate\CoordinateInterface;
15
use League\Geotools\Geotools;
16
use League\Geotools\GeotoolsInterface;
17
18
/**
19
 * Convert class
20
 *
21
 * @author Antoine Corcy <[email protected]>
22
 */
23
class Convert implements ConvertInterface
24
{
25
    /**
26
     * The coordinate to convert.
27
     *
28
     * @var CoordinateInterface
29
     */
30
    protected $coordinates;
31
32
33
    /**
34
     * Set the coordinate to convert.
35
     *
36
     * @param CoordinateInterface $coordinates The coordinate to convert.
37
     */
38 68
    public function __construct(CoordinateInterface $coordinates)
39
    {
40 68
        $this->coordinates = $coordinates;
41 68
    }
42
43
    /**
44
     * Parse decimal degrees coordinate to degrees minutes seconds and decimal minutes coordinate.
45
     *
46
     * @param string $coordinate The coordinate to parse.
47
     *
48
     * @return array The replace pairs values.
49
     */
50 34
    private function parseCoordinate($coordinate)
51
    {
52 34
        list($degrees) = explode('.', abs($coordinate));
53 34
        list($minutes) = explode('.', (abs($coordinate) - $degrees) * 60);
54
55
        return [
56 34
            'positive'       => $coordinate >= 0,
57 34
            'degrees'        => (string) $degrees,
58 34
            'decimalMinutes' => (string) round((abs($coordinate) - $degrees) * 60,
59 34
                ConvertInterface::DECIMAL_MINUTES_PRECISION,
60 34
                ConvertInterface::DECIMAL_MINUTES_MODE),
61 34
            'minutes'        => (string) $minutes,
62 34
            'seconds'        => (string) round(((abs($coordinate) - $degrees) * 60 - $minutes) * 60),
63
        ];
64
    }
65
66
    /**
67
     * {@inheritDoc}
68
     */
69 16
    public function toDegreesMinutesSeconds($format = ConvertInterface::DEFAULT_DMS_FORMAT)
70
    {
71 16
        $latitude  = $this->parseCoordinate($this->coordinates->getLatitude());
72 16
        $longitude = $this->parseCoordinate($this->coordinates->getLongitude());
73
74 16
        return strtr($format, [
75 16
            ConvertInterface::LATITUDE_SIGN       => $latitude['positive'] ? '' : '-',
76 16
            ConvertInterface::LATITUDE_DIRECTION  => $latitude['positive'] ? 'N' : 'S',
77 16
            ConvertInterface::LATITUDE_DEGREES    => $latitude['degrees'],
78 16
            ConvertInterface::LATITUDE_MINUTES    => $latitude['minutes'],
79 16
            ConvertInterface::LATITUDE_SECONDS    => $latitude['seconds'],
80 16
            ConvertInterface::LONGITUDE_SIGN      => $longitude['positive'] ? '' : '-',
81 16
            ConvertInterface::LONGITUDE_DIRECTION => $longitude['positive'] ? 'E' : 'W',
82 16
            ConvertInterface::LONGITUDE_DEGREES   => $longitude['degrees'],
83 16
            ConvertInterface::LONGITUDE_MINUTES   => $longitude['minutes'],
84 16
            ConvertInterface::LONGITUDE_SECONDS   => $longitude['seconds'],
85
        ]);
86
    }
87
88
    /**
89
     * Alias of toDegreesMinutesSeconds function.
90
     *
91
     * @param string $format The way to format the DMS coordinate.
92
     *
93
     * @return string Converted and formatted string.
94
     */
95 10
    public function toDMS($format = ConvertInterface::DEFAULT_DMS_FORMAT)
96
    {
97 10
        return $this->toDegreesMinutesSeconds($format);
98
    }
99
100
    /**
101
     * {@inheritDoc}
102
     */
103 18
    public function toDecimalMinutes($format = ConvertInterface::DEFAULT_DM_FORMAT)
104
    {
105 18
        $latitude  = $this->parseCoordinate($this->coordinates->getLatitude());
106 18
        $longitude = $this->parseCoordinate($this->coordinates->getLongitude());
107
108 18
        return strtr($format, [
109 18
            ConvertInterface::LATITUDE_SIGN             => $latitude['positive'] ? '' : '-',
110 18
            ConvertInterface::LATITUDE_DIRECTION        => $latitude['positive'] ? 'N' : 'S',
111 18
            ConvertInterface::LATITUDE_DEGREES          => $latitude['degrees'],
112 18
            ConvertInterface::LATITUDE_DECIMAL_MINUTES  => $latitude['decimalMinutes'],
113 18
            ConvertInterface::LONGITUDE_SIGN            => $longitude['positive'] ? '' : '-',
114 18
            ConvertInterface::LONGITUDE_DIRECTION       => $longitude['positive'] ? 'E' : 'W',
115 18
            ConvertInterface::LONGITUDE_DEGREES         => $longitude['degrees'],
116 18
            ConvertInterface::LONGITUDE_DECIMAL_MINUTES => $longitude['decimalMinutes'],
117
        ]);
118
    }
119
120
    /**
121
     * Alias of toDecimalMinutes function.
122
     *
123
     * @param string $format The way to format the DMS coordinate.
124
     *
125
     * @return string Converted and formatted string.
126
     */
127 11
    public function toDM($format = ConvertInterface::DEFAULT_DM_FORMAT)
128
    {
129 11
        return $this->toDecimalMinutes($format);
130
    }
131
132
    /**
133
     * {@inheritDoc}
134
     */
135 32
    public function toUniversalTransverseMercator()
136
    {
137
        // Convert decimal degrees coordinates to radian.
138 32
        $phi    = deg2rad($this->coordinates->getLatitude());
139 32
        $lambda = deg2rad($this->coordinates->getLongitude());
140
141
        // Compute the zone UTM zone.
142 32
        $zone = (int) (($this->coordinates->getLongitude() + 180.0) / 6) + 1;
143
144
        // Special zone for South Norway.
145
        // On the southwest coast of Norway, grid zone 32V (9° of longitude in width) is extended further west,
146
        // and grid zone 31V (3° of longitude in width) is correspondingly shrunk to cover only open water.
147 32
        if ($this->coordinates->getLatitude() >= 56.0 && $this->coordinates->getLatitude() < 64.0
148 32
            && $this->coordinates->getLongitude() >= 3.0 && $this->coordinates->getLongitude() < 12.0) {
149 2
            $zone = 32;
150
        }
151
152
        // Special zone for Svalbard.
153
        // In the region around Svalbard, the four grid zones 31X (9° of longitude in width),
154
        // 33X (12° of longitude in width), 35X (12° of longitude in width), and 37X (9° of longitude in width)
155
        // are extended to cover what would otherwise have been covered by the seven grid zones 31X to 37X.
156
        // The three grid zones 32X, 34X and 36X are not used.
157 32
        if ($this->coordinates->getLatitude() >= 72.0 && $this->coordinates->getLatitude() < 84.0) {
158 10
            if ($this->coordinates->getLongitude() >= 0.0 && $this->coordinates->getLongitude() < 9.0) {
159 2
                $zone = 31;
160 8
            } elseif ($this->coordinates->getLongitude() >= 9.0 && $this->coordinates->getLongitude() < 21.0) {
161 2
                $zone = 33;
162 6
            } elseif ($this->coordinates->getLongitude() >= 21.0 && $this->coordinates->getLongitude() < 33.0) {
163 2
                $zone = 35;
164 4
            } elseif ($this->coordinates->getLongitude() >= 33.0 && $this->coordinates->getLongitude() < 42.0) {
165 2
                $zone = 37;
166
            }
167
        }
168
169
        // Determines the central meridian for the given UTM zone.
170 32
        $lambda0 = deg2rad(-183.0 + ($zone * 6.0));
171
172 32
        $ep2 = (pow($this->coordinates->getEllipsoid()->getA(), 2.0) -
173 32
            pow($this->coordinates->getEllipsoid()->getB(), 2.0)) / pow($this->coordinates->getEllipsoid()->getB(), 2.0);
174 32
        $nu2 = $ep2 * pow(cos($phi), 2.0);
175 32
        $nN  = pow($this->coordinates->getEllipsoid()->getA(), 2.0) /
176 32
            ($this->coordinates->getEllipsoid()->getB() * sqrt(1 + $nu2));
177 32
        $t   = tan($phi);
178 32
        $t2  = $t * $t;
179 32
        $l   = $lambda - $lambda0;
180
181 32
        $l3coef = 1.0 - $t2 + $nu2;
182 32
        $l4coef = 5.0 - $t2 + 9 * $nu2 + 4.0 * ($nu2 * $nu2);
183 32
        $l5coef = 5.0 - 18.0 * $t2 + ($t2 * $t2) + 14.0 * $nu2 - 58.0 * $t2 * $nu2;
184 32
        $l6coef = 61.0 - 58.0 * $t2 + ($t2 * $t2) + 270.0 * $nu2 - 330.0 * $t2 * $nu2;
185 32
        $l7coef = 61.0 - 479.0 * $t2 + 179.0 * ($t2 * $t2) - ($t2 * $t2 * $t2);
186 32
        $l8coef = 1385.0 - 3111.0 * $t2 + 543.0 * ($t2 * $t2) - ($t2 * $t2 * $t2);
187
188
        // Calculate easting.
189 32
        $easting = $nN * cos($phi) * $l
190 32
            + ($nN / 6.0 * pow(cos($phi), 3.0) * $l3coef * pow($l, 3.0))
191 32
            + ($nN / 120.0 * pow(cos($phi), 5.0) * $l5coef * pow($l, 5.0))
192 32
            + ($nN / 5040.0 * pow(cos($phi), 7.0) * $l7coef * pow($l, 7.0));
193
194
        // Calculate northing.
195 32
        $n = ($this->coordinates->getEllipsoid()->getA() - $this->coordinates->getEllipsoid()->getB()) /
196 32
            ($this->coordinates->getEllipsoid()->getA() + $this->coordinates->getEllipsoid()->getB());
197 32
        $alpha = (($this->coordinates->getEllipsoid()->getA() + $this->coordinates->getEllipsoid()->getB()) / 2.0) *
198 32
            (1.0 + (pow($n, 2.0) / 4.0) + (pow($n, 4.0) / 64.0));
199 32
        $beta = (-3.0 * $n / 2.0) + (9.0 * pow($n, 3.0) / 16.0) + (-3.0 * pow($n, 5.0) / 32.0);
200 32
        $gamma = (15.0 * pow($n, 2.0) / 16.0) + (-15.0 * pow($n, 4.0) / 32.0);
201 32
        $delta = (-35.0 * pow($n, 3.0) / 48.0) + (105.0 * pow($n, 5.0) / 256.0);
202 32
        $epsilon = (315.0 * pow($n, 4.0) / 512.0);
203
        $northing = $alpha
204 32
            * ($phi + ($beta * sin(2.0 * $phi))
205 32
            + ($gamma * sin(4.0 * $phi))
206 32
            + ($delta * sin(6.0 * $phi))
207 32
            + ($epsilon * sin(8.0 * $phi)))
208 32
            + ($t / 2.0 * $nN * pow(cos($phi), 2.0) * pow($l, 2.0))
209 32
            + ($t / 24.0 * $nN * pow(cos($phi), 4.0) * $l4coef * pow($l, 4.0))
210 32
            + ($t / 720.0 * $nN * pow(cos($phi), 6.0) * $l6coef * pow($l, 6.0))
211 32
            + ($t / 40320.0 * $nN * pow(cos($phi), 8.0) * $l8coef * pow($l, 8.0));
212
213
        // Adjust easting and northing for UTM system.
214 32
        $easting = $easting * GeotoolsInterface::UTM_SCALE_FACTOR + 500000.0;
215 32
        $northing = $northing * GeotoolsInterface::UTM_SCALE_FACTOR;
216 32
        if ($northing < 0.0) {
217 2
            $northing += 10000000.0;
218
        }
219
220 32
        return sprintf('%d%s %d %d',
221 32
            $zone, Geotools::$latitudeBands[(int) (($this->coordinates->getLatitude() + 80) / 8)], $easting, $northing
222
        );
223
    }
224
225
    /**
226
     * Alias of toUniversalTransverseMercator function.
227
     *
228
     * @return string The converted UTM coordinate in meters
229
     */
230 18
    public function toUTM()
231
    {
232 18
        return $this->toUniversalTransverseMercator();
233
    }
234
}
235