Passed
Push — master ( b9eab5...b4d4f4 )
by Doug
20:40 queued 03:54
created

BritishNationalGridPoint   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 96
Duplicated Lines 0 %

Test Coverage

Coverage 97.73%

Importance

Changes 0
Metric Value
eloc 40
dl 0
loc 96
ccs 43
cts 44
cp 0.9773
rs 10
c 0
b 0
f 0
wmc 8

5 Methods

Rating   Name   Duplication   Size   Complexity  
A fromGridReference() 0 25 2
A __construct() 0 3 1
A asGridReferenceWithSpaces() 0 3 1
A asGridReference() 0 3 1
A gridReference() 0 34 3
1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord\Point;
10
11
use DateTimeInterface;
12
use PHPCoord\CoordinateReferenceSystem\Projected;
13
use PHPCoord\Exception\InvalidCoordinateException;
14
use PHPCoord\UnitOfMeasure\Length\Length;
15
use PHPCoord\UnitOfMeasure\Length\Metre;
16
17
use function floor;
18
use function implode;
19
use function str_pad;
20
use function str_replace;
21
use function strlen;
22
use function strpos;
23
use function substr;
24
25
use const STR_PAD_LEFT;
26
27
class BritishNationalGridPoint extends ProjectedPoint
28
{
29
    private const GRID_LETTERS = 'VWXYZQRSTULMNOPFGHJKABCDE';
30
31 243
    public function __construct(Length $easting, Length $northing, ?DateTimeInterface $epoch = null)
32
    {
33 243
        parent::__construct(Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID), $easting, $northing, null, null, $epoch, null);
34
    }
35
36
    /**
37
     * @param string $reference OS grid reference (e.g. "TG514131")
38
     */
39 81
    public static function fromGridReference(string $reference, ?DateTimeInterface $epoch = null): self
40
    {
41 81
        $reference = str_replace(' ', '', $reference);
42
43 81
        if (strlen($reference) % 2 !== 0) {
44 9
            throw new InvalidCoordinateException('Grid ref must be an even number of characters');
45
        }
46
47
        // first (major) letter is the 500km grid sq, origin at -1000000, -500000
48 72
        $majorEasting = strpos(self::GRID_LETTERS, $reference[0]) % 5 * 500000 - 1000000;
49 72
        $majorNorthing = floor(strpos(self::GRID_LETTERS, $reference[0]) / 5) * 500000 - 500000;
50
51
        // second (minor) letter is 100km grid sq, origin at 0,0 of this square
52 72
        $minorEasting = strpos(self::GRID_LETTERS, $reference[1]) % 5 * 100000;
53 72
        $minorNorthing = floor(strpos(self::GRID_LETTERS, $reference[1]) / 5) * 100000;
54
55
        // numbers are a division of that square into smaller and smaller pieces
56 72
        $numericPortion = substr($reference, 2);
57 72
        $numericPortionSize = strlen($numericPortion) / 2;
58 72
        $gridSizeInMetres = 1 * (10 ** (5 - $numericPortionSize));
59
60 72
        $easting = $majorEasting + $minorEasting + ((int) substr($numericPortion, 0, (int) $numericPortionSize) * $gridSizeInMetres);
61 72
        $northing = $majorNorthing + $minorNorthing + ((int) substr($numericPortion, -(int) $numericPortionSize, (int) $numericPortionSize) * $gridSizeInMetres);
62
63 72
        return new self(new Metre($easting), new Metre($northing), $epoch);
64
    }
65
66
    /**
67
     * Grid reference without spaces. e.g. TG514131.
68
     */
69 72
    public function asGridReference(int $length): string
70
    {
71 72
        return implode('', $this->gridReference($length));
72
    }
73
74
    /**
75
     * Grid reference with spaces. e.g. TG 514 131.
76
     */
77 27
    public function asGridReferenceWithSpaces(int $length): string
78
    {
79 27
        return implode(' ', $this->gridReference($length));
80
    }
81
82
    /**
83
     * Convert this grid reference into a grid reference string of a
84
     * given length (2, 4, 6, 8 or 10) including the two-character
85
     * designation for the 100km square. e.g. TG514131.
86
     *
87
     * @return array{0: string, 1: string, 2: string}
88
     */
89 99
    protected function gridReference(int $length): array
90
    {
91 99
        if ($length % 2 !== 0) {
92 9
            throw new InvalidCoordinateException('Chosen length must be an even number');
93
        }
94
95 90
        if ($length > 10) {
96
            throw new InvalidCoordinateException('Grid squares are 100,000m wide, max precision is 10');
97
        }
98
99 90
        $halfLength = $length / 2;
100
101 90
        $x = $this->easting->asMetres()->getValue();
102 90
        $y = $this->northing->asMetres()->getValue();
103 90
        $easting = str_pad((string) (int) $x, $halfLength + 1, '0', STR_PAD_LEFT);
104 90
        $northing = str_pad((string) (int) $y, $halfLength + 1, '0', STR_PAD_LEFT);
105
106 90
        $adjustedX = $x + 1000000;
107 90
        $adjustedY = $y + 500000;
108 90
        $majorSquaresEast = floor($adjustedX / 500000);
109 90
        $majorSquaresNorth = floor($adjustedY / 500000);
110 90
        $majorLetterIndex = (int) (5 * $majorSquaresNorth + $majorSquaresEast);
111 90
        $majorLetter = substr(self::GRID_LETTERS, $majorLetterIndex, 1);
112
113
        // second (minor) letter is 100km grid sq, origin at 0,0 of this square
114 90
        $minorSquaresEast = (int) $easting[0] % 5;
115 90
        $minorSquaresNorth = (int) $northing[0] % 5;
116 90
        $minorLetterIndex = (5 * $minorSquaresNorth + $minorSquaresEast);
117 90
        $minorLetter = substr(self::GRID_LETTERS, $minorLetterIndex, 1);
118
119 90
        return [
120 90
            $majorLetter . $minorLetter,
121 90
            substr($easting, 1, $halfLength),
122 90
            substr($northing, 1, $halfLength),
123 90
        ];
124
    }
125
}
126