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