import math import random def haversine(lat1, lon1, lat2, lon2): """ Calculate the great circle distance in kilometers between two points on the Earth (specified in decimal degrees). Parameters: lat1, lon1: Latitude and longitude of point 1. lat2, lon2: Latitude and longitude of point 2. Returns: float: Distance between the two points in kilometers. """ # Radius of the Earth in kilometers. Use 3956 for miles R = 6371.0 # Convert latitude and longitude from degrees to radians lat1 = math.radians(lat1) lon1 = math.radians(lon1) lat2 = math.radians(lat2) lon2 = math.radians(lon2) # Difference in coordinates dlon = lon2 - lon1 dlat = lat2 - lat1 # Haversine formula a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2 c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) distance = R * c return distance def maidenhead_to_latlon(grid_square): """ Convert a Maidenhead locator to latitude and longitude coordinates. The output coordinates represent the southwestern corner of the grid square. Parameters: grid_square (str): The Maidenhead locator. Returns: tuple: A tuple containing the latitude and longitude (in that order) of the grid square's center. """ grid_square = generate_full_maidenhead(grid_square) grid_square = grid_square.upper() lon = -180 + (ord(grid_square[0]) - ord("A")) * 20 lat = -90 + (ord(grid_square[1]) - ord("A")) * 10 lon += int(grid_square[2]) * 2 lat += int(grid_square[3]) if len(grid_square) >= 6: lon += (ord(grid_square[4]) - ord("A")) * (5 / 60) lat += (ord(grid_square[5]) - ord("A")) * (2.5 / 60) # not needed now as we always have 6 digits if len(grid_square) == 8: lon += int(grid_square[6]) * (5 / 600) lat += int(grid_square[7]) * (2.5 / 600) # Adjust to the center of the grid square # not needed now as we always have 6 digits if len(grid_square) <= 4: lon += 1 lat += 0.5 elif len(grid_square) == 6: lon += 2.5 / 60 lat += 1.25 / 60 else: lon += 2.5 / 600 lat += 1.25 / 600 return lat, lon def latlon_to_maidenhead(lat, lon, precision=6): """ Convert latitude and longitude to a Maidenhead locator. Parameters: lat (float): Latitude in degrees. lon (float): Longitude in degrees. precision (int): Number of characters in the locator (4, 6 or 8). Default 6. Returns: str: Maidenhead locator string. """ lon += 180 lat += 90 A = ord("A") _a = ord("a") lon_field = int(lon // 20) lat_field = int(lat // 10) lon_square = int((lon % 20) // 2) lat_square = int((lat % 10) // 1) lon_subsquare = int(((lon % 2) / 2) * 24) lat_subsquare = int(((lat % 1) / 1) * 24) locator = f"{chr(A + lon_field)}{chr(A + lat_field)}{lon_square}{lat_square}{chr(A + lon_subsquare)}{chr(A + lat_subsquare)}" if precision == 4: return locator[:4] elif precision == 8: lon_ext = int((((lon % (1 / 12)) / (1 / 12)) * 10)) lat_ext = int((((lat % (1 / 24)) / (1 / 24)) * 10)) return locator + f"{lon_ext}{lat_ext}" else: return locator[:6] def distance_between_locators(locator1, locator2): """ Calculate the distance between two Maidenhead locators and return the result as a dictionary. Parameters: locator1 (str): The first Maidenhead locator. locator2 (str): The second Maidenhead locator. Returns: dict: A dictionary containing the distances in kilometers and miles. """ lat1, lon1 = maidenhead_to_latlon(locator1) lat2, lon2 = maidenhead_to_latlon(locator2) km = haversine(lat1, lon1, lat2, lon2) miles = km * 0.621371 return {"kilometers": km, "miles": miles} def generate_full_maidenhead(grid_square): """ Convert a Maidenhead locator of 2 or 4 characters to a 6-character locator by generating random characters for the missing positions, while ensuring the correct format: 1-2: Uppercase letters (A-R) 3-4: Digits (0-9) 5-6: Lowercase letters (a-r) Parameters: grid_square (str): A 2, 4, or 6 character Maidenhead locator. Returns: str: A 6-character Maidenhead locator. """ grid_square = grid_square.upper() # If the grid square is longer than 6 characters, strip it to 6 characters if len(grid_square) > 6: grid_square = grid_square[:6] if len(grid_square) == 2: # Generate random digits for positions 3 and 4 grid_square += f"{random.randint(0, 9)}{random.randint(0, 9)}" # Generate random lowercase letters from 'a' to 'r' for positions 5 and 6 grid_square += random.choice("abcdefghijklmnopqr") grid_square += random.choice("abcdefghijklmnopqr") elif len(grid_square) == 4: # Generate random lowercase letters from 'a' to 'r' for positions 5 and 6 grid_square += random.choice("abcdefghijklmnopqr") grid_square += random.choice("abcdefghijklmnopqr") elif len(grid_square) == 6: # If grid square is valid and already 6 characters, enforce format grid_square = grid_square[:2].upper() + grid_square[2:4] + grid_square[4:6].lower() return grid_square else: raise ValueError("Grid square must be 2, 4, or 6 characters long.") # Adjust the case for the last two characters grid_square = grid_square[:4] + grid_square[4:].lower() return grid_square