#!/usr/bin/python3 """ Created on Mar 3, 2017 @author: 4X5DM @other: F4IYQ """ from math import floor import argparse # Constants ASCII_0 = 48 ASCII_A = 65 ASCII_a = 97 # argparse parser = argparse.ArgumentParser(description='Convert QTH locator to latitude and longitude.') subparsers = parser.add_subparsers() p_square_to_location = subparsers.add_parser('square_to_location', help='Convert QTH locator to latitude and longitude') p_square_to_location.set_defaults(func='square_to_location') p_square_to_location.add_argument('qth_locator', type=str, help='QTH locator in format AA00 or AA00aa00') p_location_to_square = subparsers.add_parser('location_to_square', help='Convert latitude and longitude to QTH locator') p_location_to_square.set_defaults(func='location_to_square') p_location_to_square.add_argument('--lat', type=float, help='Latitude in decimal format') p_location_to_square.add_argument('--lon', type=float, help='Longitude in decimal format') p_test = subparsers.add_parser('test', help='Test QTH locator conversion') p_test.set_defaults(func='test') args = parser.parse_args() def square_to_location(qth_locator): """ Converts QTH locator to latitude and longitude in decimal format. Gets QTH locator as string. Returns Tuple containing latitude and longitude as floats. """ # Validate input assert isinstance(qth_locator, str) assert 4 <= len(qth_locator) <= 8 assert len(qth_locator) % 2 == 0 qth_locator = qth_locator.upper() # Separate fields, squares and subsquares # Fields lon_field = ord(qth_locator[0]) - ASCII_A lat_field = ord(qth_locator[1]) - ASCII_A # Squares lon_sq = ord(qth_locator[2]) - ASCII_0 lat_sq = ord(qth_locator[3]) - ASCII_0 # Subsquares if len(qth_locator) >= 6: lon_sub_sq = ord(qth_locator[4]) - ASCII_A lat_sub_sq = ord(qth_locator[5]) - ASCII_A else: lon_sub_sq = 0 lat_sub_sq = 0 # Extended squares if len(qth_locator) == 8: lon_ext_sq = ord(qth_locator[6]) - ASCII_0 lat_ext_sq = ord(qth_locator[7]) - ASCII_0 else: lon_ext_sq = 0 lat_ext_sq = 0 # Calculate latitude and longitude lon = -180.0 lat = -90.0 lon += 20.0 * lon_field lat += 10.0 * lat_field lon += 2.0 * lon_sq lat += 1.0 * lat_sq lon += 5.0 / 60 * lon_sub_sq lat += 2.5 / 60 * lat_sub_sq lon += 0.5 / 60 * lon_ext_sq lat += 0.25 / 60 * lat_ext_sq return lat, lon def location_to_square(lat, lon): """ Converts latitude and longitude in decimal format to QTH locator. Gets latitude and longitude as floats. Returns QTH locator as string. """ # Validate input assert isinstance(lat, (int, float)) assert isinstance(lon, (int, float)) assert -90.0 <= lat <= 90.0 assert -180.0 <= lon <= 180.0 # Separate fields, squares and subsquares lon += 180 lat += 90 # Fields lon_field = int(floor(lon / 20)) lat_field = int(floor(lat / 10)) lon -= lon_field * 20 lat -= lat_field * 10 # Squares lon_sq = int(floor(lon / 2)) lat_sq = int(floor(lat / 1)) lon -= lon_sq * 2 lat -= lat_sq * 1 # Subsquares lon_sub_sq = int(floor(lon / (5.0 / 60))) lat_sub_sq = int(floor(lat / (2.5 / 60))) lon -= lon_sub_sq * (5.0 / 60) lat -= lat_sub_sq * (2.5 / 60) # Extended squares lon_ext_sq = int(round(lon / (0.5 / 60))) lat_ext_sq = int(round(lat / (0.25 / 60))) qth_locator = f'{chr(lon_field + ASCII_A)}' qth_locator += chr(lat_field + ASCII_A) qth_locator += chr(lon_sq + ASCII_0) qth_locator += chr(lat_sq + ASCII_0) if lon_sub_sq > 0 or lat_sub_sq > 0 or lon_ext_sq > 0 or lat_ext_sq > 0: qth_locator += chr(lon_sub_sq + ASCII_a) qth_locator += chr(lat_sub_sq + ASCII_a) if lon_ext_sq > 0 or lat_ext_sq > 0: qth_locator += chr(lon_ext_sq + ASCII_0) qth_locator += chr(lat_ext_sq + ASCII_0) return qth_locator if __name__ == '__main__': if args.func == 'test': print('Testing QTH Locator conversion...') squares = [ ('JJ00', (0, 0)), ('KM32', (32, 26)), ('KM32jn07', (32.570831, 26.750003)), ('KM72kk55', (32.437487, 34.875015)), ('JN45fo', (45.603333, 8.456667)), ('KO92so', (52.601484, 39.565160)), ('KM72jb', (32.071209, 34.780089)), # ('', (0, 0)), # ('', (0, 0)), ] print('\nQTH locator to coordinates:') for sq, res in squares: loc = square_to_location(sq) print(f'{sq}: {res}, calculated: {loc}') print('\nCoordinates to QTH locator:') for sq, loc in squares: qth = location_to_square(loc[0], loc[1]) print(f'{loc}: {sq}, calculated: {qth}') if args.func == 'square_to_location': qth = args.qth_locator lat, lon = square_to_location(qth) print(f'QTH locator: {qth}, calculated coordinates: {lat}, {lon}') exit(0) if args.func == 'location_to_square': lat = args.lat lon = args.lon if lat is None or lon is None: parser.print_help() exit(1) if lat == 0 and lon == 0: print('Error: latitude and longitude cannot be both zero.') exit(1) if lat < -90 or lat > 90: print('Error: latitude must be between -90 and 90.') exit(1) if lon < -180 or lon > 180: print('Error: longitude must be between -180 and 180.') exit(1) qth = location_to_square(lat, lon).upper() print(qth) exit(0) print('\nDone.')