Passed
Push — master ( 0baa7a...f13449 )
by Doug
50:51
created

GTXGrid::getRecord()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1.0002

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 13
nc 1
nop 2
dl 0
loc 19
ccs 14
cts 15
cp 0.9333
crap 1.0002
rs 9.8333
c 1
b 0
f 0
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\Exception\UnknownConversionException;
12
use PHPCoord\UnitOfMeasure\Length\Metre;
13
14
use function in_array;
15
use function substr;
16
17
/**
18
 * @see https://vdatum.noaa.gov/docs/gtx_info.html for documentation
19
 */
20
class GTXGrid extends GeographicGeoidHeightGrid
21
{
22
    use BilinearInterpolation;
23
24
    private int $headerLength;
25
    private string $shiftDataType;
26
27 9
    public function __construct(string $filename)
28
    {
29 9
        $this->gridFile = new GridFile($filename);
30 9
        $this->storageOrder = self::STORAGE_ORDER_INCREASING_LONGITUDE_INCREASING_LATIITUDE;
31
32 9
        $header = $this->getHeader();
33 9
        $this->startX = $header['xlonsw'];
34 9
        $this->startY = $header['xlatsw'];
35 9
        $this->numberOfColumns = $header['nlon'];
36 9
        $this->numberOfRows = $header['nlat'];
37 9
        $this->columnGridInterval = $header['dlon'];
38 9
        $this->rowGridInterval = $header['dlat'];
39
40 9
        if ($this->startX > 180) { // normalise if necessary
41 2
            $this->startX -= 360;
42
        }
43
    }
44
45
    /**
46
     * @return Metre[]
47
     */
48 10
    public function getValues(float $x, float $y): array
49
    {
50 10
        if ($x < $this->startX) { // normalise if necessary
51
            $x += 360;
52
        }
53
54 10
        $shift = $this->interpolate($x, $y)[0];
55
56
        // These are in millimeters for some reason... :/
57 10
        if (in_array($this->gridFile->getBasename(), ['vertconc.gtx', 'vertcone.gtx', 'vertconw.gtx'], true)) {
58 1
            $shift /= 1000;
59
        }
60
61 10
        return [new Metre($shift)];
62
    }
63
64 10
    private function getRecord(int $longitudeIndex, int $latitudeIndex): GridValues
0 ignored issues
show
Unused Code introduced by
The method getRecord() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
65
    {
66 10
        $recordId = match ($this->storageOrder) {
67 10
            self::STORAGE_ORDER_INCREASING_LATITUDE_INCREASING_LONGITUDE => $longitudeIndex * $this->numberOfRows + $latitudeIndex,
68 10
            self::STORAGE_ORDER_INCREASING_LONGITUDE_DECREASING_LATIITUDE => ($this->numberOfRows - $latitudeIndex - 1) * $this->numberOfColumns + $longitudeIndex,
69 10
            self::STORAGE_ORDER_INCREASING_LONGITUDE_INCREASING_LATIITUDE => $latitudeIndex * $this->numberOfColumns + $longitudeIndex,
70
            default => throw new UnknownConversionException('Unknown storage order for file: ' . $this->gridFile->getFilename())
71 10
        };
72
73 10
        $offset = $this->headerLength + $recordId * 4;
74 10
        $this->gridFile->fseek($offset);
75 10
        $rawRow = $this->gridFile->fread(4);
76
        /** @var array{shift: float} $data */
77 10
        $data = $this->unpack("{$this->shiftDataType}shift", $rawRow);
78
79 10
        return new GridValues(
80 10
            $longitudeIndex * $this->columnGridInterval + $this->startX,
81 10
            $latitudeIndex * $this->rowGridInterval + $this->startY,
82 10
            [$data['shift']]
83 10
        );
84
    }
85
86
    /**
87
     * @return array{xlatsw: float, xlonsw: float, dlat: float, dlon: float, nlat: int, nlon: int}
88
     */
89 9
    private function getHeader(): array
90
    {
91 9
        $this->gridFile->fseek(0);
92 9
        $rawHeader = $this->gridFile->fread(44);
93 9
        $ikind = substr($rawHeader, 40, 4);
94 9
        if ($this->unpack('Nikind', $ikind)['ikind'] === 1) { // big endian
95
            $this->headerLength = 44;
96
            $this->shiftDataType = 'G';
97
            $data = $this->unpack('Exlatsw/Exlonsw/Edlat/Edlon/Nnlat/Nnlon', $rawHeader);
98 9
        } elseif ($this->unpack('Vikind', $ikind)['ikind'] === 1) { // little endian
99 1
            $this->headerLength = 44;
100 1
            $this->shiftDataType = 'g';
101 1
            $data = $this->unpack('exlatsw/exlonsw/edlat/edlon/Vnlat/Vnlon', $rawHeader);
102
        } else { // not all files (e.g. NZ) have this endian check column, assume big endian
103 8
            $this->headerLength = 40;
104 8
            $this->shiftDataType = 'G';
105 8
            $data = $this->unpack('Exlatsw/Exlonsw/Edlat/Edlon/Nnlat/Nnlon', $rawHeader);
106
        }
107
108
        /** @var array{xlatsw: float, xlonsw: float, dlat: float, dlon: float, nlat: int, nlon: int} */
109 9
        return $data;
110
    }
111
}
112