Passed
Push — master ( 3e5121...8c57bf )
by Doug
27:00
created

NADCON5Grid::qterp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
nc 1
nop 4
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 1
rs 10
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\GeographicPoint;
12
use SplFileObject;
13
use UnexpectedValueException;
14
use function unpack;
15
16
class NADCON5Grid extends SplFileObject
17
{
18
    use BiquadraticInterpolation;
19
20
    private string $gridDataType;
21
22 7
    public function __construct($filename)
23
    {
24 7
        parent::__construct($filename);
25
26 7
        $header = $this->getHeader();
27 7
        $this->startX = $header['xlonsw'];
28 7
        $this->startY = $header['xlatsw'];
29 7
        $this->columnGridInterval = $header['dlon'];
30 7
        $this->rowGridInterval = $header['dlat'];
31 7
        $this->numberOfColumns = $header['nlon'];
32 7
        $this->numberOfRows = $header['nlat'];
33
34 7
        switch ($header['ikind']) {
35 7
            case 1:
36 7
                $this->gridDataType = 'G';
37 7
                break;
38
            default:
39
                throw new UnexpectedValueException("Unknown ikind: {$header['ikind']}");
40
        }
41 7
    }
42
43 7
    public function getForwardAdjustment(GeographicPoint $point): float
44
    {
45 7
        return $this->getAdjustment($point->getLatitude()->asDegrees()->getValue(), $point->getLongitude()->asDegrees()->getValue());
46
    }
47
48
    /**
49
     * Converted from NOAA FORTRAN.
50
     */
51 7
    private function getAdjustment(float $latitude, float $longitude): float
52
    {
53 7
        if ($longitude < 0) {
54 7
            $longitude += 360; // NADCON5 uses 0 = 360 = Greenwich
55
        }
56
57
        // Method:
58
        // Fit a 3x3 window over the random point. The closest
59
        // 2x2 points will surround the point. But based on which
60
        // quadrant of that 2x2 cell in which the point falls, the
61
        // 3x3 window could extend NW, NE, SW or SE from the 2x2 cell.
62
63
        // Find which column should be LEAST when fitting
64
        // a 3x3 window around $longitude.
65 7
        $diflo = ($longitude - $this->startX);
66 7
        $ratlo = $diflo / ($this->columnGridInterval / 2);
67 7
        $ilo = (int) ($ratlo) + 1;
68 7
        if ($ilo % 2 != 0) {
69
            $xIndex = ($ilo + 1) / 2 - 1;
70
        } else {
71 7
            $xIndex = ($ilo) / 2;
72
        }
73
74
        // Fix any edge overlaps
75 7
        if ($xIndex < 1) {
76
            $xIndex = 1;
77
        }
78 7
        if ($xIndex > $this->numberOfColumns - 2) {
79
            $xIndex = $this->numberOfColumns - 2;
80
        }
81
82
        // Find which row should be LEAST when fitting
83
        // a 3x3 window around $latitude.
84 7
        $difla = ($latitude - $this->startY);
85 7
        $ratla = $difla / ($this->rowGridInterval / 2);
86 7
        $ila = (int) ($ratla) + 1;
87 7
        if ($ila % 2 != 0) {
88 4
            $yIndex = ($ila + 1) / 2 - 1;
89
        } else {
90 3
            $yIndex = ($ila) / 2;
91
        }
92
93
        // Fix any edge overlaps
94 7
        if ($yIndex < 1) {
95
            $yIndex = 1;
96
        }
97 7
        if ($yIndex > $this->numberOfRows - 2) {
98
            $yIndex = $this->numberOfRows - 2;
99
        }
100
101
        // In the range of 0(westernmost) to
102
        // 2(easternmost) col, where is our
103
        // random lon value? That is, x must
104
        // be real and fall between 0 and 2.
105 7
        $x = ($longitude - $this->columnGridInterval * ($xIndex - 1) - $this->startX) / $this->columnGridInterval;
106
107
        // In the range of 0(southernmost) to
108
        // 2(northernmost) row, where is our
109
        // random lat value? That is, x must
110
        // be real and fall between 0 and 2.
111 7
        $y = ($latitude - $this->rowGridInterval * ($yIndex - 1) - $this->startY) / $this->rowGridInterval;
112
113 7
        $corners = [
114 7
            'lowerLeft' => $this->getRecord($xIndex, $yIndex),
115 7
            'lowerCentre' => $this->getRecord($xIndex + 1, $yIndex),
116 7
            'lowerRight' => $this->getRecord($xIndex + 2, $yIndex),
117 7
            'middleLeft' => $this->getRecord($xIndex, $yIndex + 1),
118 7
            'middleCentre' => $this->getRecord($xIndex + 1, $yIndex + 1),
119 7
            'middleRight' => $this->getRecord($xIndex + 2, $yIndex + 1),
120 7
            'upperLeft' => $this->getRecord($xIndex, $yIndex + 2),
121 7
            'upperCentre' => $this->getRecord($xIndex + 1, $yIndex + 2),
122 7
            'upperRight' => $this->getRecord($xIndex + 2, $yIndex + 2),
123
        ];
124
125
        //Interpolate value at lower row
126 7
        $y0 = $this->interpolateQuadratic($x, $corners['lowerLeft'], $corners['lowerCentre'], $corners['lowerRight']);
127
        //Interpolate value at middle row
128 7
        $y1 = $this->interpolateQuadratic($x, $corners['middleLeft'], $corners['middleCentre'], $corners['middleRight']);
129
        //Interpolate value at upper row
130 7
        $y2 = $this->interpolateQuadratic($x, $corners['upperLeft'], $corners['upperCentre'], $corners['upperRight']);
131
132
        //Interpolate between rows
133 7
        return $this->interpolateQuadratic($y, $y0, $y1, $y2);
134
    }
135
136 7
    public function getRecord(int $longitudeIndex, int $latitudeIndex): float
137
    {
138 7
        $startBytes = 52;
139 7
        $dataTypeBytes = $this->gridDataType === 'n' ? 2 : 4;
140 7
        $recordLength = 4 + $this->numberOfColumns * $dataTypeBytes + 4;
141
142 7
        $this->fseek($startBytes + $recordLength * ($latitudeIndex - 1));
143 7
        $rawRow = $this->fread($recordLength);
144 7
        $row = unpack("Gstartbuffer/{$this->gridDataType}{$this->numberOfColumns}lon/Gendbuffer", $rawRow);
145
146 7
        return $row['lon' . ($longitudeIndex)];
147
    }
148
149 7
    private function getHeader(): array
150
    {
151 7
        $this->fseek(0);
152 7
        $rawData = $this->fread(52);
153 7
        $data = unpack('Gstartbuffer/Exlatsw/Exlonsw/Edlat/Edlon/Nnlat/Nnlon/Nikind/Gendbuffer/', $rawData);
154
155 7
        return $data;
156
    }
157
}
158