qth_locator.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. #!/usr/bin/python3
  2. """
  3. Created on Mar 3, 2017
  4. @author: 4X5DM
  5. @other: F4IYQ
  6. """
  7. from math import floor
  8. import argparse
  9. # Constants
  10. ASCII_0 = 48
  11. ASCII_A = 65
  12. ASCII_a = 97
  13. # argparse
  14. parser = argparse.ArgumentParser(description='Convert QTH locator to latitude and longitude.')
  15. subparsers = parser.add_subparsers()
  16. p_square_to_location = subparsers.add_parser('square_to_location', help='Convert QTH locator to latitude and longitude')
  17. p_square_to_location.set_defaults(func='square_to_location')
  18. p_square_to_location.add_argument('qth_locator', type=str, help='QTH locator in format AA00 or AA00aa00')
  19. p_location_to_square = subparsers.add_parser('location_to_square', help='Convert latitude and longitude to QTH locator')
  20. p_location_to_square.set_defaults(func='location_to_square')
  21. p_location_to_square.add_argument('--lat', type=float, help='Latitude in decimal format')
  22. p_location_to_square.add_argument('--lon', type=float, help='Longitude in decimal format')
  23. p_test = subparsers.add_parser('test', help='Test QTH locator conversion')
  24. p_test.set_defaults(func='test')
  25. args = parser.parse_args()
  26. def square_to_location(qth_locator):
  27. """
  28. Converts QTH locator to latitude and longitude in decimal format.
  29. Gets QTH locator as string.
  30. Returns Tuple containing latitude and longitude as floats.
  31. """
  32. # Validate input
  33. assert isinstance(qth_locator, str)
  34. assert 4 <= len(qth_locator) <= 8
  35. assert len(qth_locator) % 2 == 0
  36. qth_locator = qth_locator.upper()
  37. # Separate fields, squares and subsquares
  38. # Fields
  39. lon_field = ord(qth_locator[0]) - ASCII_A
  40. lat_field = ord(qth_locator[1]) - ASCII_A
  41. # Squares
  42. lon_sq = ord(qth_locator[2]) - ASCII_0
  43. lat_sq = ord(qth_locator[3]) - ASCII_0
  44. # Subsquares
  45. if len(qth_locator) >= 6:
  46. lon_sub_sq = ord(qth_locator[4]) - ASCII_A
  47. lat_sub_sq = ord(qth_locator[5]) - ASCII_A
  48. else:
  49. lon_sub_sq = 0
  50. lat_sub_sq = 0
  51. # Extended squares
  52. if len(qth_locator) == 8:
  53. lon_ext_sq = ord(qth_locator[6]) - ASCII_0
  54. lat_ext_sq = ord(qth_locator[7]) - ASCII_0
  55. else:
  56. lon_ext_sq = 0
  57. lat_ext_sq = 0
  58. # Calculate latitude and longitude
  59. lon = -180.0
  60. lat = -90.0
  61. lon += 20.0 * lon_field
  62. lat += 10.0 * lat_field
  63. lon += 2.0 * lon_sq
  64. lat += 1.0 * lat_sq
  65. lon += 5.0 / 60 * lon_sub_sq
  66. lat += 2.5 / 60 * lat_sub_sq
  67. lon += 0.5 / 60 * lon_ext_sq
  68. lat += 0.25 / 60 * lat_ext_sq
  69. return lat, lon
  70. def location_to_square(lat, lon):
  71. """
  72. Converts latitude and longitude in decimal format to QTH locator.
  73. Gets latitude and longitude as floats.
  74. Returns QTH locator as string.
  75. """
  76. # Validate input
  77. assert isinstance(lat, (int, float))
  78. assert isinstance(lon, (int, float))
  79. assert -90.0 <= lat <= 90.0
  80. assert -180.0 <= lon <= 180.0
  81. # Separate fields, squares and subsquares
  82. lon += 180
  83. lat += 90
  84. # Fields
  85. lon_field = int(floor(lon / 20))
  86. lat_field = int(floor(lat / 10))
  87. lon -= lon_field * 20
  88. lat -= lat_field * 10
  89. # Squares
  90. lon_sq = int(floor(lon / 2))
  91. lat_sq = int(floor(lat / 1))
  92. lon -= lon_sq * 2
  93. lat -= lat_sq * 1
  94. # Subsquares
  95. lon_sub_sq = int(floor(lon / (5.0 / 60)))
  96. lat_sub_sq = int(floor(lat / (2.5 / 60)))
  97. lon -= lon_sub_sq * (5.0 / 60)
  98. lat -= lat_sub_sq * (2.5 / 60)
  99. # Extended squares
  100. lon_ext_sq = int(round(lon / (0.5 / 60)))
  101. lat_ext_sq = int(round(lat / (0.25 / 60)))
  102. qth_locator = f'{chr(lon_field + ASCII_A)}'
  103. qth_locator += chr(lat_field + ASCII_A)
  104. qth_locator += chr(lon_sq + ASCII_0)
  105. qth_locator += chr(lat_sq + ASCII_0)
  106. if lon_sub_sq > 0 or lat_sub_sq > 0 or lon_ext_sq > 0 or lat_ext_sq > 0:
  107. qth_locator += chr(lon_sub_sq + ASCII_a)
  108. qth_locator += chr(lat_sub_sq + ASCII_a)
  109. if lon_ext_sq > 0 or lat_ext_sq > 0:
  110. qth_locator += chr(lon_ext_sq + ASCII_0)
  111. qth_locator += chr(lat_ext_sq + ASCII_0)
  112. return qth_locator
  113. if __name__ == '__main__':
  114. if args.func == 'test':
  115. print('Testing QTH Locator conversion...')
  116. squares = [
  117. ('JJ00', (0, 0)),
  118. ('KM32', (32, 26)),
  119. ('KM32jn07', (32.570831, 26.750003)),
  120. ('KM72kk55', (32.437487, 34.875015)),
  121. ('JN45fo', (45.603333, 8.456667)),
  122. ('KO92so', (52.601484, 39.565160)),
  123. ('KM72jb', (32.071209, 34.780089)),
  124. # ('', (0, 0)),
  125. # ('', (0, 0)),
  126. ]
  127. print('\nQTH locator to coordinates:')
  128. for sq, res in squares:
  129. loc = square_to_location(sq)
  130. print(f'{sq}: {res}, calculated: {loc}')
  131. print('\nCoordinates to QTH locator:')
  132. for sq, loc in squares:
  133. qth = location_to_square(loc[0], loc[1])
  134. print(f'{loc}: {sq}, calculated: {qth}')
  135. if args.func == 'square_to_location':
  136. qth = args.qth_locator
  137. lat, lon = square_to_location(qth)
  138. print(f'QTH locator: {qth}, calculated coordinates: {lat}, {lon}')
  139. exit(0)
  140. if args.func == 'location_to_square':
  141. lat = args.lat
  142. lon = args.lon
  143. if lat is None or lon is None:
  144. parser.print_help()
  145. exit(1)
  146. if lat == 0 and lon == 0:
  147. print('Error: latitude and longitude cannot be both zero.')
  148. exit(1)
  149. if lat < -90 or lat > 90:
  150. print('Error: latitude must be between -90 and 90.')
  151. exit(1)
  152. if lon < -180 or lon > 180:
  153. print('Error: longitude must be between -180 and 180.')
  154. exit(1)
  155. qth = location_to_square(lat, lon).upper()
  156. print(qth)
  157. exit(0)
  158. print('\nDone.')