|  | 
|  | 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