Completed
Branch 3.0-dev (e499db)
by Doug
02:12
created

LatLng::setRefEll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * PHPCoord
4
 * @package PHPCoord
5
 * @author Jonathan Stott
6
 * @author Doug Wright
7
 */
8
declare(strict_types=1);
9
namespace PHPCoord;
10
11
/**
12
 * Latitude/Longitude reference
13
 * @author Jonathan Stott
14
 * @author Doug Wright
15
 * @package PHPCoord
16
 */
17
class LatLng
18
{
19
20
    /**
21
     * Latitude
22
     * @var float
23
     */
24
    protected $lat;
25
26
    /**
27
     * Longitude
28
     * @var float
29
     */
30
    protected $lng;
31
32
    /**
33
     * Height
34
     * @var float
35
     */
36
    protected $h;
37
38
    /**
39
     * Reference ellipsoid the co-ordinates are from
40
     * @var RefEll
41
     */
42
    protected $refEll;
43
44
    /**
45
     * Create a new LatLng object from the given latitude and longitude
46
     * @param float $lat
47
     * @param float $lng
48
     * @param int $height
49
     * @param RefEll $refEll
50
     */
51 31
    public function __construct(float $lat, float $lng, int $height, RefEll $refEll)
52
    {
53 31
        $this->lat = $lat;
54 31
        $this->lng = $lng;
55 31
        $this->h = $height;
0 ignored issues
show
Documentation Bug introduced by
The property $h was declared of type double, but $height is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
56 31
        $this->refEll = $refEll;
57
    }
58
59
    /**
60
     * Return a string representation of this LatLng object
61
     * @return string
62
     */
63 13
    public function __toString(): string
64
    {
65 13
        return "({$this->getLat()}, {$this->getLng()})";
66
    }
67
68
    /**
69
     * @return float
70
     */
71 24
    public function getLat(): float
72
    {
73 24
        return round($this->lat, 5);
74
    }
75
76
    /**
77
     * @return float
78
     */
79 24
    public function getLng(): float
80
    {
81 24
        return round($this->lng, 5);
82
    }
83
84
    /**
85
     * @return int
86
     */
87 10
    public function getH(): int
88
    {
89 10
        return $this->h;
90
    }
91
92
    /**
93
     * @return RefEll
94
     */
95 9
    public function getRefEll(): RefEll
96
    {
97 9
        return $this->refEll;
98
    }
99
100
    /**
101
     * Calculate the surface distance between this LatLng object and the one
102
     * passed in as a parameter.
103
     *
104
     * @param LatLng $to a LatLng object to measure the surface distance to
105
     * @return int distance in metres
106
     */
107 2
    public function distance(LatLng $to): int
108
    {
109 2
        if ($this->refEll != $to->refEll) {
110 1
            throw new \RuntimeException('Source and destination co-ordinates are not using the same ellipsoid');
111
        }
112
113
        //Mean radius definition taken from Wikipedia
114 1
        $er = ((2 * $this->refEll->getMaj()) + $this->refEll->getMin()) / 3;
115
116 1
        $latFrom = deg2rad($this->lat);
117 1
        $latTo = deg2rad($to->lat);
118 1
        $lngFrom = deg2rad($this->lng);
119 1
        $lngTo = deg2rad($to->lng);
120
121 1
        $d = acos(sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lngTo - $lngFrom)) * $er;
122
123 1
        return (int)round($d);
124
    }
125
126
127
    /**
128
     * Convert this LatLng object to OSGB36 datum.
129
     * Reference values for transformation are taken from OS document
130
     * "A Guide to Coordinate Systems in Great Britain"
131
     * @return LatLng
132
     */
133 4 View Code Duplication
    public function toOSGB36(): LatLng
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
134
    {
135 4
        if ($this->refEll == RefEll::airy1830()) {
136 2
            return $this;
137
        }
138
139 2
        $asWGS84 = $this->toWGS84();
140
141 1
        $tx = -446.448;
142 1
        $ty = 125.157;
143 1
        $tz = -542.060;
144 1
        $s = 0.0000204894;
145 1
        $rx = deg2rad(-0.1502 / 3600);
146 1
        $ry = deg2rad(-0.2470 / 3600);
147 1
        $rz = deg2rad(-0.8421 / 3600);
148
149 1
        return $asWGS84->transformDatum(RefEll::airy1830(), $tx, $ty, $tz, $s, $rx, $ry, $rz);
150
    }
151
152
    /**
153
     * Convert this LatLng object to ED50 datum.
154
     * Reference values for transformation are taken from http://www.globalmapper.com/helpv9/datum_list.htm
155
     * @return LatLng
156
     */
157 1 View Code Duplication
    public function toED50(): LatLng
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
158
    {
159 1
        if ($this->refEll == RefEll::international1924()) {
160
            return $this;
161
        }
162
163 1
        $asWGS84 = $this->toWGS84();
164
165 1
        $tx = 87;
166 1
        $ty = 98;
167 1
        $tz = 121;
168 1
        $s = 0;
169 1
        $rx = deg2rad(0);
170 1
        $ry = deg2rad(0);
171 1
        $rz = deg2rad(0);
172
173 1
        return $asWGS84->transformDatum(RefEll::international1924(), $tx, $ty, $tz, $s, $rx, $ry, $rz);
174
    }
175
176
    /**
177
     * Convert this LatLng object to NAD27 datum.
178
     * Reference values for transformation are taken from Wikipedia
179
     * @return LatLng
180
     */
181 1 View Code Duplication
    public function toNAD27(): LatLng
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
182
    {
183 1
        if ($this->refEll == RefEll::clarke1866()) {
184
            return $this;
185
        }
186
187 1
        $asWGS84 = $this->toWGS84();
188
189 1
        $tx = 8;
190 1
        $ty = -160;
191 1
        $tz = -176;
192 1
        $s = 0;
193 1
        $rx = deg2rad(0);
194 1
        $ry = deg2rad(0);
195 1
        $rz = deg2rad(0);
196
197 1
        return $asWGS84->transformDatum(RefEll::clarke1866(), $tx, $ty, $tz, $s, $rx, $ry, $rz);
198
    }
199
200
    /**
201
     * Convert this LatLng object to Ireland 1975 datum.
202
     * Reference values for transformation are taken from OSI document
203
     * "Making maps compatible with GPS"
204
     * @return LatLng
205
     */
206 1 View Code Duplication
    public function toIreland1975(): LatLng
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
207
    {
208 1
        if ($this->refEll == RefEll::airyModified()) {
209
            return $this;
210
        }
211
212 1
        $asWGS84 = $this->toWGS84();
213
214 1
        $tx = -482.530;
215 1
        $ty = 130.596;
216 1
        $tz = -564.557;
217 1
        $s = -0.00000815;
218 1
        $rx = deg2rad(-1.042 / 3600);
219 1
        $ry = deg2rad(-0.214 / 3600);
220 1
        $rz = deg2rad(-0.631 / 3600);
221
222 1
        return $asWGS84->transformDatum(RefEll::airyModified(), $tx, $ty, $tz, $s, $rx, $ry, $rz);
223
    }
224
225
    /**
226
     * Convert this LatLng object to WGS84 datum
227
     * @return LatLng
228
     */
229 15
    public function toWGS84(): LatLng {
230
231 15
        switch ($this->refEll) {
232
233 15
            case RefEll::wgs84():
234 8
                return $this; //do nothing
235
236 7
            case RefEll::airy1830(): // values from OSGB document "A Guide to Coordinate Systems in Great Britain"
237 2
                $tx = 446.448;
238 2
                $ty = -125.157;
239 2
                $tz = 542.060;
240 2
                $s = -0.0000204894;
241 2
                $rx = deg2rad(0.1502 / 3600);
242 2
                $ry = deg2rad(0.2470 / 3600);
243 2
                $rz = deg2rad(0.8421 / 3600);
244 2
                break;
245
246 5
            case RefEll::airyModified(): // values from OSI document "Making maps compatible with GPS"
247 1
                $tx = 482.530;
248 1
                $ty = -130.596;
249 1
                $tz = 564.557;
250 1
                $s = 0.00000815;
251 1
                $rx = deg2rad(1.042 / 3600);
252 1
                $ry = deg2rad(0.214 / 3600);
253 1
                $rz = deg2rad(0.631 / 3600);
254 1
                break;
255
256 4 View Code Duplication
            case RefEll::clarke1866(): // assumes NAD27, values from Wikipedia
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
257 1
                $tx = -8;
258 1
                $ty = 160;
259 1
                $tz = 176;
260 1
                $s = 0;
261 1
                $rx = deg2rad(0);
262 1
                $ry = deg2rad(0);
263 1
                $rz = deg2rad(0);
264 1
                break;
265
266 3 View Code Duplication
            case RefEll::international1924(): // assumes ED50, values from http://www.globalmapper.com/helpv9/datum_list.htm
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
267 1
                $tx = -87;
268 1
                $ty = -98;
269 1
                $tz = -121;
270 1
                $s = 0;
271 1
                $rx = deg2rad(0);
272 1
                $ry = deg2rad(0);
273 1
                $rz = deg2rad(0);
274 1
                break;
275
276 2
            case RefEll::bessel1841(): // assumes Germany, values from Wikipedia
277
                $tx = 582;
278
                $ty = -105;
279
                $tz = -414;
280
                $s = 0.0000083;
281
                $rx = deg2rad(1.04 / 3600);
282
                $ry = deg2rad(0.35 / 3600);
283
                $rz = deg2rad(-3.08 / 3600);
284
                break;
285
286
            default:
287 2
                throw new \RuntimeException('Transform parameters not known for this ellipsoid');
288
        }
289
290 5
        return $this->transformDatum(RefEll::wgs84(), $tx, $ty, $tz, $s, $rx, $ry, $rz);
291
    }
292
293
    /**
294
     * Transform co-ordinates from one datum to another using a Helmert transformation
295
     * @param RefEll $toRefEll
296
     * @param float $tranX
297
     * @param float $tranY
298
     * @param float $tranZ
299
     * @param float $scale
300
     * @param float $rotX  rotation about x-axis in seconds
301
     * @param float $rotY  rotation about y-axis in seconds
302
     * @param float $rotZ  rotation about z-axis in seconds
303
     * @return LatLng
304
     */
305 15
    public function transformDatum(RefEll $toRefEll, $tranX, $tranY, $tranZ, $scale, $rotX, $rotY, $rotZ): LatLng
306
    {
307
308 15
        if ($this->refEll == $toRefEll) {
309 6
            return $this;
310
        }
311
312 9
        $cartesian = Cartesian::fromLatLong($this);
313 9
        $newCartesian = $cartesian->transformDatum($toRefEll, $tranX, $tranY, $tranZ, $scale, $rotX, $rotY, $rotZ);
314 9
        return $newCartesian->toLatitudeLongitude();
315
    }
316
317
318
    /**
319
     * Convert this LatLng object into an OSGB grid reference. Note that this
320
     * function does not take into account the bounds of the OSGB grid -
321
     * beyond the bounds of the OSGB grid, the resulting OSRef object has no
322
     * meaning
323
     *
324
     * Reference values for transformation are taken from OS document
325
     * "A Guide to Coordinate Systems in Great Britain"
326
     *
327
     * @return OSRef
328
     */
329 3 View Code Duplication
    public function toOSRef(): OSRef
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
330
    {
331 3
        $asOSGB36 = $this->toOSGB36();
332
333 2
        $OSGB = new OSRef(0, 0); //dummy to get reference data
334 2
        $scale = $OSGB->getScaleFactor();
335 2
        $N0 = $OSGB->getOriginNorthing();
336 2
        $E0 = $OSGB->getOriginEasting();
337 2
        $phi0 = $OSGB->getOriginLatitude();
338 2
        $lambda0 = $OSGB->getOriginLongitude();
339
340 2
        $coords = $asOSGB36->toTransverseMercatorEastingNorthing($scale, $E0, $N0, $phi0, $lambda0);
341
342 2
        return new OSRef($coords['E'], $coords['N'], $this->h);
343
    }
344
345
    /**
346
     * Convert this LatLng object into an ITM grid reference
347
     *
348
     * @return ITMRef
349
     */
350 1 View Code Duplication
    public function toITMRef()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
351
    {
352 1
        $asWGS84 = $this->toWGS84();
353
354 1
        $ITM = new ITMRef(0, 0); //dummy to get reference data
355 1
        $scale = $ITM->getScaleFactor();
356 1
        $N0 = $ITM->getOriginNorthing();
357 1
        $E0 = $ITM->getOriginEasting();
358 1
        $phi0 = $ITM->getOriginLatitude();
359 1
        $lambda0 = $ITM->getOriginLongitude();
360
361 1
        $coords = $asWGS84->toTransverseMercatorEastingNorthing($scale, $E0, $N0, $phi0, $lambda0);
362
363 1
        return new ITMRef($coords['E'], $coords['N'], $this->h);
364
    }
365
366
367
    /**
368
     * Convert a WGS84 latitude and longitude to an UTM reference
369
     *
370
     * Reference values for transformation are taken from OS document
371
     * "A Guide to Coordinate Systems in Great Britain"
372
     * @return UTMRef
373
     */
374 4
    public function toUTMRef(): UTMRef
375
    {
376 4
        $asWGS84 = $this->toWGS84();
377
378 4
        $longitudeZone = (int)(($asWGS84->getLng() + 180) / 6) + 1;
379
380
        // Special zone for Norway
381 4
        if ($asWGS84->getLat() >= 56 && $asWGS84->getLat() < 64 && $asWGS84->getLng() >= 3 && $asWGS84->getLng() < 12) {
382
            $longitudeZone = 32;
383 4
        } elseif ($asWGS84->getLat() >= 72 && $asWGS84->getLat() < 84) { // Special zones for Svalbard
384
            if ($asWGS84->getLng() >= 0 && $asWGS84->getLng() < 9) {
385
                $longitudeZone = 31;
386
            } elseif ($asWGS84->getLng() >= 9 && $asWGS84->getLng() < 21) {
387
                $longitudeZone = 33;
388
            } elseif ($asWGS84->getLng() >= 21 && $asWGS84->getLng() < 33) {
389
                $longitudeZone = 35;
390
            } elseif ($asWGS84->getLng() >= 33 && $asWGS84->getLng() < 42) {
391
                $longitudeZone = 37;
392
            }
393
        }
394
395 4
        $UTMZone = $this->getUTMLatitudeZoneLetter($asWGS84->getLat());
396
397 2
        $UTM = new UTMRef(0, 0, 0, $UTMZone, $longitudeZone); //dummy to get reference data
398 2
        $scale = $UTM->getScaleFactor();
399 2
        $N0 = $UTM->getOriginNorthing();
400 2
        $E0 = $UTM->getOriginEasting();
401 2
        $phi0 = $UTM->getOriginLatitude();
402 2
        $lambda0 = $UTM->getOriginLongitude();
403
404 2
        $coords = $asWGS84->toTransverseMercatorEastingNorthing($scale, $E0, $N0, $phi0, $lambda0);
405
406 2
        if ($asWGS84->getLat() < 0) {
407 1
            $coords['N'] += 10000000;
408
        }
409
410 2
        return new UTMRef($coords['E'], $coords['N'], $asWGS84->getH(), $UTMZone, $longitudeZone);
411
    }
412
413
    /**
414
     * Work out the UTM latitude zone from the latitude
415
     * @param float $latitude
416
     * @return string
417
     */
418 4
    private function getUTMLatitudeZoneLetter($latitude): string
419
    {
420 4
        if ($latitude < -80 || $latitude > 84) {
421 2
            throw new \OutOfRangeException('UTM zones do not apply in polar regions');
422
        }
423
424 2
        $zones = "CDEFGHJKLMNPQRSTUVWXX";
425 2
        $zoneIndex = (int)(($latitude + 80) / 8);
426 2
        return $zones[$zoneIndex];
427
    }
428
429
430
    /**
431
     * Convert a latitude and longitude to easting and northing using a Transverse Mercator projection
432
     * Formula for transformation is taken from OS document
433
     * "A Guide to Coordinate Systems in Great Britain"
434
     *
435
     * @param float $scale scale factor on central meridian
436
     * @param float $originEasting easting of true origin
437
     * @param float $originNorthing northing of true origin
438
     * @param float $originLat latitude of true origin
439
     * @param float $originLong longitude of true origin
440
     * @return array
441
     */
442 5
    public function toTransverseMercatorEastingNorthing(
443
        $scale,
444
        $originEasting,
445
        $originNorthing,
446
        $originLat,
447
        $originLong
448
    ): array {
449
450 5
        $originLat = deg2rad($originLat);
451 5
        $originLong = deg2rad($originLong);
452
453 5
        $lat = deg2rad($this->lat);
454 5
        $sinLat = sin($lat);
455 5
        $cosLat = cos($lat);
456 5
        $tanLat = tan($lat);
457 5
        $tanLatSq = pow($tanLat, 2);
458 5
        $long = deg2rad($this->lng);
459
460 5
        $n = ($this->refEll->getMaj() - $this->refEll->getMin()) / ($this->refEll->getMaj() + $this->refEll->getMin());
461 5
        $nSq = pow($n, 2);
462 5
        $nCu = pow($n, 3);
463
464 5
        $v = $this->refEll->getMaj() * $scale * pow(1 - $this->refEll->getEcc() * pow($sinLat, 2), -0.5);
465 5
        $p = $this->refEll->getMaj() * $scale * (1 - $this->refEll->getEcc()) * pow(1 - $this->refEll->getEcc() * pow($sinLat, 2), -1.5);
466 5
        $hSq = (($v / $p) - 1);
467
468 5
        $latPlusOrigin = $lat + $originLat;
469 5
        $latMinusOrigin = $lat - $originLat;
470
471 5
        $longMinusOrigin = $long - $originLong;
472
473 5
        $M = $this->refEll->getMin() * $scale
474 5
            * ((1 + $n + 1.25 * ($nSq + $nCu)) * $latMinusOrigin
475 5
                - (3 * ($n + $nSq) + 2.625 * $nCu) * sin($latMinusOrigin) * cos($latPlusOrigin)
476 5
                + 1.875 * ($nSq + $nCu) * sin(2 * $latMinusOrigin) * cos(2 * $latPlusOrigin)
477 5
                - (35 / 24 * $nCu * sin(3 * $latMinusOrigin) * cos(3 * $latPlusOrigin)));
478
479 5
        $I = $M + $originNorthing;
480 5
        $II = $v / 2 * $sinLat * $cosLat;
481 5
        $III = $v / 24 * $sinLat * pow($cosLat, 3) * (5 - $tanLatSq + 9 * $hSq);
482 5
        $IIIA = $v / 720 * $sinLat * pow($cosLat, 5) * (61 - 58 * $tanLatSq + pow($tanLatSq, 2));
483 5
        $IV = $v * $cosLat;
484 5
        $V = $v / 6 * pow($cosLat, 3) * ($v / $p - $tanLatSq);
485 5
        $VI = $v / 120 * pow($cosLat, 5) * (5 - 18 * $tanLatSq + pow($tanLatSq, 2) + 14 * $hSq - 58 * $tanLatSq * $hSq);
486
487 5
        $E = (int)round($originEasting + $IV * $longMinusOrigin + $V * pow($longMinusOrigin, 3) + $VI * pow($longMinusOrigin, 5));
488 5
        $N = (int)round($I + $II * pow($longMinusOrigin, 2) + $III * pow($longMinusOrigin, 4) + $IIIA * pow($longMinusOrigin, 6));
489
490 5
        return array('E' => $E, 'N' => $N);
491
    }
492
}
493