Skip to content

Commit 49f0289

Browse files
committed
Create CodeCalculatorInt
1 parent 202a2f1 commit 49f0289

File tree

1 file changed

+116
-0
lines changed

1 file changed

+116
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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

Comments
 (0)