"""
Distance Calculator for Riyadh Districts
Uses the Haversine formula to calculate great-circle distances between coordinates.
"""
import math
from typing import List, Dict, Tuple, Optional
from riyadh_districts import RIYADH_DISTRICTS, get_district_info, search_districts
# Earth's radius in kilometers
EARTH_RADIUS_KM = 6371.0
def haversine_distance(lat1: float, lng1: float, lat2: float, lng2: float) -> float:
"""
Calculate the great-circle distance between two points on Earth
using the Haversine formula.
Args:
lat1, lng1: Latitude and longitude of point 1 (in degrees)
lat2, lng2: Latitude and longitude of point 2 (in degrees)
Returns:
Distance in kilometers
"""
# Convert to radians
lat1_rad = math.radians(lat1)
lat2_rad = math.radians(lat2)
delta_lat = math.radians(lat2 - lat1)
delta_lng = math.radians(lng2 - lng1)
# Haversine formula
a = math.sin(delta_lat / 2) ** 2 + \
math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(delta_lng / 2) ** 2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return EARTH_RADIUS_KM * c
def calculate_distance_between_districts(district1: str, district2: str) -> Optional[float]:
"""
Calculate distance between two districts by name.
Args:
district1: Name of first district
district2: Name of second district
Returns:
Distance in kilometers, or None if district not found
"""
info1 = get_district_info(district1)
info2 = get_district_info(district2)
if not info1:
raise ValueError(f"District not found: {district1}")
if not info2:
raise ValueError(f"District not found: {district2}")
return haversine_distance(info1["lat"], info1["lng"], info2["lat"], info2["lng"])
def get_distances_from_district(district_name: str) -> List[Dict]:
"""
Calculate distances from a given district to all other districts.
Args:
district_name: Name of the source district
Returns:
List of dicts with district info and distance, sorted by distance
"""
source = get_district_info(district_name)
if not source:
raise ValueError(f"District not found: {district_name}")
distances = []
for name, info in RIYADH_DISTRICTS.items():
if name == source["name"]:
continue
dist = haversine_distance(source["lat"], source["lng"], info["lat"], info["lng"])
distances.append({
"name": name,
"name_ar": info["name_ar"],
"lat": info["lat"],
"lng": info["lng"],
"distance_km": round(dist, 2)
})
# Sort by distance
distances.sort(key=lambda x: x["distance_km"])
return distances
def get_nearest_districts(district_name: str, count: int = 5) -> List[Dict]:
"""
Get the N nearest districts to a given district.
Args:
district_name: Name of the source district
count: Number of nearest districts to return (default 5)
Returns:
List of the nearest districts with their distances
"""
all_distances = get_distances_from_district(district_name)
return all_distances[:count]
def get_farthest_districts(district_name: str, count: int = 5) -> List[Dict]:
"""
Get the N farthest districts from a given district.
Args:
district_name: Name of the source district
count: Number of farthest districts to return (default 5)
Returns:
List of the farthest districts with their distances
"""
all_distances = get_distances_from_district(district_name)
return all_distances[-count:][::-1] # Reverse to show farthest first
def build_distance_matrix() -> Dict[str, Dict[str, float]]:
"""
Build a complete distance matrix between all districts.
Returns:
Nested dict where result[district1][district2] = distance in km
"""
districts = list(RIYADH_DISTRICTS.keys())
matrix = {}
for d1 in districts:
matrix[d1] = {}
info1 = RIYADH_DISTRICTS[d1]
for d2 in districts:
if d1 == d2:
matrix[d1][d2] = 0.0
else:
info2 = RIYADH_DISTRICTS[d2]
matrix[d1][d2] = round(
haversine_distance(info1["lat"], info1["lng"], info2["lat"], info2["lng"]),
2
)
return matrix
if __name__ == "__main__":
# Demo usage
print("=" * 60)
print("Riyadh District Distance Calculator")
print("=" * 60)
test_district = "Al Olaya"
print(f"\nNearest 10 districts to {test_district}:")
print("-" * 45)
nearest = get_nearest_districts(test_district, 10)
for i, d in enumerate(nearest, 1):
print(f"{i:2}. {d['name']:25} ({d['name_ar']:15}) - {d['distance_km']:5.2f} km")
print(f"\nFarthest 5 districts from {test_district}:")
print("-" * 45)
farthest = get_farthest_districts(test_district, 5)
for i, d in enumerate(farthest, 1):
print(f"{i:2}. {d['name']:25} ({d['name_ar']:15}) - {d['distance_km']:5.2f} km")
# Test distance between two specific districts
d1, d2 = "Al Olaya", "Al Hayr"
distance = calculate_distance_between_districts(d1, d2)
print(f"\nDistance from {d1} to {d2}: {distance:.2f} km")