Passed
Push — master ( fd93b5...48e916 )
by Doug
40:26 queued 29:39
created

NTv2Grid::determineBestGrid()   C

Complexity

Conditions 12
Paths 6

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 12.2487

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 23
c 1
b 0
f 0
nc 6
nop 2
dl 0
loc 31
ccs 22
cts 25
cp 0.88
crap 12.2487
rs 6.9666

How to fix   Complexity   

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 5
    public function __construct($filename)
33
    {
34 5
        $this->gridFile = new SplFileObject($filename);
35 5
        $this->storageOrder = self::STORAGE_ORDER_INCREASING_LATITUDE_INCREASING_LONGITUDE;
36
37 5
        $this->readHeader();
38 5
    }
39
40
    /**
41
     * @return ArcSecond[]
42
     */
43 5
    public function getValues(float $x, float $y): array
44
    {
45
        // NTv2 is longitude positive *west*
46 5
        $x *= -1;
47
48
        // NTv2 is in seconds, not degrees
49 5
        $x *= 3600;
50 5
        $y *= 3600;
51
52 5
        $gridToUse = $this->determineBestGrid($x, $y);
53
54 5
        return $gridToUse->getValues($x, $y);
55
    }
56
57 5
    private function readHeader(): void
58
    {
59 5
        $this->gridFile->fseek(0);
60 5
        $rawData = $this->gridFile->fread(11 * self::RECORD_SIZE);
61 5
        if (unpack('VNUM_OREC', $rawData, 8)['NUM_OREC'] !== 11) {
62
            $this->integerFormatChar = 'N';
63
            $this->doubleFormatChar = 'E';
64
            $this->floatFormatChar = 'G';
65
        }
66
67 5
        $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
69 5
        assert($data['GS_TYPE'] === 'SECONDS');
70
71 5
        $subFileStart = 11 * self::RECORD_SIZE;
72 5
        for ($i = 0; $i < $data['NUM_FILE']; ++$i) {
73 5
            $this->gridFile->fseek($subFileStart);
74 5
            $subFileRawData = $this->gridFile->fread(11 * self::RECORD_SIZE);
75 5
            $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 5
            $subFileData['offsetStart'] = $subFileStart;
77
78
            //apply rounding to eliminate fp issues when being deserialized
79 5
            $subFileData['S_LAT'] = round($subFileData['S_LAT'], 5);
80 5
            $subFileData['N_LAT'] = round($subFileData['N_LAT'], 5);
81 5
            $subFileData['E_LONG'] = round($subFileData['E_LONG'], 5);
82 5
            $subFileData['W_LONG'] = round($subFileData['W_LONG'], 5);
83 5
            $this->subFileMetaData[$subFileData['SUB_NAME']] = $subFileData;
84
85 5
            $subFileStart += 11 * self::RECORD_SIZE + $subFileData['GS_COUNT'] * self::RECORD_SIZE;
86
        }
87 5
    }
88
89 5
    private function determineBestGrid(float $longitude, float $latitude): NTv2SubGrid
90
    {
91 5
        $possibleGrids = [];
92 5
        foreach ($this->subFileMetaData as $subFileMetaDatum) {
93 5
            if ($latitude === $subFileMetaDatum['N_LAT'] && $longitude === $subFileMetaDatum['W_LONG']) {
94
                $possibleGrids[] = [self::FLAG_ON_UPPER_LATITUDE_AND_LONGITUDE, $subFileMetaDatum];
95 5
            } elseif ($longitude === $subFileMetaDatum['W_LONG']) {
96
                $possibleGrids[] = [self::FLAG_ON_UPPER_LONGITUDE, $subFileMetaDatum];
97 5
            } elseif ($latitude === $subFileMetaDatum['N_LAT']) {
98
                $possibleGrids[] = [self::FLAG_ON_UPPER_LATITUDE, $subFileMetaDatum];
99 5
            } elseif ($latitude >= $subFileMetaDatum['S_LAT'] && $latitude <= $subFileMetaDatum['N_LAT'] && $longitude >= $subFileMetaDatum['E_LONG'] && $longitude <= $subFileMetaDatum['W_LONG']) {
100 5
                $possibleGrids[] = [self::FLAG_WITHIN_LIMITS, $subFileMetaDatum];
101
            }
102
        }
103
104 5
        usort($possibleGrids, static function ($a, $b) {
105 3
            return $a[0] <=> $b[0] ?: $a[1]['LAT_INC'] <=> $b[1]['LAT_INC'] ?: $a[2]['LONG_INC'] <=> $b[2]['LONG_INC'];
106 5
        });
107
108 5
        $gridToUse = $possibleGrids[0][1];
109
110 5
        return new NTv2SubGrid(
111 5
            $this->gridFile->getPathname(),
112 5
            $gridToUse['offsetStart'],
113 5
            $gridToUse['S_LAT'],
114 5
            $gridToUse['N_LAT'],
115 5
            $gridToUse['E_LONG'],
116 5
            $gridToUse['W_LONG'],
117 5
            $gridToUse['LAT_INC'],
118 5
            $gridToUse['LONG_INC'],
119 5
            $this->floatFormatChar
120
        );
121
    }
122
}
123