mirror of https://github.com/DJ2LS/FreeDATA.git
185 lines
5.5 KiB
Python
185 lines
5.5 KiB
Python
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
|