Passed
Push — extents ( f35511...ba1d5f )
by Doug
61:44
created

NTv2Grid::getAdjustment()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 60
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 8.4953

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 47
nc 5
nop 2
dl 0
loc 60
ccs 33
cts 48
cp 0.6875
crap 8.4953
rs 8.223
c 1
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord\CoordinateOperation;
10
11
use function assert;
12
use PHPCoord\UnitOfMeasure\Angle\ArcSecond;
13
use function round;
14
use SplFileObject;
15
use function unpack;
16
use function usort;
17
18
class NTv2Grid extends GeographicGrid
19
{
20
    private const RECORD_SIZE = 16;
21
    private const FLAG_WITHIN_LIMITS = 1;
22
    private const FLAG_ON_UPPER_LATITUDE = 2;
23
    private const FLAG_ON_UPPER_LONGITUDE = 3;
24
    private const FLAG_ON_UPPER_LATITUDE_AND_LONGITUDE = 4;
25
26
    private string $integerFormatChar = 'V';
27
    private string $doubleFormatChar = 'e';
28
    private string $floatFormatChar = 'g';
29
30
    private array $subFileMetaData = [];
31
32
    public function __construct($filename)
33
    {
34
        $this->gridFile = new SplFileObject($filename);
35
        $this->storageOrder = self::STORAGE_ORDER_INCREASING_LATITUDE_INCREASING_LONGITUDE;
36
37
        $this->readHeader();
38
    }
39
40
    /**
41
     * @return ArcSecond[]
42
     */
43
    public function getValues(float $x, float $y): array
44
    {
45
        // NTv2 is longitude positive *west*
46
        $x *= -1;
47
48
        // NTv2 is in seconds, not degrees
49
        $x *= 3600;
50
        $y *= 3600;
51
52
        $gridToUse = $this->determineBestGrid($x, $y);
53
54 91
        return $gridToUse->getValues($x, $y);
55
    }
56 91
57
    private function readHeader(): void
58 91
    {
59 91
        $this->gridFile->fseek(0);
60
        $rawData = $this->gridFile->fread(11 * self::RECORD_SIZE);
61 89
        if (unpack('VNUM_OREC', $rawData, 8)['NUM_OREC'] !== 11) {
62
            $this->integerFormatChar = 'N';
63 89
            $this->doubleFormatChar = 'E';
64
            $this->floatFormatChar = 'G';
65 89
        }
66 89
67
        $data = unpack("A8/{$this->integerFormatChar}NUM_OREC/x4/A8/{$this->integerFormatChar}NUM_SREC/x4/A8/{$this->integerFormatChar}NUM_FILE/x4/A8/A8GS_TYPE/A8/A8VERSION/A8/A8SYSTEM_F/A8/A8SYSTEM_T/A8/{$this->doubleFormatChar}MAJOR_F/A8/{$this->doubleFormatChar}MINOR_F/A8/{$this->doubleFormatChar}MAJOR_T/A8/{$this->doubleFormatChar}MINOR_T", $rawData);
68 89
69
        assert($data['GS_TYPE'] === 'SECONDS');
70
71 88
        $subFileStart = 11 * self::RECORD_SIZE;
72
        for ($i = 0; $i < $data['NUM_FILE']; ++$i) {
73 88
            $this->gridFile->fseek($subFileStart);
74 88
            $subFileRawData = $this->gridFile->fread(11 * self::RECORD_SIZE);
75 88
            $subFileData = unpack("A8/A8SUB_NAME/A8/A8PARENT/A8/A8CREATED/A8/A8UPDATED/A8/{$this->doubleFormatChar}S_LAT/A8/{$this->doubleFormatChar}N_LAT/A8/{$this->doubleFormatChar}E_LONG/A8/{$this->doubleFormatChar}W_LONG/A8/{$this->doubleFormatChar}LAT_INC/A8/{$this->doubleFormatChar}LONG_INC/A8/{$this->integerFormatChar}GS_COUNT/x4", $subFileRawData);
76
            $subFileData['offsetStart'] = $subFileStart;
77
78 88
            //apply rounding to eliminate fp issues when being deserialized
79 88
            $subFileData['S_LAT'] = round($subFileData['S_LAT'], 5);
80 88
            $subFileData['N_LAT'] = round($subFileData['N_LAT'], 5);
81 88
            $subFileData['E_LONG'] = round($subFileData['E_LONG'], 5);
82 88
            $subFileData['W_LONG'] = round($subFileData['W_LONG'], 5);
83
            $this->subFileMetaData[$subFileData['SUB_NAME']] = $subFileData;
84 88
85
            $subFileStart += 11 * self::RECORD_SIZE + $subFileData['GS_COUNT'] * self::RECORD_SIZE;
86
        }
87
    }
88
89
    private function determineBestGrid(float $longitude, float $latitude): NTv2SubGrid
90 91
    {
91
        $possibleGrids = [];
92
        foreach ($this->subFileMetaData as $subFileMetaDatum) {
93 91
            if ($latitude === $subFileMetaDatum['N_LAT'] && $longitude === $subFileMetaDatum['W_LONG']) {
94
                $possibleGrids[] = [self::FLAG_ON_UPPER_LATITUDE_AND_LONGITUDE, $subFileMetaDatum];
95 91
            } elseif ($longitude === $subFileMetaDatum['W_LONG']) {
96 91
                $possibleGrids[] = [self::FLAG_ON_UPPER_LONGITUDE, $subFileMetaDatum];
97 91
            } elseif ($latitude === $subFileMetaDatum['N_LAT']) {
98
                $possibleGrids[] = [self::FLAG_ON_UPPER_LATITUDE, $subFileMetaDatum];
99 91
            } elseif ($latitude >= $subFileMetaDatum['S_LAT'] && $latitude <= $subFileMetaDatum['N_LAT'] && $longitude >= $subFileMetaDatum['E_LONG'] && $longitude <= $subFileMetaDatum['W_LONG']) {
100 91
                $possibleGrids[] = [self::FLAG_WITHIN_LIMITS, $subFileMetaDatum];
101 91
            }
102 91
        }
103 91
104 91
        usort($possibleGrids, static function ($a, $b) {
105
            return $a[0] <=> $b[0] ?: $a[1]['LAT_INC'] <=> $b[1]['LAT_INC'] ?: $a[2]['LONG_INC'] <=> $b[2]['LONG_INC'];
106 91
        });
107 91
108 91
        $gridToUse = $possibleGrids[0][1];
109 91
110 91
        return new NTv2SubGrid(
111
            $this->gridFile->getPathname(),
112
            $gridToUse['offsetStart'],
113
            $gridToUse['S_LAT'],
114
            $gridToUse['N_LAT'],
115
            $gridToUse['E_LONG'],
116
            $gridToUse['W_LONG'],
117
            $gridToUse['LAT_INC'],
118
            $gridToUse['LONG_INC'],
119
            $this->floatFormatChar
120
        );
121
    }
122
}
123