Completed
Push — master ( 3c4a39...ba1925 )
by Doug
02:35
created

LatLng::transformDatum()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 0 Features 1
Metric Value
c 8
b 0
f 1
dl 0
loc 12
rs 9.4286
cc 1
eloc 8
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
    public function __construct($lat, $lng, $height, RefEll $refEll)
51
    {
52
        $this->lat = round($lat, 5);
53
        $this->lng = round($lng, 5);
54
        $this->h = round($height);
55
        $this->refEll = $refEll;
56
    }
57
58
    /**
59
     * Return a string representation of this LatLng object
60
     * @return string
61
     */
62
    public function __toString()
63
    {
64
        return "({$this->lat}, {$this->lng})";
65
    }
66
67
    /**
68
     * @return float
69
     */
70
    public function getLat()
71
    {
72
        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
    public function getLng()
87
    {
88
        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
    public function getH()
103
    {
104
        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
    public function getRefEll()
120
    {
121
        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
    public function distance(LatLng $to)
142
    {
143
144
        if ($this->refEll != $to->refEll) {
145
            trigger_error('Source and destination co-ordinates are not using the same ellipsoid', E_USER_WARNING);
146
        }
147
148
        //Mean radius definition from taken from Wikipedia
149
        $er = ((2 * $this->refEll->getMaj()) + $this->refEll->getMin()) / 3;
150
151
        $latFrom = deg2rad($this->lat);
152
        $latTo = deg2rad($to->lat);
153
        $lngFrom = deg2rad($this->lng);
154
        $lngTo = deg2rad($to->lng);
155
156
        $d = acos(sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lngTo - $lngFrom)) * $er;
157
158
        return round($d, 5);
159
    }
160
161
162
    /**
163
     * Convert this LatLng object from OSGB36 datum to WGS84 datum.
164
     * Reference values for transformation are taken from OS document
165
     * "A Guide to Coordinate Systems in Great Britain"
166
     * @return void
167
     */
168 View Code Duplication
    public function OSGB36ToWGS84()
1 ignored issue
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...
169
    {
170
171
        if ($this->refEll != RefEll::airy1830()) {
172
            trigger_error('Current co-ordinates are not using the Airy ellipsoid', E_USER_WARNING);
173
        }
174
175
        $wgs84 = RefEll::wgs84();
176
177
        $tx = 446.448;
178
        $ty = -125.157;
179
        $tz = 542.060;
180
        $s = -0.0000204894;
181
        $rx = deg2rad(0.1502 / 3600);
182
        $ry = deg2rad(0.2470 / 3600);
183
        $rz = deg2rad(0.8421 / 3600);
184
185
        $this->transformDatum($wgs84, $tx, $ty, $tz, $s, $rx, $ry, $rz);
186
    }
187
188
189
    /**
190
     * Convert this LatLng object from WGS84 datum to OSGB36 datum.
191
     * Reference values for transformation are taken from OS document
192
     * "A Guide to Coordinate Systems in Great Britain"
193
     * @return void
194
     */
195 View Code Duplication
    public function WGS84ToOSGB36()
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...
196
    {
197
198
        if ($this->refEll != RefEll::wgs84()) {
199
            trigger_error('Current co-ordinates are not using the WGS84 ellipsoid', E_USER_WARNING);
200
        }
201
202
        $airy1830 = RefEll::airy1830();
203
204
        $tx = -446.448;
205
        $ty = 125.157;
206
        $tz = -542.060;
207
        $s = 0.0000204894;
208
        $rx = deg2rad(-0.1502 / 3600);
209
        $ry = deg2rad(-0.2470 / 3600);
210
        $rz = deg2rad(-0.8421 / 3600);
211
212
        $this->transformDatum($airy1830, $tx, $ty, $tz, $s, $rx, $ry, $rz);
213
    }
214
215
    /**
216
     * Convert this LatLng object from WGS84 datum to ED50 datum.
217
     * Reference values for transformation are taken from http://www.globalmapper.com/helpv9/datum_list.htm
218
     * @return void
219
     */
220 View Code Duplication
    public function WGS84ToED50()
1 ignored issue
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...
221
    {
222
223
        if ($this->refEll != RefEll::wgs84()) {
224
            trigger_error('Current co-ordinates are not using the WGS84 ellipsoid', E_USER_WARNING);
225
        }
226
227
        $heyford1924 = RefEll::heyford1924();
228
229
        $tx = 87;
230
        $ty = 98;
231
        $tz = 121;
232
        $s = 0;
233
        $rx = deg2rad(0);
234
        $ry = deg2rad(0);
235
        $rz = deg2rad(0);
236
237
        $this->transformDatum($heyford1924, $tx, $ty, $tz, $s, $rx, $ry, $rz);
238
    }
239
240
    /**
241
     * Convert this LatLng object from ED50 datum to WGS84 datum.
242
     * Reference values for transformation are taken from http://www.globalmapper.com/helpv9/datum_list.htm
243
     * @return void
244
     */
245 View Code Duplication
    public function ED50ToWGS84()
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...
246
    {
247
248
        if ($this->refEll != RefEll::heyford1924()) {
249
            trigger_error('Current co-ordinates are not using the Heyford ellipsoid', E_USER_WARNING);
250
        }
251
252
        $wgs84 = RefEll::wgs84();
253
254
        $tx = -87;
255
        $ty = -98;
256
        $tz = -121;
257
        $s = 0;
258
        $rx = deg2rad(0);
259
        $ry = deg2rad(0);
260
        $rz = deg2rad(0);
261
262
        $this->transformDatum($wgs84, $tx, $ty, $tz, $s, $rx, $ry, $rz);
263
    }
264
265
    /**
266
     * Convert this LatLng object from WGS84 datum to NAD27 datum.
267
     * Reference values for transformation are taken from Wikipedia
268
     * @return void
269
     */
270 View Code Duplication
    public function WGS84ToNAD27()
1 ignored issue
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...
271
    {
272
273
        if ($this->refEll != RefEll::wgs84()) {
274
            trigger_error('Current co-ordinates are not using the WGS84 ellipsoid', E_USER_WARNING);
275
        }
276
277
        $clarke1866 = RefEll::clarke1866();
278
279
        $tx = 8;
280
        $ty = -160;
281
        $tz = -176;
282
        $s = 0;
283
        $rx = deg2rad(0);
284
        $ry = deg2rad(0);
285
        $rz = deg2rad(0);
286
287
        $this->transformDatum($clarke1866, $tx, $ty, $tz, $s, $rx, $ry, $rz);
288
    }
289
290
    /**
291
     * Convert this LatLng object from NAD27 datum to WGS84 datum.
292
     * Reference values for transformation are taken from Wikipedia
293
     * @return void
294
     */
295 View Code Duplication
    public function NAD27ToWGS84()
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...
296
    {
297
298
        if ($this->refEll != RefEll::clarke1866()) {
299
            trigger_error('Current co-ordinates are not using the Clarke ellipsoid', E_USER_WARNING);
300
        }
301
302
        $wgs84 = RefEll::wgs84();
303
304
        $tx = -8;
305
        $ty = 160;
306
        $tz = 176;
307
        $s = 0;
308
        $rx = deg2rad(0);
309
        $ry = deg2rad(0);
310
        $rz = deg2rad(0);
311
312
        $this->transformDatum($wgs84, $tx, $ty, $tz, $s, $rx, $ry, $rz);
313
    }
314
315
    /**
316
     * Transform co-ordinates from one datum to another using a Helmert transformation
317
     * @param RefEll $toRefEll
318
     * @param float $tranX
319
     * @param float $tranY
320
     * @param float $tranZ
321
     * @param float $scale
322
     * @param float $rotX  rotation about x-axis in seconds
323
     * @param float $rotY  rotation about y-axis in seconds
324
     * @param float $rotZ  rotation about z-axis in seconds
325
     * @return mixed
326
     */
327
    public function transformDatum(RefEll $toRefEll, $tranX, $tranY, $tranZ, $scale, $rotX, $rotY, $rotZ)
328
    {
329
330
        $cartesian = Cartesian::fromLatLong($this);
331
        $cartesian->transformDatum($toRefEll, $tranX, $tranY, $tranZ, $scale, $rotX, $rotY, $rotZ);
332
        $newLatLng = $cartesian->toLatitudeLongitude();
333
334
        $this->lat = $newLatLng->getLat();
335
        $this->lng = $newLatLng->getLng();
336
        $this->h = $newLatLng->getH();
337
        $this->refEll = $newLatLng->getRefEll();
338
    }
339
340
341
    /**
342
     * Convert this LatLng object into an OSGB grid reference. Note that this
343
     * function does not take into account the bounds of the OSGB grid -
344
     * beyond the bounds of the OSGB grid, the resulting OSRef object has no
345
     * meaning
346
     *
347
     * Reference values for transformation are taken from OS document
348
     * "A Guide to Coordinate Systems in Great Britain"
349
     *
350
     * @return OSRef
351
     */
352
    public function toOSRef()
353
    {
354
355
        if ($this->refEll != RefEll::airy1830()) {
356
            trigger_error('Current co-ordinates are in a non-OSGB datum', E_USER_WARNING);
357
        }
358
359
        $OSGB = new OSRef(0, 0); //dummy to get reference data
360
        $scale = $OSGB->getScaleFactor();
361
        $N0 = $OSGB->getOriginNorthing();
362
        $E0 = $OSGB->getOriginEasting();
363
        $phi0 = $OSGB->getOriginLatitude();
364
        $lambda0 = $OSGB->getOriginLongitude();
365
366
        $coords = $this->toTransverseMercatorEastingNorthing($scale, $E0, $N0, $phi0, $lambda0);
367
368
        return new OSRef(round($coords['E']), round($coords['N']));
369
    }
370
371
372
    /**
373
     * Convert a WGS84 latitude and longitude to an UTM reference
374
     *
375
     * Reference values for transformation are taken from OS document
376
     * "A Guide to Coordinate Systems in Great Britain"
377
     * @return UTMRef
378
     */
379
    public function toUTMRef()
380
    {
381
382
        if ($this->refEll != RefEll::wgs84()) {
383
            trigger_error('Current co-ordinates are in a non-WGS84 datum', E_USER_WARNING);
384
        }
385
386
        $longitudeZone = (int)(($this->lng + 180) / 6) + 1;
387
388
        // Special zone for Norway
389 View Code Duplication
        if ($this->lat >= 56 && $this->lat < 64 && $this->lng >= 3 && $this->lng < 12) {
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...
390
            $longitudeZone = 32;
391
        }
392
393
        // Special zones for Svalbard
394
        if ($this->lat >= 72 && $this->lat < 84) {
395
            if ($this->lng >= 0 && $this->lng < 9) {
396
                $longitudeZone = 31;
397
            } else {
398
                if ($this->lng >= 9 && $this->lng < 21) {
399
                    $longitudeZone = 33;
400 View Code Duplication
                } else {
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...
401
                    if ($this->lng >= 21 && $this->lng < 33) {
402
                        $longitudeZone = 35;
403
                    } else {
404
                        if ($this->lng >= 33 && $this->lng < 42) {
405
                            $longitudeZone = 37;
406
                        }
407
                    }
408
                }
409
            }
410
        }
411
412
        $UTMZone = $this->getUTMLatitudeZoneLetter($this->lat);
413
414
        $UTM = new UTMRef(0, 0, $UTMZone, $longitudeZone); //dummy to get reference data
415
        $scale = $UTM->getScaleFactor();
416
        $N0 = $UTM->getOriginNorthing();
417
        $E0 = $UTM->getOriginEasting();
418
        $phi0 = $UTM->getOriginLatitude();
419
        $lambda0 = $UTM->getOriginLongitude();
420
421
        $coords = $this->toTransverseMercatorEastingNorthing($scale, $E0, $N0, $phi0, $lambda0);
422
423
        if ($this->lat < 0) {
424
            $coords['N'] += 10000000;
425
        }
426
427
        return new UTMRef(round($coords['E']), round($coords['N']), $UTMZone, $longitudeZone);
428
    }
429
430
    /**
431
     * Work out the UTM latitude zone from the latitude
432
     * @param float $latitude
433
     * @return string
434
     */
435
    private function getUTMLatitudeZoneLetter($latitude)
436
    {
437
438
        if ($latitude < -80 || $latitude > 84) {
439
            throw new \OutOfRangeException('UTM zones do not apply in polar regions');
440
        }
441
442
        $zones = "CDEFGHJKLMNPQRSTUVWXX";
443
        $zoneIndex = (int)(($latitude + 80) / 8);
444
        return $zones[$zoneIndex];
445
    }
446
447
448
    /**
449
     * Convert a latitude and longitude to easting and northing using a Transverse Mercator projection
450
     * Formula for transformation is taken from OS document
451
     * "A Guide to Coordinate Systems in Great Britain"
452
     *
453
     * @param float $scale scale factor on central meridian
454
     * @param float $originEasting easting of true origin
455
     * @param float $originNorthing northing of true origin
456
     * @param float $originLat latitude of true origin
457
     * @param float $originLong longitude of true origin
458
     * @return array
459
     */
460
    public function toTransverseMercatorEastingNorthing(
461
        $scale,
462
        $originEasting,
463
        $originNorthing,
464
        $originLat,
465
        $originLong
466
    ) {
467
468
        $originLat = deg2rad($originLat);
469
        $originLong = deg2rad($originLong);
470
471
        $lat = deg2rad($this->lat);
472
        $sinLat = sin($lat);
473
        $cosLat = cos($lat);
474
        $tanLat = tan($lat);
475
        $tanLatSq = pow($tanLat, 2);
476
        $long = deg2rad($this->lng);
477
478
        $n = ($this->refEll->getMaj() - $this->refEll->getMin()) / ($this->refEll->getMaj() + $this->refEll->getMin());
479
        $nSq = pow($n, 2);
480
        $nCu = pow($n, 3);
481
482
        $v = $this->refEll->getMaj() * $scale * pow(1 - $this->refEll->getEcc() * pow($sinLat, 2), -0.5);
483
        $p = $this->refEll->getMaj() * $scale * (1 - $this->refEll->getEcc()) * pow(1 - $this->refEll->getEcc() * pow($sinLat, 2), -1.5);
484
        $hSq = (($v / $p) - 1);
485
486
        $latPlusOrigin = $lat + $originLat;
487
        $latMinusOrigin = $lat - $originLat;
488
489
        $longMinusOrigin = $long - $originLong;
490
491
        $M = $this->refEll->getMin() * $scale
492
            * ((1 + $n + 1.25 * ($nSq + $nCu)) * $latMinusOrigin
493
                - (3 * ($n + $nSq) + 2.625 * $nCu) * sin($latMinusOrigin) * cos($latPlusOrigin)
494
                + 1.875 * ($nSq + $nCu) * sin(2 * $latMinusOrigin) * cos(2 * $latPlusOrigin)
495
                - (35 / 24 * $nCu * sin(3 * $latMinusOrigin) * cos(3 * $latPlusOrigin)));
496
497
        $I = $M + $originNorthing;
498
        $II = $v / 2 * $sinLat * $cosLat;
499
        $III = $v / 24 * $sinLat * pow($cosLat, 3) * (5 - $tanLatSq + 9 * $hSq);
500
        $IIIA = $v / 720 * $sinLat * pow($cosLat, 5) * (61 - 58 * $tanLatSq + pow($tanLatSq, 2));
501
        $IV = $v * $cosLat;
502
        $V = $v / 6 * pow($cosLat, 3) * ($v / $p - $tanLatSq);
503
        $VI = $v / 120 * pow($cosLat, 5) * (5 - 18 * $tanLatSq + pow($tanLatSq, 2) + 14 * $hSq - 58 * $tanLatSq * $hSq);
504
505
        $E = $originEasting + $IV * $longMinusOrigin + $V * pow($longMinusOrigin, 3) + $VI * pow($longMinusOrigin, 5);
506
        $N = $I + $II * pow($longMinusOrigin, 2) + $III * pow($longMinusOrigin, 4) + $IIIA * pow($longMinusOrigin, 6);
507
508
        return array('E' => $E, 'N' => $N);
509
    }
510
}
511