Compare commits

..

No commits in common. "master" and "v0.9.0" have entirely different histories.

20 changed files with 140 additions and 363 deletions

2
.github/CODEOWNERS vendored
View file

@ -1,2 +0,0 @@
# global code owner
@DH1TW

View file

@ -3,17 +3,61 @@ name: Linux
on: [push, pull_request]
jobs:
test_linux:
runs-on: "ubuntu-24.04"
name: "Ubuntu 24.04 - Python ${{ matrix.python-version }}"
# Ubuntu 20.04 is still required for python 3.6; this doesn't work on Ubuntu 22.04 anymore
runs-on: "ubuntu-20.04"
name: "Ubuntu 20.04 - Python ${{ matrix.python-version }}"
strategy:
matrix:
python-version: ["3.6"]
redis-version: [6]
steps:
- uses: "actions/checkout@v3"
- uses: "actions/setup-python@v4"
with:
python-version: "${{ matrix.python-version }}"
cache: "pip"
cache-dependency-path: |
**/setup.py
**/requirements*.txt
- name: "Install dependencies"
run: |
set -xe
sudo apt-get install -y libxml2-dev libxslt-dev
python -VV
python -m pip install --upgrade pip setuptools
python -m pip install -e .
python -m pip install -r requirements-pytest.txt
- name: Start Redis
uses: supercharge/redis-github-action@1.2.0
with:
redis-version: ${{ matrix.redis-version }}
- name: "Run tests for ${{ matrix.python-version }}"
env:
CLUBLOG_APIKEY: ${{ secrets.CLUBLOG_APIKEY }}
QRZ_USERNAME: ${{ secrets.QRZ_USERNAME }}
QRZ_PWD: ${{ secrets.QRZ_PWD }}
PYTHON_VERSION: ${{ matrix.python-version }}
# delay the execution randomly by a couple of seconds to reduce the amount
# of concurrent API calls on Clublog and QRZ.com when all CI jobs execute simultaneously
run: |
sleep $[ ( $RANDOM % 10 ) + 1 ]s
pytest ./test
test_linux_ubuntu_22:
runs-on: "ubuntu-22.04"
name: "Ubuntu 22.04 - Python ${{ matrix.python-version }}"
env:
USING_COVERAGE: '3.11'
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy3.8", "pypy3.9", "pypy3.10"]
redis-version: [7]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.7", "pypy3.8", "pypy3.9", "pypy3.10"]
redis-version: [6]
steps:
- uses: "actions/checkout@v3"
@ -49,7 +93,7 @@ jobs:
# delay the execution randomly by a couple of seconds to reduce the amount
# of concurrent API calls on Clublog and QRZ.com when all CI jobs execute simultaneously
run: |
sleep $[ ( $RANDOM % 60 ) + 1 ]s
sleep $[ ( $RANDOM % 10 ) + 1 ]s
if [[ $PYTHON_VERSION == 3.11 ]]
then
pytest --cov=test/
@ -72,12 +116,13 @@ jobs:
test_macos:
runs-on: "macos-15"
name: "MacOS 15 - Python ${{ matrix.python-version }}"
# Ubuntu 20.04 is still required for python 3.6; this doesn't work on Ubuntu 22.04 anymore
runs-on: "macos-12"
name: "MacOS 12 - Python ${{ matrix.python-version }}"
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy3.8", "pypy3.9", "pypy3.10"]
redis-version: [7.2]
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.8", "pypy3.9", "pypy3.10"]
redis-version: [6]
steps:
- uses: "actions/checkout@v3"
@ -98,7 +143,7 @@ jobs:
python -m pip install -r requirements-pytest.txt
- name: Start Redis
uses: shogo82148/actions-setup-redis@v1
uses: shogo82148/actions-setup-redis@v1.31.1
with:
redis-version: ${{ matrix.redis-version }}
@ -111,16 +156,16 @@ jobs:
# delay the execution randomly by a couple of seconds to reduce the amount
# of concurrent API calls on Clublog and QRZ.com when all CI jobs execute simultaneously
run: |
sleep $[ ( $RANDOM % 60 ) + 1 ]
sleep $[ ( $RANDOM % 10 ) + 1 ]
pytest ./test
test_windows:
runs-on: "windows-2022"
runs-on: "windows-latest"
name: "Windows latest - Python ${{ matrix.python-version }}"
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- uses: "actions/checkout@v3"
@ -145,13 +190,10 @@ jobs:
# since there is no official redis version for windows.
# Redis is then installed an run as a service
run: |
C:\msys64\usr\bin\wget.exe https://github.com/redis-windows/redis-windows/releases/download/7.0.14/Redis-7.0.14-Windows-x64-msys2-with-Service.zip
C:\msys64\usr\bin\pacman.exe -S --noconfirm unzip
C:\msys64\usr\bin\unzip.exe Redis-7.0.14-Windows-x64-msys2-with-Service.zip
sc.exe create Redis binpath=${{ github.workspace }}\Redis-7.0.14-Windows-x64-msys2-with-Service\RedisService.exe start= auto
echo "Redis service created, now starting it"
C:\msys64\usr\bin\wget.exe https://github.com/redis-windows/redis-windows/releases/download/7.0.14/Redis-7.0.14-Windows-x64-with-Service.tar.gz
C:\msys64\usr\bin\tar.exe -xvzf Redis-7.0.14-Windows-x64-with-Service.tar.gz
sc.exe create Redis binpath=D:\a\pyhamtools\pyhamtools\Redis-7.0.14-Windows-x64-with-Service\RedisService.exe start= auto
net start Redis
echo "Redis service started"
- name: "Run tests for ${{ matrix.python-version }}"
env:
@ -164,5 +206,5 @@ jobs:
# amount of concurrent API calls on Clublog and QRZ.com
# when all CI jobs execute simultaneously
run: |
start-sleep -Seconds (5..60 | get-random)
start-sleep -Seconds (5..20 | get-random)
pytest

View file

@ -1,33 +0,0 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
# fail_on_warning: true
# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub
# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- method: setuptools
path: .
- requirements: readthedocs-pip-requirements.txt

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2025 Tobias Wellnitz
Copyright (c) 2014 Tobias Wellnitz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -34,21 +34,19 @@ This Library is used in production at the [DXHeat.com DX Cluster](https://dxheat
Pyhamtools is compatible with Python >=3.6.
We check compatibility on OSX, Windows, and Linux with the following Python versions:
* Python 3.6 (will be deprecated in 2024)
* Python 3.7 (will be deprecated in 2024)
* Python 3.8
* Python 3.9
* Python 3.10
* Python 3.11
* Python 3.12
* Python 3.13
* [pypy3.7](https://pypy.org/) (will be deprecated in 2024)
* [pypy3.8](https://pypy.org/)
* [pypy3.9](https://pypy.org/)
* [pypy3.10](https://pypy.org/)
### depreciated: Python 2.7 & Python 3.5
The support for Python 2.7 and 3.5 has been deprecated at the end of 2023. The last version which supports Python 2.7 and Python 3.5 is 0.8.7.
### depricated: Python 3.6 & Python 3.7
Support for Python 3.6 and Python 3.7 has been deprecated in June 2025. The last version which support Python 3.6 and Python 3.7 is 0.11.0.
The support for Python 2.7 and 3.5 has been deprecated at the end of 2023. The last version which supports Python 2.7 and Python 3.5 is 0.8.7.
## Documentation
@ -64,7 +62,9 @@ Open Source Software licenses, including the MIT license at [choosealicense.com]
Starting with version 0.8.0, `libxml2-dev` and `libxslt-dev` are required dependencies.
There is a good change that the libraries are already installed on your system. If not, you can install them with the package manager of your distro. For example on Debian / Ubuntu based distros the corresponding command is:
## Installation
Install the dependencies (e.g. on Debian/Ubuntu):
```bash
@ -72,12 +72,6 @@ $ sudo apt-get install libxml2-dev libxslt-dev
```
You don't need to install these libraries manually on Windows / MacOS.
## Installation
The easiest way to install pyhamtools is through the packet manager `pip`:
```bash

View file

@ -1,40 +1,6 @@
Changelog
---------
PyHamtools 0.12.0
================
09. June 2025
* deprecated support for Python 3.6
* deprecated support for Python 3.7
* added support for higher Microwave bands (tnx @sq6emm)
* added support for 10 characters Maidenhead locators (tnx @sq6emm)
* updated CI pipeline
PyHamtools 0.11.0
================
02. March 2025
* added support for Python 3.13
PyHamtools 0.10.0
================
01. June 2024
* full support for 4, 6, 8 characters Maidenhead locator conversions
PyHamtools 0.9.1
================
17. March 2024
* switched from distutils to setuptools. No impact for endusers.
PyHamtools 0.9.0
================

View file

@ -25,7 +25,6 @@ from pyhamtools.version import __version__, __release__
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'sphinx_rtd_dark_mode',
]
# Add any paths that contain templates here, relative to this directory.
@ -96,9 +95,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# html_theme = 'default'
html_theme = 'sphinx_rtd_theme'
# html_theme = 'sphinx_material'
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the

View file

@ -182,7 +182,7 @@ def freq_to_band(freq):
elif ((freq >= 1200000) and (freq <= 1300000)):
band = 0.23 #23cm
mode = None
elif ((freq >= 2300000) and (freq <= 2450000)):
elif ((freq >= 2390000) and (freq <= 2450000)):
band = 0.13 #13cm
mode = None
elif ((freq >= 3300000) and (freq <= 3500000)):
@ -200,19 +200,7 @@ def freq_to_band(freq):
elif ((freq >= 47000000) and (freq <= 47200000)):
band = 0.0063 #6,3mm
mode = None
elif ((freq >= 75500000) and (freq <= 81500000)):
band = 0.004 #4mm
mode = None
elif ((freq >= 122250000) and (freq <= 123000000)):
band = 0.0025 #2.5mm
mode = None
elif ((freq >= 134000000) and (freq <= 141000000)):
band = 0.002 #2mm
mode = None
elif ((freq >= 241000000) and (freq <= 250000000)):
band = 0.001 #1mm
mode = None
else:
raise KeyError
return {"band": band, "mode": mode}
return {"band": band, "mode": mode}

View file

@ -3,13 +3,12 @@ from datetime import datetime, timezone
import ephem
def latlong_to_locator (latitude, longitude, precision=6):
def latlong_to_locator (latitude, longitude):
"""converts WGS84 coordinates into the corresponding Maidenhead Locator
Args:
latitude (float): Latitude
longitude (float): Longitude
precision (int): 4,6,8,10 chars (default 6)
Returns:
string: Maidenhead locator
@ -33,54 +32,35 @@ def latlong_to_locator (latitude, longitude, precision=6):
"""
if precision < 4 or precision == 5 or precision == 7 or precision == 9 or precision > 10:
return ValueError
if longitude >= 180 or longitude <= -180:
raise ValueError
if latitude >= 90 or latitude <= -90:
raise ValueError
longitude +=180
latitude +=90
longitude += 180;
latitude +=90;
# copied & adapted from github.com/space-physics/maidenhead
A = ord('A')
a = divmod(longitude, 20)
b = divmod(latitude, 10)
locator = chr(A + int(a[0])) + chr(A + int(b[0]))
lon = a[1] / 2.0
lat = b[1]
i = 1
while i < precision/2:
i += 1
a = divmod(lon, 1)
b = divmod(lat, 1)
if not (i % 2):
locator += str(int(a[0])) + str(int(b[0]))
lon = 24 * a[1]
lat = 24 * b[1]
else:
locator += chr(A + int(a[0])) + chr(A + int(b[0]))
lon = 10 * a[1]
lat = 10 * b[1]
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, center=True):
def locator_to_latlong (locator):
"""converts Maidenhead locator in the corresponding WGS84 coordinates
Args:
locator (string): Locator, either 4, 6 or 8 characters
center (bool): Center of (sub)square. By default True. If False, the south/western corner will be returned
locator (string): Locator, either 4 or 6 characters
Returns:
tuple (float, float): Latitude, Longitude
Raises:
ValueError: When called with wrong or invalid Maidenhead locator string
ValueError: When called with wrong or invalid input arg
TypeError: When arg is not a string
Example:
@ -99,7 +79,7 @@ def locator_to_latlong (locator, center=True):
locator = locator.upper()
if len(locator) < 4 or len(locator) == 5 or len(locator) == 7 or len(locator) == 9:
if len(locator) == 5 or len(locator) < 4:
raise ValueError
if ord(locator[0]) > ord('R') or ord(locator[0]) < ord('A'):
@ -120,64 +100,23 @@ def locator_to_latlong (locator, center=True):
if ord (locator[5]) > ord('X') or ord(locator[5]) < ord('A'):
raise ValueError
if len(locator) == 8:
if ord(locator[6]) > ord('9') or ord(locator[6]) < ord('0'):
raise ValueError
if ord (locator[7]) > ord('9') or ord(locator[7]) < ord('0'):
raise ValueError
if len(locator) == 10:
if ord(locator[8]) > ord('X') or ord(locator[8]) < ord('A'):
raise ValueError
if ord (locator[9]) > ord('X') or ord(locator[9]) < 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')) * 1
latitude += (ord(locator[3]) - ord('0'))
if len(locator) == 4:
if len(locator) == 6:
longitude += ((ord(locator[4])) - ord('A')) * (2 / 24)
latitude += ((ord(locator[5])) - ord('A')) * (1 / 24)
if center:
longitude += 2 / 2
latitude += 1.0 / 2
elif len(locator) == 6:
longitude += (ord(locator[4]) - ord('A')) * 5.0 / 60
latitude += (ord(locator[5]) - ord('A')) * 2.5 / 60
if center:
longitude += 5.0 / 60 / 2
latitude += 2.5 / 60 / 2
elif len(locator) == 8:
longitude += (ord(locator[4]) - ord('A')) * 5.0 / 60
latitude += (ord(locator[5]) - ord('A')) * 2.5 / 60
longitude += int(locator[6]) * 5.0 / 600
latitude += int(locator[7]) * 2.5 / 600
if center:
longitude += 5.0 / 600 / 2
latitude += 2.5 / 600 / 2
elif len(locator) == 10:
longitude += (ord(locator[4]) - ord('A')) * 5.0 / 60
latitude += (ord(locator[5]) - ord('A')) * 2.5 / 60
longitude += int(locator[6]) * 5.0 / 600
latitude += int(locator[7]) * 2.5 / 600
longitude += (ord(locator[8]) - ord('A')) * 1.0 / 2880
latitude += (ord(locator[9]) - ord('A')) * 1.0 / 5760
if center:
longitude += 1.0 / 2880 / 2
latitude += 1.0 / 5760 / 2
# move to center of subsquare
longitude += 1 / 24
latitude += 0.5 / 24
else:
raise ValueError
# move to center of square
longitude += 1;
latitude += 0.5;
return latitude, longitude
@ -186,14 +125,14 @@ def calculate_distance(locator1, locator2):
"""calculates the (shortpath) distance between two Maidenhead locators
Args:
locator1 (string): Locator, either 4, 6 or 8 characters
locator2 (string): Locator, either 4, 6 or 8 characters
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 maidenhead locator strings
ValueError: When called with wrong or invalid input arg
AttributeError: When args are not a string
Example:
@ -203,9 +142,6 @@ def calculate_distance(locator1, locator2):
>>> calculate_distance("JN48QM", "QF67bf")
16466.413
Note:
Distances is calculated between the centers of the (sub) squares
"""
R = 6371 #earh radius
@ -224,15 +160,15 @@ def calculate_distance(locator1, locator2):
c = 2 * atan2(sqrt(a), sqrt(1-a))
d = R * c #distance in km
return d
return d;
def calculate_distance_longpath(locator1, locator2):
"""calculates the (longpath) distance between two Maidenhead locators
Args:
locator1 (string): Locator, either 4, 6 or 8 characters
locator2 (string): Locator, either 4, 6 or 8 characters
locator1 (string): Locator, either 4 or 6 characters
locator2 (string): Locator, either 4 or 6 characters
Returns:
float: Distance in km
@ -248,8 +184,6 @@ def calculate_distance_longpath(locator1, locator2):
>>> calculate_distance_longpath("JN48QM", "QF67bf")
23541.5867
Note:
Distance is calculated between the centers of the (sub) squares
"""
c = 40008 #[km] earth circumference
@ -262,8 +196,8 @@ def calculate_heading(locator1, locator2):
"""calculates the heading from the first to the second locator
Args:
locator1 (string): Locator, either 4, 6 or 8 characters
locator2 (string): Locator, either 4, 6 or 6 characters
locator1 (string): Locator, either 4 or 6 characters
locator2 (string): Locator, either 4 or 6 characters
Returns:
float: Heading in deg
@ -279,9 +213,6 @@ def calculate_heading(locator1, locator2):
>>> calculate_heading("JN48QM", "QF67bf")
74.3136
Note:
Heading is calculated between the centers of the (sub) squares
"""
lat1, long1 = locator_to_latlong(locator1)
@ -305,8 +236,8 @@ def calculate_heading_longpath(locator1, locator2):
"""calculates the heading from the first to the second locator (long path)
Args:
locator1 (string): Locator, either 4, 6 or 8 characters
locator2 (string): Locator, either 4, 6 or 8 characters
locator1 (string): Locator, either 4 or 6 characters
locator2 (string): Locator, either 4 or 6 characters
Returns:
float: Long path heading in deg
@ -322,9 +253,6 @@ def calculate_heading_longpath(locator1, locator2):
>>> calculate_heading_longpath("JN48QM", "QF67bf")
254.3136
Note:
Distance is calculated between the centers of the (sub) squares
"""
heading = calculate_heading(locator1, locator2)
@ -337,7 +265,7 @@ def calculate_sunrise_sunset(locator, calc_date=None):
"""calculates the next sunset and sunrise for a Maidenhead locator at a give date & time
Args:
locator1 (string): Maidenhead Locator, either 4, 6 or 8 characters
locator1 (string): Maidenhead Locator, either 4 or 6 characters
calc_date (datetime, optional): Starting datetime for the calculations (UTC)
Returns:

View file

@ -1,3 +1,3 @@
VERSION = (0, 12, 0)
VERSION = (0, 9, 0)
__release__ = ''.join(['-.'[type(x) == int]+str(x) for x in VERSION])[1:]
__version__ = '.'.join((str(VERSION[0]), str(VERSION[1])))

View file

@ -1,5 +1,3 @@
sphinx>=1.8.5
sphinxcontrib-napoleon>=0.7
beautifulsoup4>=4.7.1
sphinx_rtd_theme>=0.5.2
sphinx_rtd_dark_mode>=0.1.2
beautifulsoup4>=4.7.1

View file

@ -1,9 +1,5 @@
pytest>=7.0.0
pytest>=7.0.0; python_version>='3.7'
pytest==4.6.11; python_version=='3.6'
pytest-blockage>=0.2.2
pytest-localserver>=0.5
pytest-cov>=2.12
maidenhead==1.6.0
requests>=2.32.4
beautifulsoup4==4.13.4
redis==5.2.1
ephem==4.2

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python
import os
from setuptools import setup
from distutils.core import setup
kw = {}
@ -18,7 +18,7 @@ setup(name='pyhamtools',
"requests>=2.21.0",
"ephem>=4.1.3",
"beautifulsoup4>=4.7.1",
"lxml>=5.0.0",
"lxml>=4.8.0",
"redis>=2.10.6",
],
**kw

View file

@ -90,12 +90,12 @@ response_prefix_VK9DWX_clublog = {
}
response_prefix_VK9DLX_clublog = {
u'adif': 189,
u'adif': 147,
u'continent': u'OC',
u'country': u'NORFOLK ISLAND',
u'cqz': 32,
u'latitude': -29.0,
u'longitude': 168.0
u'country': u'LORD HOWE ISLAND',
u'cqz': 30,
u'latitude': -31.6,
u'longitude': 159.1
}
response_prefix_TA7I_clublog = {
@ -126,13 +126,13 @@ response_prefix_V26K_clublog = {
}
response_prefix_VK9DLX_countryfile = {
u'adif': 189,
u'adif': 147,
u'continent': u'OC',
u'country': u'Norfolk Island',
u'cqz': 32,
u'country': u'Lord Howe Island',
u'cqz': 30,
u'ituz': 60,
u'latitude': -29.03,
u'longitude': 167.93
u'latitude': -31.55,
u'longitude': 159.08
}
response_prefix_VK9GMW_clublog = {
@ -195,7 +195,7 @@ response_Exception_VK9XO_with_start_date = {
'country': 'CHRISTMAS ISLAND',
'continent': 'OC',
'latitude': -10.48,
'longitude': 105.62,
'longitude': 105.71,
'cqz': 29
}

View file

@ -14,12 +14,7 @@ class Test_calculate_distance():
assert abs(calculate_distance("JN48QM", "FN44AB") - 5965) < 1
assert abs(calculate_distance("FN44AB", "JN48QM") - 5965) < 1
assert abs(calculate_distance("JN48QM", "QF67BF") - 16467) < 1
assert abs(calculate_distance("JN48QM84", "QF67BF84") - 16467) < 1
assert abs(calculate_distance("JN48QM84", "QF67BF") - 16464) < 1
assert abs(calculate_distance("JN48QM84", "QF67") - 16506) < 1
assert abs(calculate_distance("JN48QM", "QF67") - 16508) < 1
assert abs(calculate_distance("JN48", "QF67") - 16535) < 1
assert abs(calculate_distance("JN48QM", "QF67bf") - 16467) < 1
def test_calculate_distance_invalid_inputs(self):
with pytest.raises(AttributeError):

View file

@ -8,24 +8,10 @@ class Test_latlong_to_locator():
assert latlong_to_locator(-89.97916, -179.95833) == "AA00AA"
assert latlong_to_locator(89.97916, 179.9583) == "RR99XX"
def test_latlong_to_locator_4chars_precision(self):
assert latlong_to_locator(48.52083, 9.3750000, precision=4) == "JN48"
assert latlong_to_locator(39.222916, -86.45416, 4) == "EM69"
def test_latlong_to_locator_6chars_precision(self):
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
assert latlong_to_locator(39.222916, -86.45416, 6) == "EM69SF"
def test_latlong_to_locator_8chars_precision(self):
assert latlong_to_locator(48.51760, 9.40345, precision=8) == "JN48QM84"
assert latlong_to_locator(39.222916, -86.45416, 8) == "EM69SF53"
def test_latlong_to_locator_10chars_precision(self):
assert latlong_to_locator(45.835677, 68.525173, precision=10) == "MN45GU30AN"
assert latlong_to_locator(51.124913, 16.941840, 10) == "JO81LC39AX"
def test_latlong_to_locator_invalid_characters(self):

View file

@ -1,12 +1,10 @@
import pytest
import maidenhead
from pyhamtools.locator import locator_to_latlong
from pyhamtools.consts import LookupConventions as const
class Test_locator_to_latlong():
def test_locator_to_latlong_min_max_cases(self):
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
@ -15,79 +13,23 @@ class Test_locator_to_latlong():
assert abs(latitude - 89.97916) < 0.00001
assert abs(longitude - 179.9583) < 0.0001
def test_locator_to_latlong_4chars_precision(self):
def test_locator_to_latlong_normal_case(self):
latitude, longitude = locator_to_latlong("JN48")
assert abs(latitude - 48.5) < 0.1
assert abs(longitude - 9.0) < 0.1
latitude, longitude = locator_to_latlong("JN48", center=False)
assert abs(latitude - 48) < 0.1
assert abs(longitude - 8) < 0.1
def test_locator_to_latlong_6chars_precision(self):
latitude, longitude = locator_to_latlong("JN48QM")
assert abs(latitude - 48.52083) < 0.00001
assert abs(longitude - 9.37500) < 0.00001
assert abs(longitude - 9.3750000) < 0.0001
def test_locator_to_latlong_8chars_precision(self):
latitude, longitude = locator_to_latlong("JN48QM84")
assert abs(latitude - 48.51875) < 0.00001
assert abs(longitude - 9.40416) < 0.00001
latitude, longitude = locator_to_latlong("JN48")
assert abs(latitude - 48.5) < 0.001
assert abs(longitude - 9.000) < 0.001
latitude, longitude = locator_to_latlong("EM69SF53")
assert abs(latitude - 39.222916) < 0.00001
assert abs(longitude + 86.45416) < 0.00001
def test_locator_to_latlong_10chars_precision(self):
latitude, longitude = locator_to_latlong("JO81LC39AX")
assert abs(latitude - 51.124913) < 0.000001
assert abs(longitude - 16.941840) < 0.000001
latitude, longitude = locator_to_latlong("MN45GU30AN")
assert abs(latitude - 45.835677) < 0.000001
assert abs(longitude - 68.525173) < 0.000001
def test_locator_to_latlong_consistency_checks_6chars_lower_left_corner(self):
latitude_4, longitude_4 = locator_to_latlong("JN48", center=False)
latitude_6, longitude_6 = locator_to_latlong("JN48AA", center=False)
assert latitude_4 == latitude_6
assert longitude_4 == longitude_6
def test_locator_to_latlong_consistency_checks_8chars_lower_left_corner(self):
latitude_6, longitude_6 = locator_to_latlong("JN48AA", center=False)
latitude_8, longitude_8 = locator_to_latlong("JN48AA00", center=False)
assert latitude_6 == latitude_8
assert longitude_6 == longitude_8
def test_locator_to_latlong_consistency_checks_against_maidenhead(self):
locs = ["JN48", "EM69", "JN48QM", "EM69SF", "AA00AA", "RR99XX", "JN48QM84", "EM69SF53"]
# lower left (south/east) corner
for loc in locs:
lat, lon = locator_to_latlong(loc, center=False)
lat_m, lon_m = maidenhead.to_location(loc)
assert abs(lat - lat_m) < 0.00001
assert abs(lon - lon_m) < 0.00001
# center of square
for loc in locs:
lat, lon = locator_to_latlong(loc) # default: center=True
lat_m, lon_m = maidenhead.to_location(loc, center=True)
assert abs(lat - lat_m) < 0.1
assert abs(lon - lon_m) < 0.1
def test_locator_to_latlong_upper_lower_chars(self):
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):
@ -101,30 +43,12 @@ class Test_locator_to_latlong():
with pytest.raises(ValueError):
latitude, longitude = locator_to_latlong("JN8Q")
with pytest.raises(ValueError):
latitude, longitude = locator_to_latlong("JN8QM1")
with pytest.raises(ValueError):
latitude, longitude = locator_to_latlong("JN8QM1AA")
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("48")
with pytest.raises(ValueError):
latitude, longitude = locator_to_latlong("JNJN")
with pytest.raises(ValueError):
latitude, longitude = locator_to_latlong("JN4848")
with pytest.raises(ValueError):
latitude, longitude = locator_to_latlong("JN48QMaa")
with pytest.raises(ValueError):
latitude, longitude = locator_to_latlong("****")

View file

@ -25,8 +25,8 @@ response_Exception_KC6MM_1990 = {
'adif': 22,
'country': u'PALAU',
'continent': u'OC',
'latitude': 9.52,
'longitude': 138.21,
'latitude': 9.50,
'longitude': 138.20,
'cqz': 27,
}
@ -34,8 +34,8 @@ response_Exception_KC6MM_1992 = {
'adif': 22,
'country': u'PALAU',
'continent': u'OC',
'latitude': 9.52,
'longitude': 138.21,
'latitude': 9.50,
'longitude': 138.20,
'cqz': 27,
}
@ -53,7 +53,7 @@ response_Exception_VK9XO_with_start_date = {
'country': u'CHRISTMAS ISLAND',
'continent': u'OC',
'latitude': -10.48,
'longitude': 105.62,
'longitude': 105.71,
'cqz': 29,
}

View file

@ -29,7 +29,6 @@ class Test_lotw_methods:
execfile(os.path.join(fix_dir,"lotw_fixture.py"), namespace)
assert get_lotw_users(url=httpserver.url) == namespace['lotw_fixture']
@pytest.mark.skip("ARRL has been hacked in May 2024; skipping until LOTW is again up")
def test_download_lotw_list_and_check_types(self):
data = get_lotw_users()

View file

@ -65,7 +65,6 @@ class Test_utils_freq_to_band():
assert freq_to_band(1200000) == {"band" : 0.23, "mode":None}
def test_shf_frequencies(self):
assert freq_to_band(2320200) == {"band" : 0.13, "mode":None}
assert freq_to_band(2390000) == {"band" : 0.13, "mode":None}
assert freq_to_band(3300000) == {"band" : 0.09, "mode":None}