mirror of
https://github.com/dh1tw/pyhamtools.git
synced 2026-02-05 07:04:25 +01:00
Compare commits
21 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c15ab8c2c | ||
|
|
8ef752e3ad | ||
|
|
9c61f75c28 | ||
|
|
b51b32e575 | ||
|
|
a12616ceca | ||
|
|
1a79467db1 | ||
|
|
4bdaf8d335 | ||
|
|
940c0f072c | ||
|
|
ac444fa36b | ||
|
|
17117b1c20 | ||
|
|
1c3536396d | ||
|
|
5ec3461d03 | ||
|
|
84d88faf69 | ||
|
|
5799c7337b | ||
|
|
cea30d761a | ||
|
|
71b2a743db | ||
|
|
8d765b9346 | ||
|
|
f2b2f16806 | ||
|
|
27d61089a7 | ||
|
|
b5ba291e0d | ||
|
|
c447cdc6a8 |
2
.github/CODEOWNERS
vendored
Normal file
2
.github/CODEOWNERS
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# global code owner
|
||||||
|
@DH1TW
|
||||||
84
.github/workflows/test.yml
vendored
84
.github/workflows/test.yml
vendored
|
|
@ -3,61 +3,17 @@ name: Linux
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
test_linux:
|
test_linux:
|
||||||
# Ubuntu 20.04 is still required for python 3.6; this doesn't work on Ubuntu 22.04 anymore
|
runs-on: "ubuntu-24.04"
|
||||||
runs-on: "ubuntu-20.04"
|
name: "Ubuntu 24.04 - Python ${{ matrix.python-version }}"
|
||||||
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:
|
env:
|
||||||
USING_COVERAGE: '3.11'
|
USING_COVERAGE: '3.11'
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.7", "pypy3.8", "pypy3.9", "pypy3.10"]
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy3.8", "pypy3.9", "pypy3.10"]
|
||||||
redis-version: [6]
|
redis-version: [7]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@v3"
|
- uses: "actions/checkout@v3"
|
||||||
|
|
@ -93,7 +49,7 @@ jobs:
|
||||||
# delay the execution randomly by a couple of seconds to reduce the amount
|
# 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
|
# of concurrent API calls on Clublog and QRZ.com when all CI jobs execute simultaneously
|
||||||
run: |
|
run: |
|
||||||
sleep $[ ( $RANDOM % 10 ) + 1 ]s
|
sleep $[ ( $RANDOM % 60 ) + 1 ]s
|
||||||
if [[ $PYTHON_VERSION == 3.11 ]]
|
if [[ $PYTHON_VERSION == 3.11 ]]
|
||||||
then
|
then
|
||||||
pytest --cov=test/
|
pytest --cov=test/
|
||||||
|
|
@ -116,13 +72,12 @@ jobs:
|
||||||
|
|
||||||
|
|
||||||
test_macos:
|
test_macos:
|
||||||
# Ubuntu 20.04 is still required for python 3.6; this doesn't work on Ubuntu 22.04 anymore
|
runs-on: "macos-15"
|
||||||
runs-on: "macos-12"
|
name: "MacOS 15 - Python ${{ matrix.python-version }}"
|
||||||
name: "MacOS 12 - Python ${{ matrix.python-version }}"
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.8", "pypy3.9", "pypy3.10"]
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy3.8", "pypy3.9", "pypy3.10"]
|
||||||
redis-version: [6]
|
redis-version: [7.2]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@v3"
|
- uses: "actions/checkout@v3"
|
||||||
|
|
@ -143,7 +98,7 @@ jobs:
|
||||||
python -m pip install -r requirements-pytest.txt
|
python -m pip install -r requirements-pytest.txt
|
||||||
|
|
||||||
- name: Start Redis
|
- name: Start Redis
|
||||||
uses: shogo82148/actions-setup-redis@v1.31.1
|
uses: shogo82148/actions-setup-redis@v1
|
||||||
with:
|
with:
|
||||||
redis-version: ${{ matrix.redis-version }}
|
redis-version: ${{ matrix.redis-version }}
|
||||||
|
|
||||||
|
|
@ -156,16 +111,16 @@ jobs:
|
||||||
# delay the execution randomly by a couple of seconds to reduce the amount
|
# 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
|
# of concurrent API calls on Clublog and QRZ.com when all CI jobs execute simultaneously
|
||||||
run: |
|
run: |
|
||||||
sleep $[ ( $RANDOM % 10 ) + 1 ]
|
sleep $[ ( $RANDOM % 60 ) + 1 ]
|
||||||
pytest ./test
|
pytest ./test
|
||||||
|
|
||||||
test_windows:
|
test_windows:
|
||||||
runs-on: "windows-latest"
|
runs-on: "windows-2022"
|
||||||
name: "Windows latest - Python ${{ matrix.python-version }}"
|
name: "Windows latest - Python ${{ matrix.python-version }}"
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@v3"
|
- uses: "actions/checkout@v3"
|
||||||
|
|
@ -190,10 +145,13 @@ jobs:
|
||||||
# since there is no official redis version for windows.
|
# since there is no official redis version for windows.
|
||||||
# Redis is then installed an run as a service
|
# Redis is then installed an run as a service
|
||||||
run: |
|
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-with-Service.tar.gz
|
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\tar.exe -xvzf Redis-7.0.14-Windows-x64-with-Service.tar.gz
|
C:\msys64\usr\bin\pacman.exe -S --noconfirm unzip
|
||||||
sc.exe create Redis binpath=D:\a\pyhamtools\pyhamtools\Redis-7.0.14-Windows-x64-with-Service\RedisService.exe start= auto
|
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"
|
||||||
net start Redis
|
net start Redis
|
||||||
|
echo "Redis service started"
|
||||||
|
|
||||||
- name: "Run tests for ${{ matrix.python-version }}"
|
- name: "Run tests for ${{ matrix.python-version }}"
|
||||||
env:
|
env:
|
||||||
|
|
@ -206,5 +164,5 @@ jobs:
|
||||||
# amount of concurrent API calls on Clublog and QRZ.com
|
# amount of concurrent API calls on Clublog and QRZ.com
|
||||||
# when all CI jobs execute simultaneously
|
# when all CI jobs execute simultaneously
|
||||||
run: |
|
run: |
|
||||||
start-sleep -Seconds (5..20 | get-random)
|
start-sleep -Seconds (5..60 | get-random)
|
||||||
pytest
|
pytest
|
||||||
33
.readthedocs.yaml
Normal file
33
.readthedocs.yaml
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# 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
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 Tobias Wellnitz
|
Copyright (c) 2025 Tobias Wellnitz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
20
README.md
20
README.md
|
|
@ -34,19 +34,21 @@ This Library is used in production at the [DXHeat.com DX Cluster](https://dxheat
|
||||||
Pyhamtools is compatible with Python >=3.6.
|
Pyhamtools is compatible with Python >=3.6.
|
||||||
We check compatibility on OSX, Windows, and Linux with the following Python versions:
|
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.8
|
||||||
* Python 3.9
|
* Python 3.9
|
||||||
* Python 3.10
|
* Python 3.10
|
||||||
* Python 3.11
|
* Python 3.11
|
||||||
* Python 3.12
|
* Python 3.12
|
||||||
* [pypy3.7](https://pypy.org/) (will be deprecated in 2024)
|
* Python 3.13
|
||||||
* [pypy3.8](https://pypy.org/)
|
* [pypy3.8](https://pypy.org/)
|
||||||
* [pypy3.9](https://pypy.org/)
|
* [pypy3.9](https://pypy.org/)
|
||||||
* [pypy3.10](https://pypy.org/)
|
* [pypy3.10](https://pypy.org/)
|
||||||
|
|
||||||
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.
|
### 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.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|
@ -62,9 +64,7 @@ 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.
|
Starting with version 0.8.0, `libxml2-dev` and `libxslt-dev` are required dependencies.
|
||||||
|
|
||||||
## Installation
|
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:
|
||||||
|
|
||||||
Install the dependencies (e.g. on Debian/Ubuntu):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
||||||
|
|
@ -72,6 +72,12 @@ $ 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`:
|
The easiest way to install pyhamtools is through the packet manager `pip`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,40 @@
|
||||||
Changelog
|
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
|
PyHamtools 0.9.0
|
||||||
================
|
================
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ from pyhamtools.version import __version__, __release__
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
'sphinx.ext.napoleon',
|
'sphinx.ext.napoleon',
|
||||||
|
'sphinx_rtd_dark_mode',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
|
@ -95,7 +96,9 @@ pygments_style = 'sphinx'
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'default'
|
# html_theme = 'default'
|
||||||
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
# html_theme = 'sphinx_material'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# 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
|
# further. For a list of options available for each theme, see the
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ def freq_to_band(freq):
|
||||||
elif ((freq >= 1200000) and (freq <= 1300000)):
|
elif ((freq >= 1200000) and (freq <= 1300000)):
|
||||||
band = 0.23 #23cm
|
band = 0.23 #23cm
|
||||||
mode = None
|
mode = None
|
||||||
elif ((freq >= 2390000) and (freq <= 2450000)):
|
elif ((freq >= 2300000) and (freq <= 2450000)):
|
||||||
band = 0.13 #13cm
|
band = 0.13 #13cm
|
||||||
mode = None
|
mode = None
|
||||||
elif ((freq >= 3300000) and (freq <= 3500000)):
|
elif ((freq >= 3300000) and (freq <= 3500000)):
|
||||||
|
|
@ -200,7 +200,19 @@ def freq_to_band(freq):
|
||||||
elif ((freq >= 47000000) and (freq <= 47200000)):
|
elif ((freq >= 47000000) and (freq <= 47200000)):
|
||||||
band = 0.0063 #6,3mm
|
band = 0.0063 #6,3mm
|
||||||
mode = None
|
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:
|
else:
|
||||||
raise KeyError
|
raise KeyError
|
||||||
|
|
||||||
return {"band": band, "mode": mode}
|
return {"band": band, "mode": mode}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ from datetime import datetime, timezone
|
||||||
|
|
||||||
import ephem
|
import ephem
|
||||||
|
|
||||||
def latlong_to_locator (latitude, longitude):
|
def latlong_to_locator (latitude, longitude, precision=6):
|
||||||
"""converts WGS84 coordinates into the corresponding Maidenhead Locator
|
"""converts WGS84 coordinates into the corresponding Maidenhead Locator
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
latitude (float): Latitude
|
latitude (float): Latitude
|
||||||
longitude (float): Longitude
|
longitude (float): Longitude
|
||||||
|
precision (int): 4,6,8,10 chars (default 6)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string: Maidenhead locator
|
string: Maidenhead locator
|
||||||
|
|
@ -32,35 +33,54 @@ def latlong_to_locator (latitude, longitude):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if precision < 4 or precision == 5 or precision == 7 or precision == 9 or precision > 10:
|
||||||
|
return ValueError
|
||||||
|
|
||||||
if longitude >= 180 or longitude <= -180:
|
if longitude >= 180 or longitude <= -180:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
if latitude >= 90 or latitude <= -90:
|
if latitude >= 90 or latitude <= -90:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
longitude += 180;
|
longitude +=180
|
||||||
latitude +=90;
|
latitude +=90
|
||||||
|
|
||||||
locator = chr(ord('A') + int(longitude / 20))
|
# copied & adapted from github.com/space-physics/maidenhead
|
||||||
locator += chr(ord('A') + int(latitude / 10))
|
A = ord('A')
|
||||||
locator += chr(ord('0') + int((longitude % 20) / 2))
|
a = divmod(longitude, 20)
|
||||||
locator += chr(ord('0') + int(latitude % 10))
|
b = divmod(latitude, 10)
|
||||||
locator += chr(ord('A') + int((longitude - int(longitude / 2) * 2) / (2 / 24)))
|
locator = chr(A + int(a[0])) + chr(A + int(b[0]))
|
||||||
locator += chr(ord('A') + int((latitude - int(latitude / 1) * 1 ) / (1 / 24)))
|
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]
|
||||||
|
|
||||||
return locator
|
return locator
|
||||||
|
|
||||||
def locator_to_latlong (locator):
|
def locator_to_latlong (locator, center=True):
|
||||||
"""converts Maidenhead locator in the corresponding WGS84 coordinates
|
"""converts Maidenhead locator in the corresponding WGS84 coordinates
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
locator (string): Locator, either 4 or 6 characters
|
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
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple (float, float): Latitude, Longitude
|
tuple (float, float): Latitude, Longitude
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: When called with wrong or invalid input arg
|
ValueError: When called with wrong or invalid Maidenhead locator string
|
||||||
TypeError: When arg is not a string
|
TypeError: When arg is not a string
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
@ -79,7 +99,7 @@ def locator_to_latlong (locator):
|
||||||
|
|
||||||
locator = locator.upper()
|
locator = locator.upper()
|
||||||
|
|
||||||
if len(locator) == 5 or len(locator) < 4:
|
if len(locator) < 4 or len(locator) == 5 or len(locator) == 7 or len(locator) == 9:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
if ord(locator[0]) > ord('R') or ord(locator[0]) < ord('A'):
|
if ord(locator[0]) > ord('R') or ord(locator[0]) < ord('A'):
|
||||||
|
|
@ -100,23 +120,64 @@ def locator_to_latlong (locator):
|
||||||
if ord (locator[5]) > ord('X') or ord(locator[5]) < ord('A'):
|
if ord (locator[5]) > ord('X') or ord(locator[5]) < ord('A'):
|
||||||
raise ValueError
|
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
|
longitude = (ord(locator[0]) - ord('A')) * 20 - 180
|
||||||
latitude = (ord(locator[1]) - ord('A')) * 10 - 90
|
latitude = (ord(locator[1]) - ord('A')) * 10 - 90
|
||||||
longitude += (ord(locator[2]) - ord('0')) * 2
|
longitude += (ord(locator[2]) - ord('0')) * 2
|
||||||
latitude += (ord(locator[3]) - ord('0'))
|
latitude += (ord(locator[3]) - ord('0')) * 1
|
||||||
|
|
||||||
if len(locator) == 6:
|
if len(locator) == 4:
|
||||||
longitude += ((ord(locator[4])) - ord('A')) * (2 / 24)
|
|
||||||
latitude += ((ord(locator[5])) - ord('A')) * (1 / 24)
|
|
||||||
|
|
||||||
# move to center of subsquare
|
if center:
|
||||||
longitude += 1 / 24
|
longitude += 2 / 2
|
||||||
latitude += 0.5 / 24
|
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
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# move to center of square
|
raise ValueError
|
||||||
longitude += 1;
|
|
||||||
latitude += 0.5;
|
|
||||||
|
|
||||||
return latitude, longitude
|
return latitude, longitude
|
||||||
|
|
||||||
|
|
@ -125,14 +186,14 @@ def calculate_distance(locator1, locator2):
|
||||||
"""calculates the (shortpath) distance between two Maidenhead locators
|
"""calculates the (shortpath) distance between two Maidenhead locators
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
locator1 (string): Locator, either 4 or 6 characters
|
locator1 (string): Locator, either 4, 6 or 8 characters
|
||||||
locator2 (string): Locator, either 4 or 6 characters
|
locator2 (string): Locator, either 4, 6 or 8 characters
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: Distance in km
|
float: Distance in km
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: When called with wrong or invalid input arg
|
ValueError: When called with wrong or invalid maidenhead locator strings
|
||||||
AttributeError: When args are not a string
|
AttributeError: When args are not a string
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
@ -142,6 +203,9 @@ def calculate_distance(locator1, locator2):
|
||||||
>>> calculate_distance("JN48QM", "QF67bf")
|
>>> calculate_distance("JN48QM", "QF67bf")
|
||||||
16466.413
|
16466.413
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Distances is calculated between the centers of the (sub) squares
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
R = 6371 #earh radius
|
R = 6371 #earh radius
|
||||||
|
|
@ -160,15 +224,15 @@ def calculate_distance(locator1, locator2):
|
||||||
c = 2 * atan2(sqrt(a), sqrt(1-a))
|
c = 2 * atan2(sqrt(a), sqrt(1-a))
|
||||||
d = R * c #distance in km
|
d = R * c #distance in km
|
||||||
|
|
||||||
return d;
|
return d
|
||||||
|
|
||||||
|
|
||||||
def calculate_distance_longpath(locator1, locator2):
|
def calculate_distance_longpath(locator1, locator2):
|
||||||
"""calculates the (longpath) distance between two Maidenhead locators
|
"""calculates the (longpath) distance between two Maidenhead locators
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
locator1 (string): Locator, either 4 or 6 characters
|
locator1 (string): Locator, either 4, 6 or 8 characters
|
||||||
locator2 (string): Locator, either 4 or 6 characters
|
locator2 (string): Locator, either 4, 6 or 8 characters
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: Distance in km
|
float: Distance in km
|
||||||
|
|
@ -184,6 +248,8 @@ def calculate_distance_longpath(locator1, locator2):
|
||||||
>>> calculate_distance_longpath("JN48QM", "QF67bf")
|
>>> calculate_distance_longpath("JN48QM", "QF67bf")
|
||||||
23541.5867
|
23541.5867
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Distance is calculated between the centers of the (sub) squares
|
||||||
"""
|
"""
|
||||||
|
|
||||||
c = 40008 #[km] earth circumference
|
c = 40008 #[km] earth circumference
|
||||||
|
|
@ -196,8 +262,8 @@ def calculate_heading(locator1, locator2):
|
||||||
"""calculates the heading from the first to the second locator
|
"""calculates the heading from the first to the second locator
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
locator1 (string): Locator, either 4 or 6 characters
|
locator1 (string): Locator, either 4, 6 or 8 characters
|
||||||
locator2 (string): Locator, either 4 or 6 characters
|
locator2 (string): Locator, either 4, 6 or 6 characters
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: Heading in deg
|
float: Heading in deg
|
||||||
|
|
@ -213,6 +279,9 @@ def calculate_heading(locator1, locator2):
|
||||||
>>> calculate_heading("JN48QM", "QF67bf")
|
>>> calculate_heading("JN48QM", "QF67bf")
|
||||||
74.3136
|
74.3136
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Heading is calculated between the centers of the (sub) squares
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
lat1, long1 = locator_to_latlong(locator1)
|
lat1, long1 = locator_to_latlong(locator1)
|
||||||
|
|
@ -236,8 +305,8 @@ def calculate_heading_longpath(locator1, locator2):
|
||||||
"""calculates the heading from the first to the second locator (long path)
|
"""calculates the heading from the first to the second locator (long path)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
locator1 (string): Locator, either 4 or 6 characters
|
locator1 (string): Locator, either 4, 6 or 8 characters
|
||||||
locator2 (string): Locator, either 4 or 6 characters
|
locator2 (string): Locator, either 4, 6 or 8 characters
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: Long path heading in deg
|
float: Long path heading in deg
|
||||||
|
|
@ -253,6 +322,9 @@ def calculate_heading_longpath(locator1, locator2):
|
||||||
>>> calculate_heading_longpath("JN48QM", "QF67bf")
|
>>> calculate_heading_longpath("JN48QM", "QF67bf")
|
||||||
254.3136
|
254.3136
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Distance is calculated between the centers of the (sub) squares
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
heading = calculate_heading(locator1, locator2)
|
heading = calculate_heading(locator1, locator2)
|
||||||
|
|
@ -265,7 +337,7 @@ def calculate_sunrise_sunset(locator, calc_date=None):
|
||||||
"""calculates the next sunset and sunrise for a Maidenhead locator at a give date & time
|
"""calculates the next sunset and sunrise for a Maidenhead locator at a give date & time
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
locator1 (string): Maidenhead Locator, either 4 or 6 characters
|
locator1 (string): Maidenhead Locator, either 4, 6 or 8 characters
|
||||||
calc_date (datetime, optional): Starting datetime for the calculations (UTC)
|
calc_date (datetime, optional): Starting datetime for the calculations (UTC)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
VERSION = (0, 9, 0)
|
VERSION = (0, 12, 0)
|
||||||
__release__ = ''.join(['-.'[type(x) == int]+str(x) for x in VERSION])[1:]
|
__release__ = ''.join(['-.'[type(x) == int]+str(x) for x in VERSION])[1:]
|
||||||
__version__ = '.'.join((str(VERSION[0]), str(VERSION[1])))
|
__version__ = '.'.join((str(VERSION[0]), str(VERSION[1])))
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
sphinx>=1.8.5
|
sphinx>=1.8.5
|
||||||
sphinxcontrib-napoleon>=0.7
|
sphinxcontrib-napoleon>=0.7
|
||||||
beautifulsoup4>=4.7.1
|
beautifulsoup4>=4.7.1
|
||||||
|
sphinx_rtd_theme>=0.5.2
|
||||||
|
sphinx_rtd_dark_mode>=0.1.2
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
pytest>=7.0.0; python_version>='3.7'
|
pytest>=7.0.0
|
||||||
pytest==4.6.11; python_version=='3.6'
|
|
||||||
pytest-blockage>=0.2.2
|
pytest-blockage>=0.2.2
|
||||||
pytest-localserver>=0.5
|
pytest-localserver>=0.5
|
||||||
pytest-cov>=2.12
|
pytest-cov>=2.12
|
||||||
|
maidenhead==1.6.0
|
||||||
|
requests>=2.32.4
|
||||||
|
beautifulsoup4==4.13.4
|
||||||
|
redis==5.2.1
|
||||||
|
ephem==4.2
|
||||||
4
setup.py
4
setup.py
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import os
|
import os
|
||||||
from distutils.core import setup
|
from setuptools import setup
|
||||||
|
|
||||||
kw = {}
|
kw = {}
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ setup(name='pyhamtools',
|
||||||
"requests>=2.21.0",
|
"requests>=2.21.0",
|
||||||
"ephem>=4.1.3",
|
"ephem>=4.1.3",
|
||||||
"beautifulsoup4>=4.7.1",
|
"beautifulsoup4>=4.7.1",
|
||||||
"lxml>=4.8.0",
|
"lxml>=5.0.0",
|
||||||
"redis>=2.10.6",
|
"redis>=2.10.6",
|
||||||
],
|
],
|
||||||
**kw
|
**kw
|
||||||
|
|
|
||||||
|
|
@ -90,12 +90,12 @@ response_prefix_VK9DWX_clublog = {
|
||||||
}
|
}
|
||||||
|
|
||||||
response_prefix_VK9DLX_clublog = {
|
response_prefix_VK9DLX_clublog = {
|
||||||
u'adif': 147,
|
u'adif': 189,
|
||||||
u'continent': u'OC',
|
u'continent': u'OC',
|
||||||
u'country': u'LORD HOWE ISLAND',
|
u'country': u'NORFOLK ISLAND',
|
||||||
u'cqz': 30,
|
u'cqz': 32,
|
||||||
u'latitude': -31.6,
|
u'latitude': -29.0,
|
||||||
u'longitude': 159.1
|
u'longitude': 168.0
|
||||||
}
|
}
|
||||||
|
|
||||||
response_prefix_TA7I_clublog = {
|
response_prefix_TA7I_clublog = {
|
||||||
|
|
@ -126,13 +126,13 @@ response_prefix_V26K_clublog = {
|
||||||
}
|
}
|
||||||
|
|
||||||
response_prefix_VK9DLX_countryfile = {
|
response_prefix_VK9DLX_countryfile = {
|
||||||
u'adif': 147,
|
u'adif': 189,
|
||||||
u'continent': u'OC',
|
u'continent': u'OC',
|
||||||
u'country': u'Lord Howe Island',
|
u'country': u'Norfolk Island',
|
||||||
u'cqz': 30,
|
u'cqz': 32,
|
||||||
u'ituz': 60,
|
u'ituz': 60,
|
||||||
u'latitude': -31.55,
|
u'latitude': -29.03,
|
||||||
u'longitude': 159.08
|
u'longitude': 167.93
|
||||||
}
|
}
|
||||||
|
|
||||||
response_prefix_VK9GMW_clublog = {
|
response_prefix_VK9GMW_clublog = {
|
||||||
|
|
@ -195,7 +195,7 @@ response_Exception_VK9XO_with_start_date = {
|
||||||
'country': 'CHRISTMAS ISLAND',
|
'country': 'CHRISTMAS ISLAND',
|
||||||
'continent': 'OC',
|
'continent': 'OC',
|
||||||
'latitude': -10.48,
|
'latitude': -10.48,
|
||||||
'longitude': 105.71,
|
'longitude': 105.62,
|
||||||
'cqz': 29
|
'cqz': 29
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,12 @@ class Test_calculate_distance():
|
||||||
|
|
||||||
assert abs(calculate_distance("JN48QM", "FN44AB") - 5965) < 1
|
assert abs(calculate_distance("JN48QM", "FN44AB") - 5965) < 1
|
||||||
assert abs(calculate_distance("FN44AB", "JN48QM") - 5965) < 1
|
assert abs(calculate_distance("FN44AB", "JN48QM") - 5965) < 1
|
||||||
assert abs(calculate_distance("JN48QM", "QF67bf") - 16467) < 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
|
||||||
|
|
||||||
def test_calculate_distance_invalid_inputs(self):
|
def test_calculate_distance_invalid_inputs(self):
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,24 @@ class Test_latlong_to_locator():
|
||||||
assert latlong_to_locator(-89.97916, -179.95833) == "AA00AA"
|
assert latlong_to_locator(-89.97916, -179.95833) == "AA00AA"
|
||||||
assert latlong_to_locator(89.97916, 179.9583) == "RR99XX"
|
assert latlong_to_locator(89.97916, 179.9583) == "RR99XX"
|
||||||
|
|
||||||
def test_latlong_to_locator_normal_case(self):
|
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):
|
||||||
|
|
||||||
assert latlong_to_locator(48.52083, 9.3750000) == "JN48QM"
|
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(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):
|
def test_latlong_to_locator_invalid_characters(self):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
import maidenhead
|
||||||
from pyhamtools.locator import locator_to_latlong
|
from pyhamtools.locator import locator_to_latlong
|
||||||
from pyhamtools.consts import LookupConventions as const
|
from pyhamtools.consts import LookupConventions as const
|
||||||
|
|
||||||
|
|
||||||
class Test_locator_to_latlong():
|
class Test_locator_to_latlong():
|
||||||
|
|
||||||
def test_locator_to_latlong_edge_cases(self):
|
def test_locator_to_latlong_min_max_cases(self):
|
||||||
latitude, longitude = locator_to_latlong("AA00AA")
|
latitude, longitude = locator_to_latlong("AA00AA")
|
||||||
assert abs(latitude + 89.97916) < 0.00001
|
assert abs(latitude + 89.97916) < 0.00001
|
||||||
assert abs(longitude +179.95833) < 0.0001
|
assert abs(longitude +179.95833) < 0.0001
|
||||||
|
|
@ -13,23 +15,79 @@ class Test_locator_to_latlong():
|
||||||
assert abs(latitude - 89.97916) < 0.00001
|
assert abs(latitude - 89.97916) < 0.00001
|
||||||
assert abs(longitude - 179.9583) < 0.0001
|
assert abs(longitude - 179.9583) < 0.0001
|
||||||
|
|
||||||
def test_locator_to_latlong_normal_case(self):
|
def test_locator_to_latlong_4chars_precision(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")
|
latitude, longitude = locator_to_latlong("JN48")
|
||||||
assert abs(latitude - 48.5) < 0.001
|
assert abs(latitude - 48.5) < 0.1
|
||||||
assert abs(longitude - 9.000) < 0.001
|
assert abs(longitude - 9.0) < 0.1
|
||||||
|
|
||||||
def test_locator_to_latlong_mixed_signs(self):
|
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
|
||||||
|
|
||||||
|
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("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):
|
||||||
|
|
||||||
latitude, longitude = locator_to_latlong("Jn48qM")
|
latitude, longitude = locator_to_latlong("Jn48qM")
|
||||||
assert abs(latitude - 48.52083) < 0.00001
|
assert abs(latitude - 48.52083) < 0.00001
|
||||||
assert abs(longitude - 9.3750000) < 0.0001
|
assert abs(longitude - 9.3750000) < 0.0001
|
||||||
|
|
||||||
|
|
||||||
def test_locator_to_latlong_wrong_amount_of_characters(self):
|
def test_locator_to_latlong_wrong_amount_of_characters(self):
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
|
|
@ -43,12 +101,30 @@ class Test_locator_to_latlong():
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
latitude, longitude = locator_to_latlong("JN8Q")
|
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):
|
def test_locator_to_latlong_invalid_characters(self):
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
latitude, longitude = locator_to_latlong("21XM99")
|
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):
|
with pytest.raises(ValueError):
|
||||||
latitude, longitude = locator_to_latlong("****")
|
latitude, longitude = locator_to_latlong("****")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ response_Exception_KC6MM_1990 = {
|
||||||
'adif': 22,
|
'adif': 22,
|
||||||
'country': u'PALAU',
|
'country': u'PALAU',
|
||||||
'continent': u'OC',
|
'continent': u'OC',
|
||||||
'latitude': 9.50,
|
'latitude': 9.52,
|
||||||
'longitude': 138.20,
|
'longitude': 138.21,
|
||||||
'cqz': 27,
|
'cqz': 27,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,8 +34,8 @@ response_Exception_KC6MM_1992 = {
|
||||||
'adif': 22,
|
'adif': 22,
|
||||||
'country': u'PALAU',
|
'country': u'PALAU',
|
||||||
'continent': u'OC',
|
'continent': u'OC',
|
||||||
'latitude': 9.50,
|
'latitude': 9.52,
|
||||||
'longitude': 138.20,
|
'longitude': 138.21,
|
||||||
'cqz': 27,
|
'cqz': 27,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ response_Exception_VK9XO_with_start_date = {
|
||||||
'country': u'CHRISTMAS ISLAND',
|
'country': u'CHRISTMAS ISLAND',
|
||||||
'continent': u'OC',
|
'continent': u'OC',
|
||||||
'latitude': -10.48,
|
'latitude': -10.48,
|
||||||
'longitude': 105.71,
|
'longitude': 105.62,
|
||||||
'cqz': 29,
|
'cqz': 29,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class Test_lotw_methods:
|
||||||
execfile(os.path.join(fix_dir,"lotw_fixture.py"), namespace)
|
execfile(os.path.join(fix_dir,"lotw_fixture.py"), namespace)
|
||||||
assert get_lotw_users(url=httpserver.url) == namespace['lotw_fixture']
|
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):
|
def test_download_lotw_list_and_check_types(self):
|
||||||
|
|
||||||
data = get_lotw_users()
|
data = get_lotw_users()
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ class Test_utils_freq_to_band():
|
||||||
assert freq_to_band(1200000) == {"band" : 0.23, "mode":None}
|
assert freq_to_band(1200000) == {"band" : 0.23, "mode":None}
|
||||||
|
|
||||||
def test_shf_frequencies(self):
|
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(2390000) == {"band" : 0.13, "mode":None}
|
||||||
|
|
||||||
assert freq_to_band(3300000) == {"band" : 0.09, "mode":None}
|
assert freq_to_band(3300000) == {"band" : 0.09, "mode":None}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue