Passed
Push — master ( 99135b...c08bef )
by Doug
16:52
created

GTXGrid::getHeader()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.6284

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 16
c 1
b 0
f 0
nc 3
nop 0
dl 0
loc 20
ccs 10
cts 17
cp 0.5881
crap 3.6284
rs 9.7333
1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord\CoordinateOperation;
10
11
use PHPCoord\GeographicPoint;
12
use PHPCoord\UnitOfMeasure\Length\Metre;
13
use SplFileObject;
14
use function substr;
15
use function unpack;
16
17
/**
18
 * @see https://vdatum.noaa.gov/docs/gtx_info.html for documentation
19
 */
20
class GTXGrid extends SplFileObject
21
{
22
    private float $startLatitude;
23
    private float $startLongitude;
24
    private float $latitudeGridInterval;
25
    private float $longitudeGridInterval;
26
    private int $numberLatitudes;
27
    private int $numberLongitudes;
28
    private int $headerLength;
29
    private string $offsetDataType;
30
31 6
    public function __construct($filename)
32
    {
33 6
        parent::__construct($filename);
34
35 6
        $header = $this->getHeader();
36 6
        $this->startLatitude = $header['xlatsw'];
37 6
        $this->startLongitude = $header['xlonsw'];
38 6
        $this->latitudeGridInterval = $header['dlat'];
39 6
        $this->longitudeGridInterval = $header['dlon'];
40 6
        $this->numberLatitudes = $header['nlat'];
41 6
        $this->numberLongitudes = $header['nlon'];
42
43 6
        if ($this->startLongitude > 180) { // normalise if necessary
44
            $this->startLongitude -= 360;
45
        }
46 6
    }
47
48
    /**
49
     * Converted from NOAA FORTRAN.
50
     */
51 6
    public function getAdjustment(GeographicPoint $point): Metre
52
    {
53 6
        $latitude = $point->getLatitude()->getValue();
54 6
        $longitude = $point->getLongitude()->getValue();
55
56 6
        $latitudeIndex = (int) (string) (($latitude - $this->startLatitude) / $this->latitudeGridInterval);
57 6
        $longitudeIndex = (int) (string) (($longitude - $this->startLongitude) / $this->longitudeGridInterval);
58
59 6
        $corner0 = $this->getRecord($latitudeIndex, $longitudeIndex);
60 6
        $corner1 = $this->getRecord($latitudeIndex + 1, $longitudeIndex);
61 6
        $corner2 = $this->getRecord($latitudeIndex + 1, $longitudeIndex + 1);
62 6
        $corner3 = $this->getRecord($latitudeIndex, $longitudeIndex + 1);
63
64 6
        $dLatitude = $latitude - ($latitudeIndex * $this->latitudeGridInterval) - $this->startLatitude;
65 6
        $dLongitude = $longitude - ($longitudeIndex * $this->longitudeGridInterval) - $this->startLongitude;
66
67 6
        $t = $dLatitude / $this->latitudeGridInterval;
68 6
        $u = $dLongitude / $this->longitudeGridInterval;
69
70 6
        $offset = (1 - $t) * (1 - $u) * $corner0 + ($t) * (1 - $u) * $corner1 + ($t) * ($u) * $corner2 + (1 - $t) * ($u) * $corner3;
71
72 6
        return new Metre($offset);
73
    }
74
75 6
    public function getRecord(int $latitudeIndex, int $longitudeIndex): float
76
    {
77 6
        $offset = $this->headerLength + ($latitudeIndex * $this->numberLongitudes + $longitudeIndex) * 4;
78 6
        $this->fseek($offset);
79 6
        $rawRow = $this->fread(4);
80 6
        $data = unpack("{$this->offsetDataType}offset", $rawRow);
81
82 6
        return $data['offset'];
83
    }
84
85 6
    private function getHeader(): array
86
    {
87 6
        $this->fseek(0);
88 6
        $rawHeader = $this->fread(44);
89 6
        $ikind = substr($rawHeader, 40, 4);
90 6
        if (unpack('Nikind', $ikind)['ikind'] === 1) { // big endian
91
            $this->headerLength = 44;
92
            $this->offsetDataType = 'G';
93
            $data = unpack('Exlatsw/Exlonsw/Edlat/Edlon/Nnlat/Nnlon', $rawHeader);
94 6
        } elseif (unpack('Vikind', $ikind)['ikind'] === 1) { // little endian
95
            $this->headerLength = 44;
96
            $this->offsetDataType = 'g';
97
            $data = unpack('exlatsw/exlonsw/edlat/edlon/Vnlat/Vnlon', $rawHeader);
98
        } else { // not all files (e.g. NZ) have this endian check column, assume big endian
99 6
            $this->headerLength = 40;
100 6
            $this->offsetDataType = 'G';
101 6
            $data = unpack('Exlatsw/Exlonsw/Edlat/Edlon/Nnlat/Nnlon', $rawHeader);
102
        }
103
104 6
        return $data;
105
    }
106
}
107