$hi ? $hi : $v); } /** * @param float $lat Decimal degrees, [-90, 90] * @param float $lon Decimal degrees, [-180, 180] * @param int $ts Unix UTC timestamp * @return string 20-character base32 geotemporal hash */ public static function generate($lat, $lon, $ts) { $lat = self::clamp((float)$lat, self::LAT_MIN, self::LAT_MAX); $lon = self::clamp((float)$lon, self::LON_MIN, self::LON_MAX); $ts = self::clamp((int)$ts, self::TIME_MIN, self::TIME_MAX); $norm_lat = ($lat - self::LAT_MIN) / (self::LAT_MAX - self::LAT_MIN); $norm_lon = ($lon - self::LON_MIN) / (self::LON_MAX - self::LON_MIN); $norm_time = ($ts - self::TIME_MIN) / self::TIME_RANGE; $MAX = 4294967295; // 2^32 - 1 $int_lat = (int) floor($norm_lat * $MAX); $int_lon = (int) floor($norm_lon * $MAX); $int_time = (int) floor($norm_time * $MAX); // Interleave MSB→LSB: lat_i, lon_i, time_i $bits = ''; for ($i = 31; $i >= 0; $i--) { $bits .= (($int_lat >> $i) & 1) ? '1' : '0'; $bits .= (($int_lon >> $i) & 1) ? '1' : '0'; $bits .= (($int_time >> $i) & 1) ? '1' : '0'; } // Pad to multiple of 5, base32-encode $rem = strlen($bits) % 5; if ($rem !== 0) { $bits .= str_repeat('0', 5 - $rem); } $out = ''; $len = strlen($bits); for ($i = 0; $i < $len; $i += 5) { $out .= self::ALPHABET[bindec(substr($bits, $i, 5))]; } return $out; } /** * Longest common prefix of an array of strings. */ public static function longestCommonPrefix(array $strings) { if (empty($strings)) return ''; $prefix = array_shift($strings); foreach ($strings as $s) { $n = min(strlen($prefix), strlen($s)); $i = 0; while ($i < $n && $prefix[$i] === $s[$i]) $i++; $prefix = substr($prefix, 0, $i); if ($prefix === '') break; } return $prefix; } }