mirror of
https://github.com/dh1tw/pyhamtools.git
synced 2025-12-06 06:52:00 +01:00
Added module for Locator based calculations (Distance, Heading, Locator -> Lat / Long....etc)
This commit is contained in:
parent
0fabb0c90b
commit
eec8dc2008
354
pyhamtools/locator.py
Normal file
354
pyhamtools/locator.py
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
from __future__ import division
|
||||
from math import pi, sin, cos, atan2, sqrt, radians, log, tan, degrees
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
import ephem
|
||||
|
||||
UTC = pytz.UTC
|
||||
|
||||
def latlong_to_locator (latitude, longitude):
|
||||
"""converts WGS84 coordinates into the corresponding Maidenhead Locator
|
||||
|
||||
Args:
|
||||
latitude (float): Latitude
|
||||
longitude (float): Longitude
|
||||
|
||||
Returns:
|
||||
string: Maidenhead locator
|
||||
|
||||
Raises:
|
||||
ValueError: When called with wrong or invalid input args
|
||||
TypeError: When args are non float values
|
||||
|
||||
Example:
|
||||
The following example converts latitude and longitude into the Maidenhead locator
|
||||
|
||||
>>> from pyhamtools.locator import latlong_to_locator
|
||||
>>> latitude = 48.5208333
|
||||
>>> longitude = 9.375
|
||||
'JN48QM'
|
||||
|
||||
Note:
|
||||
Latitude (negative = West, positive = East)
|
||||
Longitude (negative = South, positive = North)
|
||||
|
||||
"""
|
||||
|
||||
if longitude >= 180 or longitude <= -180:
|
||||
raise ValueError
|
||||
|
||||
if latitude >= 90 or latitude <= -90:
|
||||
raise ValueError
|
||||
|
||||
longitude += 180;
|
||||
latitude +=90;
|
||||
|
||||
locator = chr(ord('A') + int(longitude / 20))
|
||||
locator += chr(ord('A') + int(latitude / 10))
|
||||
locator += chr(ord('0') + int((longitude % 20) / 2))
|
||||
locator += chr(ord('0') + int(latitude % 10))
|
||||
locator += chr(ord('A') + int((longitude - int(longitude / 2) * 2) / (2 / 24)))
|
||||
locator += chr(ord('A') + int((latitude - int(latitude / 1) * 1 ) / (1 / 24)))
|
||||
|
||||
return locator
|
||||
|
||||
def locator_to_latlong (locator):
|
||||
"""converts Maidenhead locator in the corresponding WGS84 coordinates
|
||||
|
||||
Args:
|
||||
locator (string): Locator, either 4 or 6 characters
|
||||
|
||||
Returns:
|
||||
tuple (float, float): Latitude, Longitude
|
||||
|
||||
Raises:
|
||||
ValueError: When called with wrong or invalid input arg
|
||||
TypeError: When arg is not a string
|
||||
|
||||
Example:
|
||||
The following example converts a Maidenhead locator into Latitude and Longitude
|
||||
|
||||
>>> from pyhamtools.locator import locator_to_latlong
|
||||
>>> latitude, longitude = locator_to_latlong("JN48QM")
|
||||
>>> print latitude, longitude
|
||||
48.5208333333 9.375
|
||||
|
||||
Note:
|
||||
Latitude (negative = West, positive = East)
|
||||
Longitude (negative = South, positive = North)
|
||||
|
||||
"""
|
||||
|
||||
locator = locator.upper()
|
||||
|
||||
if len(locator) == 5 or len(locator) < 4:
|
||||
raise ValueError
|
||||
|
||||
if ord(locator[0]) > ord('R') or ord(locator[0]) < ord('A'):
|
||||
raise ValueError
|
||||
|
||||
if ord(locator[1]) > ord('R') or ord(locator[1]) < ord('A'):
|
||||
raise ValueError
|
||||
|
||||
if ord(locator[2]) > ord('9') or ord(locator[2]) < ord('0'):
|
||||
raise ValueError
|
||||
|
||||
if ord(locator[3]) > ord('9') or ord(locator[3]) < ord('0'):
|
||||
raise ValueError
|
||||
|
||||
if len(locator) == 6:
|
||||
if ord(locator[4]) > ord('X') or ord(locator[4]) < ord('A'):
|
||||
raise ValueError
|
||||
if ord (locator[5]) > ord('X') or ord(locator[5]) < ord('A'):
|
||||
raise ValueError
|
||||
|
||||
longitude = (ord(locator[0]) - ord('A')) * 20 - 180
|
||||
latitude = (ord(locator[1]) - ord('A')) * 10 - 90
|
||||
longitude += (ord(locator[2]) - ord('0')) * 2
|
||||
latitude += (ord(locator[3]) - ord('0'))
|
||||
|
||||
if len(locator) == 6:
|
||||
longitude += ((ord(locator[4])) - ord('A')) * (2 / 24)
|
||||
latitude += ((ord(locator[5])) - ord('A')) * (1 / 24)
|
||||
|
||||
# move to center of subsquare
|
||||
longitude += 1 / 24
|
||||
latitude += 0.5 / 24
|
||||
|
||||
else:
|
||||
# move to center of square
|
||||
longitude += 1;
|
||||
latitude += 0.5;
|
||||
|
||||
return latitude, longitude
|
||||
|
||||
|
||||
def calculate_distance(locator1, locator2):
|
||||
"""calculates the (shortpath) distance between two Maidenhead locators
|
||||
|
||||
Args:
|
||||
locator1 (string): Locator, either 4 or 6 characters
|
||||
locator2 (string): Locator, either 4 or 6 characters
|
||||
|
||||
Returns:
|
||||
float: Distance in km
|
||||
|
||||
Raises:
|
||||
ValueError: When called with wrong or invalid input arg
|
||||
AttributeError: When args are not a string
|
||||
|
||||
Example:
|
||||
The following calculates the distance between two Maidenhead locators in km
|
||||
|
||||
>>> from pyhamtools.locator import calculate_distance
|
||||
>>> calculate_distance("JN48QM", "QF67bf")
|
||||
16466.413
|
||||
|
||||
"""
|
||||
|
||||
R = 6371 #earh radius
|
||||
lat1, long1 = locator_to_latlong(locator1)
|
||||
lat2, long2 = locator_to_latlong(locator2)
|
||||
|
||||
d_lat = radians(lat2) - radians(lat1)
|
||||
d_long = radians(long2) - radians(long1)
|
||||
|
||||
r_lat1 = radians(lat1)
|
||||
r_long1 = radians(long1)
|
||||
r_lat2 = radians(lat2)
|
||||
r_long2 = radians(long2)
|
||||
|
||||
a = sin(d_lat/2) * sin(d_lat/2) + cos(r_lat1) * cos(r_lat2) * sin(d_long/2) * sin(d_long/2)
|
||||
c = 2 * atan2(sqrt(a), sqrt(1-a))
|
||||
d = R * c #distance in km
|
||||
|
||||
return d;
|
||||
|
||||
|
||||
def calculate_distance_longpath(locator1, locator2):
|
||||
"""calculates the (longpath) distance between two Maidenhead locators
|
||||
|
||||
Args:
|
||||
locator1 (string): Locator, either 4 or 6 characters
|
||||
locator2 (string): Locator, either 4 or 6 characters
|
||||
|
||||
Returns:
|
||||
float: Distance in km
|
||||
|
||||
Raises:
|
||||
ValueError: When called with wrong or invalid input arg
|
||||
AttributeError: When args are not a string
|
||||
|
||||
Example:
|
||||
The following calculates the longpath distance between two Maidenhead locators in km
|
||||
|
||||
>>> from pyhamtools.locator import calculate_distance_longpath
|
||||
>>> calculate_distance_longpath("JN48QM", "QF67bf")
|
||||
23541.5867
|
||||
|
||||
"""
|
||||
|
||||
c = 40008 #[km] earth circumference
|
||||
sp = calculate_distance(locator1, locator2)
|
||||
|
||||
return c - sp
|
||||
|
||||
|
||||
def calculate_heading(locator1, locator2):
|
||||
"""calculates the heading from the first to the second locator
|
||||
|
||||
Args:
|
||||
locator1 (string): Locator, either 4 or 6 characters
|
||||
locator2 (string): Locator, either 4 or 6 characters
|
||||
|
||||
Returns:
|
||||
float: Heading in deg
|
||||
|
||||
Raises:
|
||||
ValueError: When called with wrong or invalid input arg
|
||||
AttributeError: When args are not a string
|
||||
|
||||
Example:
|
||||
The following calculates the heading from locator1 to locator2
|
||||
|
||||
>>> from pyhamtools.locator import calculate_heading
|
||||
>>> calculate_heading("JN48QM", "QF67bf")
|
||||
74.3136
|
||||
|
||||
"""
|
||||
|
||||
lat1, long1 = locator_to_latlong(locator1)
|
||||
lat2, long2 = locator_to_latlong(locator2)
|
||||
|
||||
r_lat1 = radians(lat1)
|
||||
r_lon1 = radians(long1)
|
||||
|
||||
r_lat2 = radians(lat2)
|
||||
r_lon2 = radians(long2)
|
||||
|
||||
d_lon = radians(long2 - long1)
|
||||
|
||||
b = atan2(sin(d_lon)*cos(r_lat2),cos(r_lat1)*sin(r_lat2)-sin(r_lat1)*cos(r_lat2)*cos(d_lon)) # bearing calc
|
||||
bd = degrees(b)
|
||||
br,bn = divmod(bd+360,360) # the bearing remainder and final bearing
|
||||
|
||||
return bn
|
||||
|
||||
def calculate_heading_longpath(locator1, locator2):
|
||||
"""calculates the heading from the first to the second locator (long path)
|
||||
|
||||
Args:
|
||||
locator1 (string): Locator, either 4 or 6 characters
|
||||
locator2 (string): Locator, either 4 or 6 characters
|
||||
|
||||
Returns:
|
||||
float: Long path heading in deg
|
||||
|
||||
Raises:
|
||||
ValueError: When called with wrong or invalid input arg
|
||||
AttributeError: When args are not a string
|
||||
|
||||
Example:
|
||||
The following calculates the long path heading from locator1 to locator2
|
||||
|
||||
>>> from pyhamtools.locator import calculate_heading_longpath
|
||||
>>> calculate_heading_longpath("JN48QM", "QF67bf")
|
||||
254.3136
|
||||
|
||||
"""
|
||||
|
||||
heading = calculate_heading(locator1, locator2)
|
||||
|
||||
lp = (heading + 180)%360
|
||||
|
||||
return lp
|
||||
|
||||
def calculate_sunrise_sunset(locator, calc_date=datetime.utcnow()):
|
||||
"""calculates the next sunset and sunrise for a Maidenhead locator at a give date & time
|
||||
|
||||
Args:
|
||||
locator1 (string): Maidenhead Locator, either 4 or 6 characters
|
||||
calc_date (datetime, optional): Starting datetime for the calculations (UTC)
|
||||
|
||||
Returns:
|
||||
dict: Containing datetimes for morning_dawn, sunrise, evening_dawn, sunset
|
||||
|
||||
Raises:
|
||||
ValueError: When called with wrong or invalid input arg
|
||||
AttributeError: When args are not a string
|
||||
|
||||
Example:
|
||||
The following calculates the next sunrise & sunset for JN48QM on the 1./Jan/2014
|
||||
|
||||
>>> from pyhamtools.locator import calculate_sunrise_sunset
|
||||
>>> from datetime import datetime
|
||||
>>> import pytz
|
||||
>>> UTC = pytz.UTC
|
||||
>>> myDate = datetime(year=2014, month=1, day=1, tzinfo=UTC)
|
||||
>>> calculate_sunrise_sunset("JN48QM", myDate)
|
||||
74.3136
|
||||
|
||||
"""
|
||||
morning_dawn = None
|
||||
sunrise = None
|
||||
evening_dawn = None
|
||||
sunset = None
|
||||
|
||||
latitude, longitude = locator_to_latlong(locator)
|
||||
|
||||
if type(calc_date) != datetime:
|
||||
raise ValueError
|
||||
|
||||
sun = ephem.Sun()
|
||||
home = ephem.Observer()
|
||||
|
||||
home.lat = str(latitude)
|
||||
home.long = str(longitude)
|
||||
home.date = calc_date
|
||||
|
||||
sun.compute(home)
|
||||
|
||||
try:
|
||||
nextrise = home.next_rising(sun)
|
||||
nextset = home.next_setting(sun)
|
||||
|
||||
home.horizon = '-6'
|
||||
beg_twilight = home.next_rising(sun, use_center=True)
|
||||
end_twilight = home.next_setting(sun, use_center=True)
|
||||
|
||||
morning_dawn = beg_twilight.datetime()
|
||||
sunrise = nextrise.datetime()
|
||||
|
||||
evening_dawn = nextset.datetime()
|
||||
sunset = end_twilight.datetime()
|
||||
|
||||
#if sun never sets or rises (e.g. at polar circles)
|
||||
except ephem.AlwaysUpError as e:
|
||||
morning_dawn = None
|
||||
sunrise = None
|
||||
evening_dawn = None
|
||||
sunset = None
|
||||
except ephem.NeverUpError as e:
|
||||
morning_dawn = None
|
||||
sunrise = None
|
||||
evening_dawn = None
|
||||
sunset = None
|
||||
|
||||
result = {}
|
||||
result['morning_dawn'] = morning_dawn
|
||||
result['sunrise'] = sunrise
|
||||
result['evening_dawn'] = evening_dawn
|
||||
result['sunset'] = sunset
|
||||
|
||||
if morning_dawn:
|
||||
result['morning_dawn'] = morning_dawn.replace(tzinfo=UTC)
|
||||
if sunrise:
|
||||
result['sunrise'] = sunrise.replace(tzinfo=UTC)
|
||||
if evening_dawn:
|
||||
result['evening_dawn'] = evening_dawn.replace(tzinfo=UTC)
|
||||
if sunset:
|
||||
result['sunset'] = sunset.replace(tzinfo=UTC)
|
||||
print result
|
||||
return result
|
||||
|
||||
3
setup.py
3
setup.py
|
|
@ -8,7 +8,7 @@ if sys.version_info >= (3,):
|
|||
kw['use_2to3'] = True
|
||||
|
||||
setup(name='pyhamtools',
|
||||
version='0.3.1',
|
||||
version='0.4.0',
|
||||
description='Collection of Tools for Amateur Radio developers',
|
||||
author='Tobias Wellnitz, DH1TW',
|
||||
author_email='Tobias@dh1tw.de',
|
||||
|
|
@ -18,6 +18,7 @@ setup(name='pyhamtools',
|
|||
install_requires=[
|
||||
"pytz",
|
||||
"requests",
|
||||
"pyephem",
|
||||
],
|
||||
**kw
|
||||
)
|
||||
|
|
|
|||
69
test/test_dxcluster.py
Normal file
69
test/test_dxcluster.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import pytest
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
import pytz
|
||||
|
||||
|
||||
from pyhamtools.consts import LookupConventions as const
|
||||
from pyhamtools.dxcluster import decode_char_spot, decode_pc11_message, decode_pc61_message
|
||||
|
||||
UTC = pytz.UTC
|
||||
|
||||
fix_spot1 = "DX de CT3FW: 21004.8 HC2AO 599 TKS(CW)QSL READ,QRZ.COM 2132Z"
|
||||
fix_spot1_broken_spotter_call = "DX de $QRM: 21004.8 HC2AO 599 TKS(CW)QSL READ,QRZ.COM 2132Z"
|
||||
|
||||
fix_spot_pc11 = "PC11^14010.0^R155AP^30-Apr-2014^2252Z^CQ CQ^R9CAC^RN6BN^H95^~"
|
||||
fix_spot_pc61 = "PC61^14030.5^ZF2NUE^30-Apr-2014^2253Z^ ^ND4X^W4NJA^72.51.152.150^H96^~"
|
||||
|
||||
fix_spot2 = "DX de DL6NAA: 10368887.0 DL7VTX/B 55s in JO50VFjo62 never hrd B4 1505Z"
|
||||
fix_spot3 = "DX de CT3FW: 21004.8 IDIOT 599 TKS(CW)QSL READ,QRZ.COM 2132Z"
|
||||
fix_spot4 = "DX de OK1TEH: 144000.0 C0NTEST -> www.darkside.cz/qrv.php 1328Z JO70"
|
||||
fix_spot5 = "DX de DK7UK: 50099.0 EA5/ON4CAU JN48QT<ES>IM98 QRP 5W LOOP ANT 1206Z"
|
||||
fix_spot6 = "DX de UA3ZBK: 14170.0 UR8EW/QRP POWER 2-GU81+SPYDER 1211Z"
|
||||
fix_spot7 = "DX de 9K2/K2SES 14205.0 DK0HY 0921Z" #missing semicolon
|
||||
fix_spot8 = "DX de DK1CS:9330368887.0 DL7VTX/B 1505Z"
|
||||
fix_spot9 = "DX de DH1TW: 23.0 DS1TW 1505Z"
|
||||
fix_spot10 = "DX de DH1TW 234.0 DS1TW 1505Z"
|
||||
fix_spot11 = "DX de DH1TW: 234.0 DS1TW 1505Z"
|
||||
fix_spot12 = "DX de DH1TW: 50105.0 ZD6DYA 1505Z"
|
||||
|
||||
response_spot1 = {
|
||||
const.SPOTTER: "CT3FW",
|
||||
const.DX: "HC2AO",
|
||||
const.BAND: 15,
|
||||
const.MODE: "CW",
|
||||
const.COMMENT: "599 TKS(CW)QSL READ,QRZ.COM",
|
||||
const.TIME: datetime.utcnow().replace( hour=21, minute=32, second=0, microsecond = 0, tzinfo=UTC)
|
||||
}
|
||||
|
||||
|
||||
class TestDXClusterSpots:
|
||||
|
||||
def test_spots(self):
|
||||
assert decode_char_spot(fix_spot1)[const.SPOTTER] == "CT3FW"
|
||||
assert decode_char_spot(fix_spot1)[const.DX] == "HC2AO"
|
||||
assert decode_char_spot(fix_spot1)[const.FREQUENCY] == 21004.8
|
||||
assert decode_char_spot(fix_spot1)[const.COMMENT] == "599 TKS(CW)QSL READ,QRZ.COM"
|
||||
assert isinstance(decode_char_spot(fix_spot1)[const.TIME], datetime)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
decode_char_spot(fix_spot1_broken_spotter_call)
|
||||
|
||||
|
||||
def test_spots_pc11(self):
|
||||
assert decode_pc11_message(fix_spot_pc11)[const.SPOTTER] == "R9CAC"
|
||||
assert decode_pc11_message(fix_spot_pc11)[const.DX] == "R155AP"
|
||||
assert decode_pc11_message(fix_spot_pc11)[const.FREQUENCY] == 14010.0
|
||||
assert decode_pc11_message(fix_spot_pc11)[const.COMMENT] == "CQ CQ"
|
||||
assert decode_pc11_message(fix_spot_pc11)["node"] == "RN6BN"
|
||||
assert isinstance(decode_pc11_message(fix_spot_pc11)[const.TIME], datetime)
|
||||
|
||||
def test_spots_pc61(self):
|
||||
assert decode_pc61_message(fix_spot_pc61)[const.SPOTTER] == "ND4X"
|
||||
assert decode_pc61_message(fix_spot_pc61)[const.DX] == "ZF2NUE"
|
||||
assert decode_pc61_message(fix_spot_pc61)[const.FREQUENCY] == 14030.5
|
||||
assert decode_pc61_message(fix_spot_pc61)[const.COMMENT] == " "
|
||||
assert decode_pc61_message(fix_spot_pc61)["node"] == "W4NJA"
|
||||
assert decode_pc61_message(fix_spot_pc61)["ip"] == "72.51.152.150"
|
||||
assert isinstance(decode_pc61_message(fix_spot_pc61)[const.TIME], datetime)
|
||||
60
test/test_locator_distances.py
Normal file
60
test/test_locator_distances.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import pytest
|
||||
from pyhamtools.locator import calculate_distance, calculate_distance_longpath, calculate_heading, calculate_heading_longpath
|
||||
from pyhamtools.consts import LookupConventions as const
|
||||
|
||||
class Test_calculate_distance():
|
||||
|
||||
def test_calculate_distance_edge_cases(self):
|
||||
|
||||
assert calculate_distance("JN48QM", "JN48QM") == 0
|
||||
assert calculate_distance("JN48", "JN48") == 0
|
||||
assert abs(calculate_distance("AA00AA", "rr00xx") - 19009) < 1
|
||||
|
||||
def test_calculate_distance_normal_case(self):
|
||||
|
||||
assert abs(calculate_distance("JN48QM", "FN44AB") - 5965) < 1
|
||||
assert abs(calculate_distance("FN44AB", "JN48QM") - 5965) < 1
|
||||
assert abs(calculate_distance("JN48QM", "QF67bf") - 16467) < 1
|
||||
|
||||
def test_calculate_distance_invalid_inputs(self):
|
||||
with pytest.raises(AttributeError):
|
||||
calculate_distance(5, 12)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
calculate_distance("XX0XX", "ZZ0Z")
|
||||
|
||||
def test_calculate_distance_longpath_normal_case(self):
|
||||
|
||||
assert abs(calculate_distance_longpath("JN48QM", "FN44AB") - 34042) < 1
|
||||
assert abs(calculate_distance_longpath("JN48QM", "QF67bf") - 23541) < 1
|
||||
|
||||
def test_calculate_distance_longpath_edge_cases(self):
|
||||
|
||||
assert abs(calculate_distance_longpath("JN48QM", "JN48QM") - 40008) < 1
|
||||
assert abs(calculate_distance_longpath("JN48QM", "AE15UU") - 20645) < 1 #ZL7 Chatham - almost antipods
|
||||
|
||||
|
||||
class Test_calculate_heading():
|
||||
|
||||
def test_calculate_heading_normal_cases(self):
|
||||
|
||||
assert abs(calculate_heading("JN48QM", "FN44AB") - 298) < 1
|
||||
assert abs(calculate_heading("FN44AB", "JN48QM") - 54) < 1
|
||||
assert abs(calculate_heading("JN48QM", "QF67bf") - 74) < 1
|
||||
assert abs(calculate_heading("QF67BF", "JN48QM") - 310) < 1
|
||||
|
||||
def test_calculate_heading_edge_cases(self):
|
||||
|
||||
assert abs(calculate_heading("JN48QM", "JN48QM") - 0 ) < 1
|
||||
|
||||
def test_calculate_heading_longpath(self):
|
||||
|
||||
assert abs(calculate_heading_longpath("JN48QM", "FN44AB") - 118) < 1
|
||||
assert abs(calculate_heading_longpath("FN44AB", "JN48QM") - 234) < 1
|
||||
assert abs(calculate_heading_longpath("JN48QM", "QF67BF") - 254) < 1
|
||||
assert abs(calculate_heading_longpath("QF67BF", "JN48QM") - 130) < 1
|
||||
|
||||
def test_calculate_heading_longpath_edge_cases(self):
|
||||
|
||||
assert abs(calculate_heading_longpath("JN48QM", "JN48QM") - 180 ) < 1
|
||||
|
||||
33
test/test_locator_latlong_to_locator.py
Normal file
33
test/test_locator_latlong_to_locator.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import pytest
|
||||
from pyhamtools.locator import latlong_to_locator
|
||||
from pyhamtools.consts import LookupConventions as const
|
||||
|
||||
class Test_latlong_to_locator():
|
||||
|
||||
def test_latlong_to_locator_edge_cases(self):
|
||||
assert latlong_to_locator(-89.97916, -179.95833) == "AA00AA"
|
||||
assert latlong_to_locator(89.97916, 179.9583) == "RR99XX"
|
||||
|
||||
def test_latlong_to_locator_normal_case(self):
|
||||
|
||||
assert latlong_to_locator(48.52083, 9.3750000) == "JN48QM"
|
||||
assert latlong_to_locator(48.5, 9.0) == "JN48MM" #center of the square
|
||||
|
||||
def test_latlong_to_locator_invalid_characters(self):
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latlong_to_locator("JN48QM", "test")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latlong_to_locator("", "")
|
||||
|
||||
def test_latlong_to_locator_out_of_boundry(self):
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latlong_to_locator(-90, -180)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latlong_to_locator(90, 180)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latlong_to_locator(10000, 120000)
|
||||
58
test/test_locator_locator_to_latlong.py
Normal file
58
test/test_locator_locator_to_latlong.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import pytest
|
||||
from pyhamtools.locator import locator_to_latlong
|
||||
from pyhamtools.consts import LookupConventions as const
|
||||
|
||||
class Test_locator_to_latlong():
|
||||
|
||||
def test_locator_to_latlong_edge_cases(self):
|
||||
latitude, longitude = locator_to_latlong("AA00AA")
|
||||
assert abs(latitude + 89.97916) < 0.00001
|
||||
assert abs(longitude +179.95833) < 0.0001
|
||||
|
||||
latitude, longitude = locator_to_latlong("RR99XX")
|
||||
assert abs(latitude - 89.97916) < 0.00001
|
||||
assert abs(longitude - 179.9583) < 0.0001
|
||||
|
||||
def test_locator_to_latlong_normal_case(self):
|
||||
|
||||
latitude, longitude = locator_to_latlong("JN48QM")
|
||||
assert abs(latitude - 48.52083) < 0.00001
|
||||
assert abs(longitude - 9.3750000) < 0.0001
|
||||
|
||||
latitude, longitude = locator_to_latlong("JN48")
|
||||
assert abs(latitude - 48.5) < 0.001
|
||||
assert abs(longitude - 9.000) < 0.001
|
||||
|
||||
def test_locator_to_latlong_mixed_signs(self):
|
||||
|
||||
latitude, longitude = locator_to_latlong("Jn48qM")
|
||||
assert abs(latitude - 48.52083) < 0.00001
|
||||
assert abs(longitude - 9.3750000) < 0.0001
|
||||
|
||||
|
||||
def test_locator_to_latlong_wrong_amount_of_characters(self):
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latitude, longitude = locator_to_latlong("J")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latitude, longitude = locator_to_latlong("JN")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latitude, longitude = locator_to_latlong("JN4")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latitude, longitude = locator_to_latlong("JN8Q")
|
||||
|
||||
def test_locator_to_latlong_invalid_characters(self):
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latitude, longitude = locator_to_latlong("21XM99")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latitude, longitude = locator_to_latlong("****")
|
||||
|
||||
def test_locator_to_latlong_out_of_boundry(self):
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
latitude, longitude = locator_to_latlong("RR99XY")
|
||||
57
test/test_locator_sunrise_sunset.py
Normal file
57
test/test_locator_sunrise_sunset.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
from pyhamtools.locator import calculate_sunrise_sunset
|
||||
|
||||
UTC = pytz.UTC
|
||||
|
||||
class Test_calculate_sunrise_sunset_normal_case():
|
||||
|
||||
def test_calculate_sunrise_sunset(self):
|
||||
|
||||
time_margin = timedelta(minutes=1)
|
||||
locator = "JN48QM"
|
||||
|
||||
test_time = datetime(year=2014, month=1, day=1, tzinfo=UTC)
|
||||
result_JN48QM_1_1_2014_evening_dawn = datetime(2014, 1, 1, 15, 38, tzinfo=UTC)
|
||||
result_JN48QM_1_1_2014_morning_dawn = datetime(2014, 1, 1, 6, 36, tzinfo=UTC)
|
||||
result_JN48QM_1_1_2014_sunrise = datetime(2014, 1, 1, 7, 14, tzinfo=UTC)
|
||||
result_JN48QM_1_1_2014_sunset = datetime(2014, 1, 1, 16, 15, 23, 31016, tzinfo=UTC)
|
||||
|
||||
assert calculate_sunrise_sunset(locator, test_time)['morning_dawn'] - result_JN48QM_1_1_2014_morning_dawn < time_margin
|
||||
assert calculate_sunrise_sunset(locator, test_time)['evening_dawn'] - result_JN48QM_1_1_2014_evening_dawn < time_margin
|
||||
assert calculate_sunrise_sunset(locator, test_time)['sunset'] - result_JN48QM_1_1_2014_sunset < time_margin
|
||||
assert calculate_sunrise_sunset(locator, test_time)['sunrise'] - result_JN48QM_1_1_2014_sunrise < time_margin
|
||||
|
||||
def test_calculate_distance_edge_case(self):
|
||||
|
||||
time_margin = timedelta(minutes=1)
|
||||
locator = "AA00AA"
|
||||
# no sunrise or sunset at southpol during arctic summer
|
||||
|
||||
test_time = datetime(year=2014, month=1, day=1, tzinfo=UTC)
|
||||
result_AA00AA_1_1_2014_evening_dawn = datetime(2014, 1, 1, 15, 38, tzinfo=UTC)
|
||||
result_AA00AA_1_1_2014_morning_dawn = datetime(2014, 1, 1, 6, 36, tzinfo=UTC)
|
||||
result_AA00AA_1_1_2014_sunrise = datetime(2014, 1, 1, 7, 14, tzinfo=UTC)
|
||||
result_AA00AA_1_1_2014_sunset = datetime(2014, 1, 1, 16, 15, 23, 31016, tzinfo=UTC)
|
||||
|
||||
assert calculate_sunrise_sunset(locator, test_time)['morning_dawn'] == None
|
||||
assert calculate_sunrise_sunset(locator, test_time)['evening_dawn'] == None
|
||||
assert calculate_sunrise_sunset(locator, test_time)['sunset'] == None
|
||||
assert calculate_sunrise_sunset(locator, test_time)['sunrise'] == None
|
||||
|
||||
def test_calculate_distance_invalid_inputs(self):
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
calculate_sunrise_sunset("", "")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
calculate_sunrise_sunset("JN48QM", "")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
calculate_sunrise_sunset("JN48", 55)
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
calculate_sunrise_sunset(33, datetime.now())
|
||||
Loading…
Reference in a new issue