*/ public const REACH_KM = array( 'AO' => 1100, 'AQ' => 5000, 'AR' => 2000, 'AU' => 2800, 'BO' => 1000, 'BR' => 2500, 'CA' => 3900, 'CD' => 1400, 'CL' => 2500, 'CN' => 3000, 'CO' => 1000, 'DZ' => 1400, 'EC' => 1200, 'EG' => 1000, 'ES' => 2000, 'ET' => 1000, 'FM' => 1500, 'FR' => 1000, 'GL' => 1500, 'ID' => 2800, 'IN' => 2000, 'IR' => 1200, 'IT' => 900, 'JP' => 1600, 'KI' => 3000, 'KZ' => 1500, 'LY' => 1200, 'MG' => 900, 'ML' => 1300, 'MN' => 1300, 'MR' => 1000, 'MX' => 1800, 'MY' => 1700, 'MZ' => 1100, 'NE' => 1200, 'NO' => 1300, 'NZ' => 900, 'PE' => 1200, 'PF' => 1500, 'PG' => 1000, 'PH' => 1000, 'PK' => 1000, 'PT' => 1600, 'RU' => 4000, 'SA' => 1300, 'SD' => 1200, 'SE' => 1000, 'SO' => 1000, 'TD' => 1200, 'TF' => 1500, 'TR' => 900, 'TZ' => 900, 'US' => 2600, 'VE' => 1000, 'YE' => 1000, 'ZA' => 1100, ); /** * Whether getting from one country to the other in the time given is * impossible. False whenever either end is unknown. * * @param string|null $from The earlier login's country. * @param string|null $to This login's country. * @param int $elapsed_seconds Time between the two logins. */ public static function is_impossible( ?string $from, ?string $to, int $elapsed_seconds ): bool { $km = self::lower_bound_km( $from, $to ); if ( null === $km || $km < self::MIN_DISTANCE_KM ) { return false; } // Covering $km at SPEED_KMH takes this long; less time than that is // impossible. Compared in seconds so a zero gap needs no division. return $elapsed_seconds < ( $km / self::SPEED_KMH ) * HOUR_IN_SECONDS; } /** * The least distance a traveller between the two countries must have * covered, in km: centroid distance less both reaches, never negative. * Null when either country has no place in the table. * * @param string|null $from A country code. * @param string|null $to A country code. */ public static function lower_bound_km( ?string $from, ?string $to ): ?float { $a = CountryCentroids::point( $from ); $b = CountryCentroids::point( $to ); if ( null === $a || null === $b ) { return null; } if ( $from === $to ) { return 0.0; } return max( 0.0, self::distance_km( $a, $b ) - self::reach_km( (string) $from ) - self::reach_km( (string) $to ) ); } /** * Great-circle distance between two points, km (haversine). * * @param array{0: float, 1: float} $a Latitude, longitude in degrees. * @param array{0: float, 1: float} $b Latitude, longitude in degrees. */ public static function distance_km( array $a, array $b ): float { $lat1 = deg2rad( $a[0] ); $lat2 = deg2rad( $b[0] ); $dlat = $lat2 - $lat1; $dlon = deg2rad( $b[1] - $a[1] ); $h = sin( $dlat / 2 ) ** 2 + cos( $lat1 ) * cos( $lat2 ) * sin( $dlon / 2 ) ** 2; return 2 * self::EARTH_RADIUS_KM * asin( min( 1.0, sqrt( $h ) ) ); } /** * A country's reach, km. * * @param string $code An ISO 3166-1 alpha-2 code. */ public static function reach_km( string $code ): int { return self::REACH_KM[ $code ] ?? self::DEFAULT_REACH_KM; } }