|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace Vectorial1024\OpenLocationCodePhp\CodeCalculator; |
| 4 | + |
| 5 | +use RuntimeException; |
| 6 | +use Vectorial1024\OpenLocationCodePhp\CodeArea; |
| 7 | +use Vectorial1024\OpenLocationCodePhp\OpenLocationCode; |
| 8 | + |
| 9 | +/** |
| 10 | + * A Open Location Code calculator that uses "long" int. |
| 11 | + * As such, this has ensured accuracy, but cannot be used in 32-bit PHP. |
| 12 | + */ |
| 13 | +class CodeCalculatorInt extends AbstractCodeCalculator |
| 14 | +{ |
| 15 | + // Value of the most significant latitude digit after it has been converted to an integer. |
| 16 | + // Note: since we are using 64-bit PHP, this can be an int. |
| 17 | + public const int LAT_MSP_VALUE = self::LAT_INTEGER_MULTIPLIER * OpenLocationCode::ENCODING_BASE * OpenLocationCode::ENCODING_BASE; |
| 18 | + |
| 19 | + // Value of the most significant longitude digit after it has been converted to an integer. |
| 20 | + // Note: since we are using 64-bit PHP, this can be an int. |
| 21 | + public const int LNG_MSP_VALUE = self::LNG_INTEGER_MULTIPLIER * OpenLocationCode::ENCODING_BASE * OpenLocationCode::ENCODING_BASE; |
| 22 | + |
| 23 | + public function __construct() |
| 24 | + { |
| 25 | + if (PHP_INT_SIZE < 8) { |
| 26 | + // 32-bit PHP has 32-bit int, which uses 4 bytes i.e. PHP_INT_SIZE = 4 |
| 27 | + throw new RuntimeException("CodeCalculatorInt cannot be used due to bad PHP_INT_MAX value. Use CodeCalculatorFloat instead."); |
| 28 | + } |
| 29 | + } |
| 30 | + |
| 31 | + protected function generateRevOlcCode(float $latitude, float $longitude, int $codeLength): string |
| 32 | + { |
| 33 | + // PHP has native support for string concatenation, and string reversal is quite fast. |
| 34 | + $revCode = ""; |
| 35 | + |
| 36 | + // Compute the code. |
| 37 | + // The idea of this approach is to convert each value to an integer |
| 38 | + // after multiplying it by the final precision. |
| 39 | + // This allows us to use only integer operations, so |
| 40 | + // avoiding any accumulation of floating point representation errors. |
| 41 | + |
| 42 | + // Multiply values by their precision and convert to positive. |
| 43 | + // Rounding avoids/minimises errors due to floating point precision. |
| 44 | + $latVal = intdiv((int) round(($latitude + OpenLocationCode::LATITUDE_MAX) * self::LAT_INTEGER_MULTIPLIER * 1e6), 1e6); |
| 45 | + $lngVal = intdiv((int) round(($longitude + OpenLocationCode::LONGITUDE_MAX) * self::LNG_INTEGER_MULTIPLIER * 1e6), 1e6); |
| 46 | + |
| 47 | + // Compute the grid part of the code if necessary. |
| 48 | + if ($codeLength > OpenLocationCode::PAIR_CODE_LENGTH) { |
| 49 | + for ($i = 0; $i < OpenLocationCode::GRID_CODE_LENGTH; $i++) { |
| 50 | + $latDigit = $latVal % OpenLocationCode::GRID_ROWS; |
| 51 | + $lngDigit = $lngVal % OpenLocationCode::GRID_COLUMNS; |
| 52 | + $ndx = $latDigit * OpenLocationCode::GRID_COLUMNS + $lngDigit; |
| 53 | + $revCode .= OpenLocationCode::CODE_ALPHABET[$ndx]; |
| 54 | + $latVal = intdiv($latVal, OpenLocationCode::GRID_ROWS); |
| 55 | + $lngVal = intdiv($lngVal, OpenLocationCode::GRID_COLUMNS); |
| 56 | + } |
| 57 | + unset($i, $latDigit, $lngDigit, $ndx); |
| 58 | + } else { |
| 59 | + $latVal = (int) ($latVal / pow(OpenLocationCode::GRID_ROWS, OpenLocationCode::GRID_CODE_LENGTH)); |
| 60 | + $lngVal = (int) ($lngVal / pow(OpenLocationCode::GRID_COLUMNS, OpenLocationCode::GRID_CODE_LENGTH)); |
| 61 | + } |
| 62 | + |
| 63 | + // Compute the pair section of the code. |
| 64 | + for ($i = 0; $i < intdiv(OpenLocationCode::PAIR_CODE_LENGTH, 2); $i++) { |
| 65 | + $revCode .= OpenLocationCode::CODE_ALPHABET[$lngVal % OpenLocationCode::ENCODING_BASE]; |
| 66 | + $revCode .= OpenLocationCode::CODE_ALPHABET[$latVal % OpenLocationCode::ENCODING_BASE]; |
| 67 | + $latVal = intdiv($latVal, OpenLocationCode::ENCODING_BASE); |
| 68 | + $lngVal = intdiv($lngVal, OpenLocationCode::ENCODING_BASE); |
| 69 | + // If we are at the separator position, add the separator |
| 70 | + if ($i == 0) { |
| 71 | + $revCode .= OpenLocationCode::SEPARATOR; |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + return $revCode; |
| 76 | + } |
| 77 | + |
| 78 | + public function decode(string $strippedCode): CodeArea |
| 79 | + { |
| 80 | + // Initialize the values. |
| 81 | + $latVal = -OpenLocationCode::LATITUDE_MAX * self::LAT_INTEGER_MULTIPLIER; |
| 82 | + $lngVal = -OpenLocationCode::LONGITUDE_MAX * self::LNG_INTEGER_MULTIPLIER; |
| 83 | + // Define the place value for the digits. We'll divide this down as we work through the code. |
| 84 | + $latPlaceVal = self::LAT_MSP_VALUE; |
| 85 | + $lngPlaceVal = self::LNG_MSP_VALUE; |
| 86 | + for ($i = OpenLocationCode::PAIR_CODE_LENGTH; $i < min(strlen($strippedCode), OpenLocationCode::MAX_DIGIT_COUNT); $i += 2) { |
| 87 | + $latPlaceVal = intdiv($latPlaceVal, OpenLocationCode::ENCODING_BASE); |
| 88 | + $lngPlaceVal = intdiv($lngPlaceVal, OpenLocationCode::ENCODING_BASE); |
| 89 | + $latVal += strpos(OpenLocationCode::CODE_ALPHABET, $strippedCode[$i]) * $latPlaceVal; |
| 90 | + $lngVal = strpos(OpenLocationCode::CODE_ALPHABET, $strippedCode[$i + 1]) * $lngPlaceVal; |
| 91 | + } |
| 92 | + unset($i); |
| 93 | + for ($i = OpenLocationCode::PAIR_CODE_LENGTH; $i < min(strlen($strippedCode), OpenLocationCode::MAX_DIGIT_COUNT); $i++) { |
| 94 | + $latPlaceVal = intdiv($latPlaceVal, OpenLocationCode::GRID_ROWS); |
| 95 | + $lngPlaceVal = intdiv($lngPlaceVal, OpenLocationCode::GRID_COLUMNS); |
| 96 | + $digit = strpos(OpenLocationCode::CODE_ALPHABET, $strippedCode[$i]); |
| 97 | + $row = intdiv($digit, OpenLocationCode::GRID_COLUMNS); |
| 98 | + $col = $digit % OpenLocationCode::GRID_COLUMNS; |
| 99 | + $latVal += $row * $latPlaceVal; |
| 100 | + $lngVal += $col * $lngPlaceVal; |
| 101 | + unset($digit); |
| 102 | + } |
| 103 | + unset($i); |
| 104 | + $latitudeLo = $latVal / self::LAT_INTEGER_MULTIPLIER; |
| 105 | + $longitudeLo = $lngVal / self::LNG_INTEGER_MULTIPLIER; |
| 106 | + $latitudeHi = ($latVal + $latPlaceVal) / self::LAT_INTEGER_MULTIPLIER; |
| 107 | + $longitudeHi = ($lngVal + $lngPlaceVal) / self::LNG_INTEGER_MULTIPLIER; |
| 108 | + return new CodeArea( |
| 109 | + $latitudeLo, |
| 110 | + $longitudeLo, |
| 111 | + $latitudeHi, |
| 112 | + $longitudeHi, |
| 113 | + min(strlen($strippedCode), OpenLocationCode::MAX_DIGIT_COUNT), |
| 114 | + ); |
| 115 | + } |
| 116 | +} |
0 commit comments