Completed
Push — master ( 89e8f9...491935 )
by Doug
08:18
created

LatLng::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 4
crap 1
1
<?php
2
/**
3
 * PHPCoord
4
 * @package PHPCoord
5
 * @author Jonathan Stott
6
 * @author Doug Wright
7
 */
8
namespace PHPCoord;
9
10
/**
11
 * Latitude/Longitude reference
12
 * @author Jonathan Stott
13
 * @author Doug Wright
14
 * @package PHPCoord
15
 */
16
class LatLng
17
{
18
19
    /**
20
     * Latitude
21
     * @var float
22
     */
23
    protected $lat;
24
25
    /**
26
     * Longitude
27
     * @var float
28
     */
29
    protected $lng;
30
31
    /**
32
     * Height
33
     * @var float
34
     */
35
    protected $h;
36
37
    /**
38
     * Reference ellipsoid the co-ordinates are from
39
     * @var RefEll
40
     */
41
    protected $refEll;
42
43
    /**
44
     * Create a new LatLng object from the given latitude and longitude
45
     * @param float $lat
46
     * @param float $lng
47
     * @param float $height
48
     * @param RefEll $refEll
49
     */
50 30
    public function __construct($lat, $lng, $height, RefEll $refEll)
51
    {
52 30
        $this->lat = round($lat, 5);
53 30
        $this->lng = round($lng, 5);
54 30
        $this->h = round($height);
55 30
        $this->refEll = $refEll;
56
    }
57
58
    /**
59
     * Return a string representation of this LatLng object
60
     * @return string
61
     */
62 13
    public function __toString()
63
    {
64 13
        return "({$this->lat}, {$this->lng})";
65
    }
66
67
    /**
68
     * @return float
69
     */
70 15
    public function getLat()
71
    {
72 15
        return $this->lat;
73
    }
74
75
    /**
76
     * @param float $lat
77
     */
78
    public function setLat($lat)
79
    {
80
        $this->lat = $lat;
81
    }
82
83
    /**
84
     * @return float
85
     */
86 15
    public function getLng()
87
    {
88 15
        return $this->lng;
89
    }
90
91
    /**
92
     * @param float $lng
93
     */
94
    public function setLng($lng)
95
    {
96
        $this->lng = $lng;
97
    }
98
99
    /**
100
     * @return float
101
     */
102 9
    public function getH()
103
    {
104 9
        return $this->h;
105
    }
106
107
    /**
108
     * @param float $h
109
     */
110
    public function setH($h)
111
    {
112
        $this->h = $h;
113
    }
114
115
116
    /**
117
     * @return RefEll
118
     */
119 9
    public function getRefEll()
120
    {
121 9
        return $this->refEll;
122
    }
123
124
    /**
125
     * @param RefEll $refEll
126
     */
127
    public function setRefEll(RefEll $refEll)
128
    {
129
        $this->refEll = $refEll;
130
    }
131
132
133
134
    /**
135
     * Calculate the surface distance between this LatLng object and the one
136
     * passed in as a parameter.
137
     *
138
     * @param LatLng $to a LatLng object to measure the surface distance to
139
     * @return float
140
     */
141 2
    public function distance(LatLng $to)
142
    {
143 2
        if ($this->refEll != $to->refEll) {
144 1
            throw new \RuntimeException('Source and destination co-ordinates are not using the same ellipsoid');
145
        }
146
147
        //Mean radius definition from taken from Wikipedia
148 1
        $er = ((2 * $this->refEll->getMaj()) + $this->refEll->getMin()) / 3;
149
150 1
        $latFrom = deg2rad($this->lat);
151 1
        $latTo = deg2rad($to->lat);
152 1
        $lngFrom = deg2rad($this->lng);
153 1
        $lngTo = deg2rad($to->lng);
154
155 1
        $d = acos(sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lngTo - $lngFrom)) * $er;
156
157 1
        return round($d, 5);
158
    }
159
160
161
    /**
162
     * Convert this LatLng object to OSGB36 datum.
163
     * Reference values for transformation are taken from OS document
164
     * "A Guide to Coordinate Systems in Great Britain"
165
     * @return void
166
     */
167 4
    public function toOSGB36()
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...
168
    {
169 4
        if ($this->refEll == RefEll::airy1830()) {
170 2
            return;
171
        }
172
173 2
        $this->toWGS84();
174
175 1
        $tx = -446.448;
176 1
        $ty = 125.157;
177 1
        $tz = -542.060;
178 1
        $s = 0.0000204894;
179 1
        $rx = deg2rad(-0.1502 / 3600);
180 1
        $ry = deg2rad(-0.2470 / 3600);
181 1
        $rz = deg2rad(-0.8421 / 3600);
182
183 1
        $this->transformDatum(RefEll::airy1830(), $tx, $ty, $tz, $s, $rx, $ry, $rz);
184
    }
185
186
    /**
187
     * Convert this LatLng object to ED50 datum.
188
     * Reference values for transformation are taken from http://www.globalmapper.com/helpv9/datum_list.htm
189
     * @return void
190
     */
191 1
    public function toED50()
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...
192
    {
193 1
        if ($this->refEll == RefEll::international1924()) {
194
            return;
195
        }
196
197 1
        $this->toWGS84();
198
199 1
        $tx = 87;
200 1
        $ty = 98;
201 1
        $tz = 121;
202 1
        $s = 0;
203 1
        $rx = deg2rad(0);
204 1
        $ry = deg2rad(0);
205 1
        $rz = deg2rad(0);
206
207 1
        $this->transformDatum(RefEll::international1924(), $tx, $ty, $tz, $s, $rx, $ry, $rz);
208
    }
209
210
    /**
211
     * Convert this LatLng object to NAD27 datum.
212
     * Reference values for transformation are taken from Wikipedia
213
     * @return void
214
     */
215 1
    public function toNAD27()
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...
216
    {
217 1
        if ($this->refEll == RefEll::clarke1866()) {
218
            return;
219
        }
220
221 1
        $this->toWGS84();
222
223 1
        $tx = 8;
224 1
        $ty = -160;
225 1
        $tz = -176;
226 1
        $s = 0;
227 1
        $rx = deg2rad(0);
228 1
        $ry = deg2rad(0);
229 1
        $rz = deg2rad(0);
230
231 1
        $this->transformDatum(RefEll::clarke1866(), $tx, $ty, $tz, $s, $rx, $ry, $rz);
232
    }
233
234
    /**
235
     * Convert this LatLng object to Ireland 1975 datum.
236
     * Reference values for transformation are taken from OSI document
237
     * "Making maps compatible with GPS"
238
     * @return void
239
     */
240 1
    public function toIreland1975()
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...
241
    {
242 1
        if ($this->refEll == RefEll::airyModified()) {
243
            return;
244
        }
245
246 1
        $this->toWGS84();
247
248 1
        $tx = -482.530;
249 1
        $ty = 130.596;
250 1
        $tz = -564.557;
251 1
        $s = -0.00000815;
252 1
        $rx = deg2rad(-1.042 / 3600);
253 1
        $ry = deg2rad(-0.214 / 3600);
254 1
        $rz = deg2rad(-0.631 / 3600);
255
256 1
        $this->transformDatum(RefEll::airyModified(), $tx, $ty, $tz, $s, $rx, $ry, $rz);
257
    }
258
259
    /**
260
     * Convert this LatLng object to WGS84 datum
261
     * @return void
262
     */
263 14
    public function toWGS84() {
264
265 14
        switch ($this->refEll) {
266
267 14
            case RefEll::wgs84():
268 8
                return; //do nothing
269
270 7
            case RefEll::airy1830(): // values from OSGB document "A Guide to Coordinate Systems in Great Britain"
271 2
                $tx = 446.448;
272 2
                $ty = -125.157;
273 2
                $tz = 542.060;
274 2
                $s = -0.0000204894;
275 2
                $rx = deg2rad(0.1502 / 3600);
276 2
                $ry = deg2rad(0.2470 / 3600);
277 2
                $rz = deg2rad(0.8421 / 3600);
278 2
                break;
279
280 5
            case RefEll::airyModified(): // values from OSI document "Making maps compatible with GPS"
281 1
                $tx = 482.530;
282 1
                $ty = -130.596;
283 1
                $tz = 564.557;
284 1
                $s = 0.00000815;
285 1
                $rx = deg2rad(1.042 / 3600);
286 1
                $ry = deg2rad(0.214 / 3600);
287 1
                $rz = deg2rad(0.631 / 3600);
288 1
                break;
289
290 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...
291 1
                $tx = -8;
292 1
                $ty = 160;
293 1
                $tz = 176;
294 1
                $s = 0;
295 1
                $rx = deg2rad(0);
296 1
                $ry = deg2rad(0);
297 1
                $rz = deg2rad(0);
298 1
                break;
299
300 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...
301 1
                $tx = -87;
302 1
                $ty = -98;
303 1
                $tz = -121;
304 1
                $s = 0;
305 1
                $rx = deg2rad(0);
306 1
                $ry = deg2rad(0);
307 1
                $rz = deg2rad(0);
308 1
                break;
309
310 2
            case RefEll::bessel1841(): // assumes Germany, values from Wikipedia
311
                $tx = 582;
312
                $ty = -105;
313
                $tz = -414;
314
                $s = 0.0000083;
315
                $rx = deg2rad(1.04 / 3600);
316
                $ry = deg2rad(0.35 / 3600);
317
                $rz = deg2rad(-3.08 / 3600);
318
                break;
319
320
            default:
321 2
                throw new \RuntimeException('Transform parameters not known for this ellipsoid');
322
        }
323
324 5
        $this->transformDatum(RefEll::wgs84(), $tx, $ty, $tz, $s, $rx, $ry, $rz);
325
    }
326
327
    /**
328
     * Transform co-ordinates from one datum to another using a Helmert transformation
329
     * @param RefEll $toRefEll
330
     * @param float $tranX
331
     * @param float $tranY
332
     * @param float $tranZ
333
     * @param float $scale
334
     * @param float $rotX  rotation about x-axis in seconds
335
     * @param float $rotY  rotation about y-axis in seconds
336
     * @param float $rotZ  rotation about z-axis in seconds
337
     * @return mixed
338
     */
339 15
    public function transformDatum(RefEll $toRefEll, $tranX, $tranY, $tranZ, $scale, $rotX, $rotY, $rotZ)
340
    {
341
342 15
        if ($this->refEll == $toRefEll) {
343 6
            return;
344
        }
345
346 9
        $cartesian = Cartesian::fromLatLong($this);
347 9
        $cartesian->transformDatum($toRefEll, $tranX, $tranY, $tranZ, $scale, $rotX, $rotY, $rotZ);
348 9
        $newLatLng = $cartesian->toLatitudeLongitude();
349
350 9
        $this->lat = $newLatLng->getLat();
351 9
        $this->lng = $newLatLng->getLng();
352 9
        $this->h = $newLatLng->getH();
353 9
        $this->refEll = $newLatLng->getRefEll();
354
    }
355
356
357
    /**
358
     * Convert this LatLng object into an OSGB grid reference. Note that this
359
     * function does not take into account the bounds of the OSGB grid -
360
     * beyond the bounds of the OSGB grid, the resulting OSRef object has no
361
     * meaning
362
     *
363
     * Reference values for transformation are taken from OS document
364
     * "A Guide to Coordinate Systems in Great Britain"
365
     *
366
     * @return OSRef
367
     */
368 3 View Code Duplication
    public function toOSRef()
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...
369
    {
370 3
        $this->toOSGB36();
371
372 2
        $OSGB = new OSRef(0, 0); //dummy to get reference data
373 2
        $scale = $OSGB->getScaleFactor();
374 2
        $N0 = $OSGB->getOriginNorthing();
375 2
        $E0 = $OSGB->getOriginEasting();
376 2
        $phi0 = $OSGB->getOriginLatitude();
377 2
        $lambda0 = $OSGB->getOriginLongitude();
378
379 2
        $coords = $this->toTransverseMercatorEastingNorthing($scale, $E0, $N0, $phi0, $lambda0);
380
381 2
        return new OSRef(round($coords['E']), round($coords['N']), $this->h);
382
    }
383
384
    /**
385
     * Convert this LatLng object into an ITM grid reference
386
     *
387
     * @return ITMRef
388
     */
389 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...
390
    {
391
        $this->toWGS84();
392
393
        $ITM = new ITMRef(0, 0); //dummy to get reference data
394
        $scale = $ITM->getScaleFactor();
395
        $N0 = $ITM->getOriginNorthing();
396
        $E0 = $ITM->getOriginEasting();
397
        $phi0 = $ITM->getOriginLatitude();
398
        $lambda0 = $ITM->getOriginLongitude();
399
400
        $coords = $this->toTransverseMercatorEastingNorthing($scale, $E0, $N0, $phi0, $lambda0);
401
402
        return new ITMRef(round($coords['E']), round($coords['N']), $this->h);
403
    }
404
405
406
    /**
407
     * Convert a WGS84 latitude and longitude to an UTM reference
408
     *
409
     * Reference values for transformation are taken from OS document
410
     * "A Guide to Coordinate Systems in Great Britain"
411
     * @return UTMRef
412
     */
413 4
    public function toUTMRef()
414
    {
415 4
        $this->toWGS84();
416
417 4
        $longitudeZone = (int)(($this->lng + 180) / 6) + 1;
418
419
        // Special zone for Norway
420 4
        if ($this->lat >= 56 && $this->lat < 64 && $this->lng >= 3 && $this->lng < 12) {
421
            $longitudeZone = 32;
422 4
        } elseif ($this->lat >= 72 && $this->lat < 84) { // Special zones for Svalbard
423
            if ($this->lng >= 0 && $this->lng < 9) {
424
                $longitudeZone = 31;
425
            } elseif ($this->lng >= 9 && $this->lng < 21) {
426
                $longitudeZone = 33;
427
            } elseif ($this->lng >= 21 && $this->lng < 33) {
428
                $longitudeZone = 35;
429
            } elseif ($this->lng >= 33 && $this->lng < 42) {
430
                $longitudeZone = 37;
431
            }
432
        }
433
434 4
        $UTMZone = $this->getUTMLatitudeZoneLetter($this->lat);
435
436 2
        $UTM = new UTMRef(0, 0, 0, $UTMZone, $longitudeZone); //dummy to get reference data
437 2
        $scale = $UTM->getScaleFactor();
438 2
        $N0 = $UTM->getOriginNorthing();
439 2
        $E0 = $UTM->getOriginEasting();
440 2
        $phi0 = $UTM->getOriginLatitude();
441 2
        $lambda0 = $UTM->getOriginLongitude();
442
443 2
        $coords = $this->toTransverseMercatorEastingNorthing($scale, $E0, $N0, $phi0, $lambda0);
444
445 2
        if ($this->lat < 0) {
446 1
            $coords['N'] += 10000000;
447
        }
448
449 2
        return new UTMRef(round($coords['E']), round($coords['N']), $this->h, $UTMZone, $longitudeZone);
450
    }
451
452
    /**
453
     * Work out the UTM latitude zone from the latitude
454
     * @param float $latitude
455
     * @return string
456
     */
457 4
    private function getUTMLatitudeZoneLetter($latitude)
458
    {
459
460 4
        if ($latitude < -80 || $latitude > 84) {
461 2
            throw new \OutOfRangeException('UTM zones do not apply in polar regions');
462
        }
463
464 2
        $zones = "CDEFGHJKLMNPQRSTUVWXX";
465 2
        $zoneIndex = (int)(($latitude + 80) / 8);
466 2
        return $zones[$zoneIndex];
467
    }
468
469
470
    /**
471
     * Convert a latitude and longitude to easting and northing using a Transverse Mercator projection
472
     * Formula for transformation is taken from OS document
473
     * "A Guide to Coordinate Systems in Great Britain"
474
     *
475
     * @param float $scale scale factor on central meridian
476
     * @param float $originEasting easting of true origin
477
     * @param float $originNorthing northing of true origin
478
     * @param float $originLat latitude of true origin
479
     * @param float $originLong longitude of true origin
480
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,double>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
481
     */
482 4
    public function toTransverseMercatorEastingNorthing(
483
        $scale,
484
        $originEasting,
485
        $originNorthing,
486
        $originLat,
487
        $originLong
488
    ) {
489
490 4
        $originLat = deg2rad($originLat);
491 4
        $originLong = deg2rad($originLong);
492
493 4
        $lat = deg2rad($this->lat);
494 4
        $sinLat = sin($lat);
495 4
        $cosLat = cos($lat);
496 4
        $tanLat = tan($lat);
497 4
        $tanLatSq = pow($tanLat, 2);
498 4
        $long = deg2rad($this->lng);
499
500 4
        $n = ($this->refEll->getMaj() - $this->refEll->getMin()) / ($this->refEll->getMaj() + $this->refEll->getMin());
501 4
        $nSq = pow($n, 2);
502 4
        $nCu = pow($n, 3);
503
504 4
        $v = $this->refEll->getMaj() * $scale * pow(1 - $this->refEll->getEcc() * pow($sinLat, 2), -0.5);
505 4
        $p = $this->refEll->getMaj() * $scale * (1 - $this->refEll->getEcc()) * pow(1 - $this->refEll->getEcc() * pow($sinLat, 2), -1.5);
506 4
        $hSq = (($v / $p) - 1);
507
508 4
        $latPlusOrigin = $lat + $originLat;
509 4
        $latMinusOrigin = $lat - $originLat;
510
511 4
        $longMinusOrigin = $long - $originLong;
512
513 4
        $M = $this->refEll->getMin() * $scale
514 4
            * ((1 + $n + 1.25 * ($nSq + $nCu)) * $latMinusOrigin
515 4
                - (3 * ($n + $nSq) + 2.625 * $nCu) * sin($latMinusOrigin) * cos($latPlusOrigin)
516 4
                + 1.875 * ($nSq + $nCu) * sin(2 * $latMinusOrigin) * cos(2 * $latPlusOrigin)
517 4
                - (35 / 24 * $nCu * sin(3 * $latMinusOrigin) * cos(3 * $latPlusOrigin)));
518
519 4
        $I = $M + $originNorthing;
520 4
        $II = $v / 2 * $sinLat * $cosLat;
521 4
        $III = $v / 24 * $sinLat * pow($cosLat, 3) * (5 - $tanLatSq + 9 * $hSq);
522 4
        $IIIA = $v / 720 * $sinLat * pow($cosLat, 5) * (61 - 58 * $tanLatSq + pow($tanLatSq, 2));
523 4
        $IV = $v * $cosLat;
524 4
        $V = $v / 6 * pow($cosLat, 3) * ($v / $p - $tanLatSq);
525 4
        $VI = $v / 120 * pow($cosLat, 5) * (5 - 18 * $tanLatSq + pow($tanLatSq, 2) + 14 * $hSq - 58 * $tanLatSq * $hSq);
526
527 4
        $E = $originEasting + $IV * $longMinusOrigin + $V * pow($longMinusOrigin, 3) + $VI * pow($longMinusOrigin, 5);
528 4
        $N = $I + $II * pow($longMinusOrigin, 2) + $III * pow($longMinusOrigin, 4) + $IIIA * pow($longMinusOrigin, 6);
529
530 4
        return array('E' => $E, 'N' => $N);
531
    }
532
}
533