diff --git a/composer.json b/composer.json index ad55823eb..8cb7ac49a 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "php-coord/php-coord", + "name": "stevegoddard/php-coord", "description": "PHPCoord is a set of PHP functions for handling various co-ordinate systems and converting between them", "keywords": ["coord","geo", "wgs84", "osgb36", "latitude", "longitude", "grid ref", "utm", "itm" ,"gps"], "homepage": "https://github.com/dvdoug/PHPCoord", diff --git a/src/OSRef.php b/src/OSRef.php index 494c820a3..c95bc97b1 100644 --- a/src/OSRef.php +++ b/src/OSRef.php @@ -19,6 +19,7 @@ class OSRef extends TransverseMercator { private const GRID_LETTERS = 'VWXYZQRSTULMNOPFGHJKABCDE'; + private const GRID_LETTERS_TETRAD = 'AFKQVBGLRWCHMSXDINTYEJPUZ'; /** * @return RefEll @@ -91,7 +92,10 @@ public function __construct($x, $y, $z = 0) */ public static function fromGridReference(string $ref): self { - if (strlen($ref) % 2 !== 0) { + $tetrad = false; + if(strlen($ref) === 5) { + $tetrad = true; + } elseif (strlen($ref) % 2 !== 0) { throw new LengthException('Grid ref must be an even number of characters'); } @@ -103,20 +107,33 @@ public static function fromGridReference(string $ref): self $minorEasting = strpos(self::GRID_LETTERS, $ref[1]) % 5 * 100000; $minorNorthing = (floor(strpos(self::GRID_LETTERS, $ref[1]) / 5)) * 100000; + //tetrad letter is 2km grid sq. THE GRID HAS A DIFFERENT ORIENTATION - starts bottom left and runs bottom to top. Includes I but no O. + $tetradEasting = 0; + $tetradNorthing = 0; + if ($tetrad) { + $tetradEasting = strpos(self::GRID_LETTERS_TETRAD, $ref[4]) % 5 * 2000; + $tetradNorthing = (floor(strpos(self::GRID_LETTERS_TETRAD, $ref[4]) / 5)) * 2000; + } + //numbers are a division of that square into smaller and smaller pieces - $numericPortion = substr($ref, 2); - $numericPortionSize = strlen($numericPortion) / 2; + if ($tetrad) { + $numericPortion = substr($ref, 2, 2); + $numericPortionSize = strlen($numericPortion) / 2; + } else { + $numericPortion = substr($ref, 2); + $numericPortionSize = strlen($numericPortion) / 2; + } $gridSizeInMetres = 1 * (10 ** (5 - $numericPortionSize)); - $easting = $majorEasting + $minorEasting + (substr($numericPortion, 0, $numericPortionSize) * $gridSizeInMetres); - $northing = $majorNorthing + $minorNorthing + (substr($numericPortion, -$numericPortionSize, $numericPortionSize) * $gridSizeInMetres); + $easting = $majorEasting + $minorEasting + $tetradEasting + (substr($numericPortion, 0, $numericPortionSize) * $gridSizeInMetres); + $northing = $majorNorthing + $minorNorthing + $tetradNorthing + (substr($numericPortion, -$numericPortionSize, $numericPortionSize) * $gridSizeInMetres); return new static((int) $easting, (int) $northing); } /** * Convert this grid reference into a grid reference string of a - * given length (2, 4, 6, 8 or 10) including the two-character + * given length (2, 5, 4, 6, 8 or 10) including the two-character * designation for the 100km square. e.g. TG514131. * * @param int $length @@ -125,11 +142,19 @@ public static function fromGridReference(string $ref): self */ public function toGridReference(int $length): string { - if ($length % 2 !== 0) { + $tetrad = false; + if($length === 5) { + $tetrad = true; + } elseif ($length % 2 !== 0) { throw new LengthException('Chosen length must be an even number'); } - $halfLength = $length / 2; + // manually set tetrad half length + if ($tetrad) { + $halfLength = 1; + } else { + $halfLength = $length / 2; + } $easting = str_pad((string) $this->x, 6, '0', STR_PAD_LEFT); $northing = str_pad((string) $this->y, 6, '0', STR_PAD_LEFT); @@ -141,13 +166,26 @@ public function toGridReference(int $length): string $majorLetterIndex = (int) (5 * $majorSquaresNorth + $majorSquaresEast); $majorLetter = substr(self::GRID_LETTERS, $majorLetterIndex, 1); - //second (minor) letter is 100km grid sq, origin at 0,0 of this square - $minorSquaresEast = $easting[0] % 5; - $minorSquaresNorth = $northing[0] % 5; - $minorLetterIndex = (5 * $minorSquaresNorth + $minorSquaresEast); + //second (minor) letter is 100km grid sq, origin at 0,0 of this square - work from the modulus + $majorSquaresEastModulus = $adjustedX % 500000; + $majorSquaresNorthModulus = $adjustedY % 500000; + $minorSquaresEast = floor($majorSquaresEastModulus / 100000); + $minorSquaresNorth = floor($majorSquaresNorthModulus / 100000); + $minorLetterIndex = (int) (5 * $minorSquaresNorth + $minorSquaresEast); $minorLetter = substr(self::GRID_LETTERS, $minorLetterIndex, 1); - return $majorLetter . $minorLetter . substr($easting, 1, $halfLength) . substr($northing, 1, $halfLength); + // tetrad + $tetradLetter = null; + if ($tetrad) { + $minorSquaresEastModulus = $majorSquaresEastModulus % 10000; + $minorSquaresNorthModulus = $majorSquaresNorthModulus % 10000; + $tetradSquaresEast = floor($minorSquaresEastModulus / 2000); + $tetradSquaresNorth = floor($minorSquaresNorthModulus / 2000); + $tetradLetterIndex = (int) (5 * $tetradSquaresNorth + $tetradSquaresEast); + $tetradLetter = substr(self::GRID_LETTERS_TETRAD, $tetradLetterIndex, 1); + } + + return $majorLetter . $minorLetter . substr($easting, 1, $halfLength) . substr($northing, 1, $halfLength) . $tetradLetter; } /** diff --git a/tests/OSRefTest.php b/tests/OSRefTest.php index 778bd5443..62025d3e1 100644 --- a/tests/OSRefTest.php +++ b/tests/OSRefTest.php @@ -147,6 +147,23 @@ public function testFromSixFigureString2(): void self::assertEquals($expected, $OSRef->__toString()); } + public function testFromTetradString(): void + { + $OSRef = OSRef::fromGridReference('SO24G'); + $expected = '(322000, 242000)'; + + self::assertEquals($expected, $OSRef->__toString()); + } + + public function testToTetradString(): void + { + $OSRef = new OSRef(216604, 771209); + + $expected = 'NN17Q'; + + self::assertEquals($expected, $OSRef->toGridReference(5)); + } + public function testIssue7(): void { $osRef = new OSRef(322000, 241000);