[Git][debian-gis-team/utm][master] 7 commits: New upstream version 0.8.0
Antonio Valentino (@antonio.valentino)
gitlab at salsa.debian.org
Sat Feb 1 10:17:06 GMT 2025
Antonio Valentino pushed to branch master at Debian GIS Project / utm
Commits:
b70a4808 by Antonio Valentino at 2025-02-01T10:55:38+01:00
New upstream version 0.8.0
- - - - -
519987f0 by Antonio Valentino at 2025-02-01T10:55:38+01:00
Update upstream source from tag 'upstream/0.8.0'
Update to upstream version '0.8.0'
with Debian dir 49df393730aa36c88204acbbf6057510dda5d8bf
- - - - -
0fd90629 by Antonio Valentino at 2025-02-01T10:56:19+01:00
New upstream release
- - - - -
6cf85537 by Antonio Valentino at 2025-02-01T11:00:16+01:00
Drop 0001-Do-not-use-distutils.patch
- - - - -
7ba51323 by Antonio Valentino at 2025-02-01T10:09:11+00:00
Add build-dependency on python3-pytest
- - - - -
28cc98a2 by Antonio Valentino at 2025-02-01T10:12:03+00:00
Update dates in d/copyright
- - - - -
168e1cdd by Antonio Valentino at 2025-02-01T10:12:03+00:00
Set distribution to unstable
- - - - -
21 changed files:
- + .github/FUNDING.yml
- .github/workflows/ci.yml
- CHANGELOG.rst
- README.rst
- debian/changelog
- debian/control
- debian/copyright
- − debian/patches/0001-Do-not-use-distutils.patch
- − debian/patches/series
- + numpy-1.x-requirements.txt
- + numpy-2.x-requirements.txt
- + release-requirements.txt
- + renovate.json
- − requirements-numpy.txt
- requirements.txt
- scripts/utm-converter
- setup.py
- test/test_utm.py
- utm/__init__.py
- + utm/_version.py
- utm/conversion.py
Changes:
=====================================
.github/FUNDING.yml
=====================================
@@ -0,0 +1,2 @@
+github: Turbo87
+custom: https://paypal.me/tobiasbieniek
=====================================
.github/workflows/ci.yml
=====================================
@@ -6,6 +6,7 @@ on:
- master
- "v*"
tags:
+ - "v*"
pull_request:
schedule:
- cron: '0 3 * * *' # daily, at 3am
@@ -16,29 +17,35 @@ jobs:
fail-fast: true
matrix:
python-version:
+ - "3.13"
+ - "3.12"
+ - "3.11"
+ - "3.10"
- "3.9"
- "3.8"
- - "3.7"
- - "3.6"
- - "3.5"
- - "2.7"
- numpy:
- - true
+ numpy-version:
- false
+ - "1.x"
+ - "2.x"
+ exclude:
+ - python-version: "3.13"
+ numpy-version: "1.x"
+ - python-version: "3.8"
+ numpy-version: "2.x"
- name: "Tests (Python v${{ matrix.python-version }}, NumPy: ${{ matrix.numpy }})"
- runs-on: ubuntu-latest
+ name: "Tests (Python v${{ matrix.python-version }}, NumPy: ${{ matrix.numpy-version }})"
+ runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout at v2
+ - uses: actions/checkout at v4
- - uses: actions/setup-python at v1
+ - uses: actions/setup-python at v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -r requirements.txt
- - run: pip install -r requirements-numpy.txt
- if: matrix.numpy == true
+ - run: pip install -r numpy-${{ matrix.numpy-version }}-requirements.txt
+ if: matrix.numpy-version != false
- run: pytest -v --cov=utm --color=yes
@@ -49,15 +56,17 @@ jobs:
name: Release
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout at v2
+ - uses: actions/checkout at v4
- - uses: actions/setup-python at v1
+ - uses: actions/setup-python at v5
with:
- python-version: 3.9
+ python-version: "3.13"
+
+ - run: pip install -r release-requirements.txt
- run: python setup.py sdist
- - uses: pypa/gh-action-pypi-publish at v1.4.1
+ - uses: pypa/gh-action-pypi-publish at v1.12.4
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}
=====================================
CHANGELOG.rst
=====================================
@@ -1,6 +1,24 @@
Changelog
=========
+v0.8.0
+------
+
+* Add support for Python 3.10, 3.11, 3.12 and 3.13
+* Drop support for Python 2.7, 3.5, 3.6, 3.7 and 3.8
+* Add version (#62)
+* Convert all tests to pytest (#65)
+* Port to setuptools (#89)
+* Add long description for PyPi (#99)
+* Fix numpy array being modified in place (#86)
+* Fix ``latlon_to_zone_number()`` returning bogus zone 61 for longitude 180 (#110)
+* Fix forcing zones around equator and add ``force_northern`` in ``from_latlon()`` (#124)
+* Improve ``to_latlon()`` accuracy (#120)
+* Update all (test) dependencies, taking into account supported Python versions (e.g. #116, #128)
+* Add ``zone_letter_to_central_latitude()`` as a counterpart to ``zone_number_to_central_longitude()`` (#130)
+* Bring CI script into the 2024 realm
+
+
v0.7.0
------
@@ -14,7 +32,7 @@ v0.6.0
* Drop support for Python 2.6 and 3.3 (#53)
* Improve documentation (#50)
* Fix issue near anti-meridian when forcing zones (#47)
-* Improve `to_latlon()` accuracy (#49)
+* Improve ``to_latlon()`` accuracy (#49)
v0.5.0
=====================================
README.rst
=====================================
@@ -1,12 +1,6 @@
utm
===
-.. image:: https://travis-ci.org/Turbo87/utm.png
-
-.. image:: https://img.shields.io/badge/License-MIT-yellow.svg
- :target: https://github.com/Turbo87/utm/blob/master/LICENSE
-
-
Bidirectional UTM-WGS84 converter for python
Usage
=====================================
debian/changelog
=====================================
@@ -1,9 +1,17 @@
-utm (0.7.0-4) UNRELEASED; urgency=medium
+utm (0.8.0-1) unstable; urgency=medium
- * Team upload.
+ [ Bas Couwenberg ]
* Bump Standards-Version to 4.7.0, no changes.
- -- Bas Couwenberg <sebastic at debian.org> Sun, 28 Jul 2024 20:06:34 +0200
+ [ Antonio Valentino ]
+ * New upstream release.
+ * debian/patches:
+ - Drop 0001-Do-not-use-distutils.patch, applied upstream.
+ * debian/control:
+ - Add buld-dependency on python3-pytest.
+ * Update dates in d/copyright.
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it> Sat, 01 Feb 2025 10:03:10 +0000
utm (0.7.0-3) unstable; urgency=medium
=====================================
debian/control
=====================================
@@ -7,6 +7,7 @@ Build-Depends: debhelper-compat (= 13),
dh-python,
dh-sequence-python3,
python3-all,
+ python3-pytest <!nocheck>,
python3-setuptools
Standards-Version: 4.7.0
Testsuite: autopkgtest-pkg-pybuild
=====================================
debian/copyright
=====================================
@@ -8,7 +8,7 @@ Copyright: 2012-2023, Tobias Bieniek <Tobias.Bieniek at gmx.de>
License: Expat
Files: debian/*
-Copyright: 2023, Antonio Valentino <antonio.valentino at tiscali.it>
+Copyright: 2023-2025, Antonio Valentino <antonio.valentino at tiscali.it>
License: Expat
License: Expat
=====================================
debian/patches/0001-Do-not-use-distutils.patch deleted
=====================================
@@ -1,19 +0,0 @@
-From: Antonio Valentino <antonio.valentino at tiscali.it>
-Date: Wed, 9 Aug 2023 16:16:12 +0000
-Subject: Do not use distutils
-
-Forwarded: https://github.com/Turbo87/utm/pull/89
----
- setup.py | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/setup.py b/setup.py
-index d4b0be5..213c401 100644
---- a/setup.py
-+++ b/setup.py
-@@ -1,4 +1,4 @@
--from distutils.core import setup
-+from setuptools import setup
-
- setup(
- name='utm',
=====================================
debian/patches/series deleted
=====================================
@@ -1 +0,0 @@
-0001-Do-not-use-distutils.patch
=====================================
numpy-1.x-requirements.txt
=====================================
@@ -0,0 +1,2 @@
+numpy==1.24.4; python_version < '3.9'
+numpy==1.26.4; python_version >= '3.9' and python_version < '3.13'
=====================================
numpy-2.x-requirements.txt
=====================================
@@ -0,0 +1,3 @@
+numpy==2.0.0; python_version >= '3.9' and python_version < '3.10'
+numpy==2.1.0; python_version >= '3.10' and python_version < '3.13'
+numpy==2.2.0; python_version >= '3.13'
=====================================
release-requirements.txt
=====================================
@@ -0,0 +1 @@
+setuptools >= 75, < 76
=====================================
renovate.json
=====================================
@@ -0,0 +1,7 @@
+{
+ "extends": [
+ "config:base",
+ ":dependencyDashboard",
+ ":semanticCommitsDisabled"
+ ]
+}
=====================================
requirements-numpy.txt deleted
=====================================
@@ -1 +0,0 @@
-numpy==1.16.6
\ No newline at end of file
=====================================
requirements.txt
=====================================
@@ -1,2 +1,3 @@
-pytest==4.6.11
-pytest-cov==2.10.1
\ No newline at end of file
+pytest==8.3.4
+pytest-cov==5.0.0; python_version < '3.9'
+pytest-cov==6.0.0; python_version >= '3.9'
=====================================
scripts/utm-converter
=====================================
@@ -1,5 +1,7 @@
#!/usr/bin/env python
+from __future__ import print_function
+
import argparse
import utm
@@ -21,13 +23,13 @@ args = parser.parse_args()
if all(arg in args for arg in ['easting', 'northing', 'zone_number', 'zone_letter']):
if args.zone_letter == '':
parser_utm.print_usage()
- print "utm-converter utm: error: too few arguments"
+ print("utm-converter utm: error: too few arguments")
exit()
coordinate = utm.to_latlon(args.easting, args.northing,
- args.zone_number, args.zone_letter)
+ args.zone_number, args.zone_letter)
elif all(arg in args for arg in ['latitude', 'longitude']):
coordinate = utm.from_latlon(args.latitude, args.longitude)
-print ','.join([str(component) for component in coordinate])
+print(','.join(str(component) for component in coordinate))
=====================================
setup.py
=====================================
@@ -1,12 +1,20 @@
-from distutils.core import setup
+from setuptools import setup
+
+from utm._version import __version__
+
+from pathlib import Path
+this_directory = Path(__file__).parent
+long_description = (this_directory / "README.rst").read_text()
setup(
name='utm',
- version='0.7.0',
+ version=__version__,
author='Tobias Bieniek',
author_email='Tobias.Bieniek at gmx.de',
url='https://github.com/Turbo87/utm',
description='Bidirectional UTM-WGS84 converter for python',
+ long_description=long_description,
+ long_description_content_type='text/x-rst',
keywords=['utm', 'wgs84', 'coordinate', 'converter'],
classifiers=[
'Programming Language :: Python',
=====================================
test/test_utm.py
=====================================
@@ -1,356 +1,510 @@
+from __future__ import division
+
import utm as UTM
-import unittest
+
+import functools
+import pytest
try:
import numpy as np
+
use_numpy = True
except ImportError:
use_numpy = False
-class UTMTestCase(unittest.TestCase):
- def assert_utm_equal(self, a, b):
- if use_numpy and isinstance(b[0], np.ndarray):
- self.assertTrue(np.allclose(a[0], b[0]))
- self.assertTrue(np.allclose(a[1], b[1]))
- else:
- self.assertAlmostEqual(a[0], b[0], 0)
- self.assertAlmostEqual(a[1], b[1], 0)
- self.assertEqual(a[2], b[2])
- self.assertEqual(a[3].upper(), b[3].upper())
-
- def assert_latlon_equal(self, a, b):
- if use_numpy and isinstance(b[0], np.ndarray):
- self.assertTrue(np.allclose(a[0], b[0], rtol=1e-4, atol=1e-4))
- self.assertTrue(np.allclose(a[1], b[1], rtol=1e-4, atol=1e-4))
- else:
- self.assertAlmostEqual(a[0], b[0], 4)
- self.assertAlmostEqual(a[1], b[1], 4)
+def assert_utm_equal(a, b):
+ if use_numpy and isinstance(b[0], np.ndarray):
+ assert np.allclose(a[0], b[0])
+ assert np.allclose(a[1], b[1])
+ else:
+ assert a[0] == pytest.approx(b[0], abs=1)
+ assert a[1] == pytest.approx(b[1], abs=1)
+ assert a[2] == b[2]
+ assert a[3].upper() == b[3].upper()
-class KnownValues(UTMTestCase):
- known_values = [
- # Aachen, Germany
- (
- (50.77535, 6.08389),
- (294409, 5628898, 32, 'U'),
- {'northern': True},
- ),
- # New York, USA
- (
- (40.71435, -74.00597),
- (583960, 4507523, 18, 'T'),
- {'northern': True},
- ),
- # Wellington, New Zealand
- (
- (-41.28646, 174.77624),
- (313784, 5427057, 60, 'G'),
- {'northern': False},
- ),
- # Capetown, South Africa
- (
- (-33.92487, 18.42406),
- (261878, 6243186, 34, 'H'),
- {'northern': False},
- ),
- # Mendoza, Argentina
- (
- (-32.89018, -68.84405),
- (514586, 6360877, 19, 'h'),
- {'northern': False},
- ),
- # Fairbanks, Alaska, USA
- (
- (64.83778, -147.71639),
- (466013, 7190568, 6, 'W'),
- {'northern': True},
- ),
- # Ben Nevis, Scotland, UK
- (
- (56.79680, -5.00601),
- (377486, 6296562, 30, 'V'),
- {'northern': True},
- ),
- # Latitude 84
+def assert_latlon_equal(a, b):
+ if use_numpy and isinstance(b[0], np.ndarray):
+ def longitude_close(lon1, lon2, rtol=1e-4, atol=1e-4):
+ # Check if longitudes are close after normalization
+ is_close = functools.partial(np.isclose, lon1, rtol=rtol, atol=atol)
+ return is_close(lon2) or is_close(lon2 - 360) or is_close(lon2 + 360)
+
+ assert np.allclose(a[0], b[0], rtol=1e-4, atol=1e-4)
+ if isinstance(a[1], np.ndarray):
+ assert all(longitude_close(lon_a, lon_b) for lon_a, lon_b in zip(a[1].flatten(), b[1].flatten()))
+ else:
+ assert all(longitude_close(a[1], lon_b) for lon_b in b[1].flatten())
+ else:
+ assert a[0] == pytest.approx(b[0], 4)
+ assert (
+ a[1] == pytest.approx(b[1], 4) or
+ a[1] == pytest.approx(b[1] - 360, 4) or
+ a[1] == pytest.approx(b[1] + 360, 4)
+ )
+
+
+known_values = [
+ # Aachen, Germany
+ (
+ (50.77535, 6.08389),
+ (294409, 5628898, 32, "U"),
+ {"northern": True},
+ ),
+ # New York, USA
+ (
+ (40.71435, -74.00597),
+ (583960, 4507523, 18, "T"),
+ {"northern": True},
+ ),
+ # Wellington, New Zealand
+ (
+ (-41.28646, 174.77624),
+ (313784, 5427057, 60, "G"),
+ {"northern": False},
+ ),
+ # Capetown, South Africa
+ (
+ (-33.92487, 18.42406),
+ (261878, 6243186, 34, "H"),
+ {"northern": False},
+ ),
+ # Mendoza, Argentina
+ (
+ (-32.89018, -68.84405),
+ (514586, 6360877, 19, "h"),
+ {"northern": False},
+ ),
+ # Fairbanks, Alaska, USA
+ (
+ (64.83778, -147.71639),
+ (466013, 7190568, 6, "W"),
+ {"northern": True},
+ ),
+ # Ben Nevis, Scotland, UK
+ (
+ (56.79680, -5.00601),
+ (377486, 6296562, 30, "V"),
+ {"northern": True},
+ ),
+ # Bergen, Norway
+ (
+ (60.38952, 5.320675),
+ (297264, 6700454, 32, "V"),
+ {"northern": True},
+ ),
+ # Alkefjellet, Spitsbergen, Svalbard
+ (
+ (79.45574, 18.76338),
+ (576830, 8823320, 33, "X"),
+ {"northern": True},
+ ),
+ # Latitude 84
+ (
+ (84, -5.00601),
+ (476594, 9328501, 30, "X"),
+ {"northern": True},
+ ),
+ # East-most point on the Equator
+ (
+ (0, 180),
+ (166021, 0, 1, "N"),
+ {"northern": True},
+ ),
+ # West-most point on the Equator
+ (
+ (0, -180),
+ (166021, 0, 1, "N"),
+ {"northern": True}
+ ),
+]
+
+
+ at pytest.mark.parametrize("latlon, utm, utm_kw", known_values)
+def test_from_latlon(latlon, utm, utm_kw):
+ """from_latlon should give known result with known input"""
+ result = UTM.from_latlon(*latlon)
+ assert_utm_equal(utm, result)
+
+
+ at pytest.mark.skipif(not use_numpy, reason="numpy not installed")
+ at pytest.mark.parametrize("latlon, utm, utm_kw", known_values)
+def test_from_latlon_numpy(latlon, utm, utm_kw):
+ result = UTM.from_latlon(*[np.array([x]) for x in latlon])
+ assert_utm_equal(utm, result)
+
+
+ at pytest.mark.skipif(not use_numpy, reason="numpy not installed")
+def test_from_latlon_numpy_static():
+ lats = np.array([0.0, 3.0, 6.0])
+ lons = np.array([0.0, 1.0, 3.4])
+ result = UTM.from_latlon(lats, lons)
+ assert_utm_equal(
(
- (84, -5.00601),
- (476594, 9328501, 30, 'X'),
- {'northern': True},
+ np.array(
+ [166021.44317933032, 277707.83075574087, 544268.12794623]
+ ),
+ np.array([0.0, 331796.29167519242, 663220.7198366751]),
+ 31,
+ "N",
),
- ]
-
- def test_from_latlon(self):
- '''from_latlon should give known result with known input'''
- for latlon, utm, _ in self.known_values:
- result = UTM.from_latlon(*latlon)
- self.assert_utm_equal(utm, result)
-
- def test_from_latlon_numpy(self):
- if not use_numpy:
- return
- lats = np.array([0.0, 3.0, 6.0])
- lons = np.array([0.0, 1.0, 3.4])
- result = UTM.from_latlon(lats, lons)
- self.assert_utm_equal((np.array([166021.44317933032,
- 277707.83075574087,
- 544268.12794623]),
- np.array([0.0,
- 331796.29167519242,
- 663220.7198366751]),
- 31, 'N'), result)
-
- for latlon, utm, _ in self.known_values:
- result = UTM.from_latlon(*[np.array([x]) for x in latlon])
- self.assert_utm_equal(utm, result)
-
- def test_to_latlon(self):
- '''to_latlon should give known result with known input'''
- for latlon, utm, utm_kw in self.known_values:
- result = UTM.to_latlon(*utm)
- self.assert_latlon_equal(latlon, result)
-
- result = UTM.to_latlon(*utm[0:3], **utm_kw)
- self.assert_latlon_equal(latlon, result)
-
- def test_to_latlon_numpy(self):
- if not use_numpy:
- return
- result = UTM.to_latlon(np.array([166021.44317933032,
- 277707.83075574087,
- 544268.12794623]),
- np.array([0.0,
- 331796.29167519242,
- 663220.7198366751]),
- 31, northern=True)
- self.assert_latlon_equal((np.array([0.0, 3.0, 6.0]),
- np.array([0.0, 1.0, 3.4])),
- result)
-
- for latlon, utm, utm_kw in self.known_values:
- utm = [np.array([x]) for x in utm[:2]] + list(utm[2:])
- result = UTM.to_latlon(*utm)
- self.assert_latlon_equal(latlon, result)
-
-
-class BadInput(UTMTestCase):
- def test_from_latlon_range_checks(self):
- '''from_latlon should fail with out-of-bounds input'''
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, -100, 0)
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, -80.1, 0)
- for i in range(-8000, 8400):
- UTM.from_latlon(i / 100.0, 0)
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, 84.1, 0)
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, 100, 0)
-
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, 0, -300)
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, 0, -180.1)
- for i in range(-18000, 18000):
- UTM.from_latlon(0, i / 100.0)
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, 0, 180.1)
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, 0, 300)
-
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, -100, -300)
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, 100, -300)
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, -100, 300)
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, 100, 300)
-
- # test forcing zone ranges
- # NYC should be zone 18T
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, 40.71435, -74.00597, 70, 'T')
- self.assertRaises(UTM.OutOfRangeError, UTM.from_latlon, 40.71435, -74.00597, 18, 'A')
-
- def test_to_latlon_range_checks(self):
- '''to_latlon should fail with out-of-bounds input'''
-
- # test easting range
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 0, 5000000, 32, 'U')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 99999, 5000000, 32, 'U')
-
- for i in range(100000, 999999, 1000):
- UTM.to_latlon(i, 5000000, 32, 'U')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 1000000, 5000000, 32, 'U')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 100000000000, 5000000, 32, 'U')
-
- # test northing range
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, -100000, 32, 'U')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, -1, 32, 'U')
- for i in range(10, 10000000, 1000):
- UTM.to_latlon(500000, i, 32, 'U')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 10000001, 32, 'U')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 50000000, 32, 'U')
-
- # test zone numbers
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 5000000, 0, 'U')
-
- for i in range(1, 60):
- UTM.to_latlon(500000, 5000000, i, 'U')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 5000000, 61, 'U')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 5000000, 1000, 'U')
-
- # test zone letters
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 5000000, 32, 'A')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 5000000, 32, 'B')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 5000000, 32, 'I')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 5000000, 32, 'O')
-
- for i in range(ord('C'), ord('X')):
- i = chr(i)
- if i != 'I' and i != 'O':
- UTM.to_latlon(500000, 5000000, 32, i)
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 5000000, 32, 'Y')
-
- self.assertRaises(
- UTM.OutOfRangeError, UTM.to_latlon, 500000, 5000000, 32, 'Z')
-
-
-class Zone32V(unittest.TestCase):
-
- def assert_zone_equal(self, result, expected_number, expected_letter):
- self.assertEqual(result[2], expected_number)
- self.assertEqual(result[3].upper(), expected_letter.upper())
-
- def test_inside(self):
- self.assert_zone_equal(UTM.from_latlon(56, 3), 32, 'V')
- self.assert_zone_equal(UTM.from_latlon(56, 6), 32, 'V')
- self.assert_zone_equal(UTM.from_latlon(56, 9), 32, 'V')
- self.assert_zone_equal(UTM.from_latlon(56, 11.999999), 32, 'V')
-
- self.assert_zone_equal(UTM.from_latlon(60, 3), 32, 'V')
- self.assert_zone_equal(UTM.from_latlon(60, 6), 32, 'V')
- self.assert_zone_equal(UTM.from_latlon(60, 9), 32, 'V')
- self.assert_zone_equal(UTM.from_latlon(60, 11.999999), 32, 'V')
-
- self.assert_zone_equal(UTM.from_latlon(63.999999, 3), 32, 'V')
- self.assert_zone_equal(UTM.from_latlon(63.999999, 6), 32, 'V')
- self.assert_zone_equal(UTM.from_latlon(63.999999, 9), 32, 'V')
- self.assert_zone_equal(UTM.from_latlon(63.999999, 11.999999), 32, 'V')
-
- def test_left_of(self):
- self.assert_zone_equal(UTM.from_latlon(55.999999, 2.999999), 31, 'U')
- self.assert_zone_equal(UTM.from_latlon(56, 2.999999), 31, 'V')
- self.assert_zone_equal(UTM.from_latlon(60, 2.999999), 31, 'V')
- self.assert_zone_equal(UTM.from_latlon(63.999999, 2.999999), 31, 'V')
- self.assert_zone_equal(UTM.from_latlon(64, 2.999999), 31, 'W')
-
- def test_right_of(self):
- self.assert_zone_equal(UTM.from_latlon(55.999999, 12), 33, 'U')
- self.assert_zone_equal(UTM.from_latlon(56, 12), 33, 'V')
- self.assert_zone_equal(UTM.from_latlon(60, 12), 33, 'V')
- self.assert_zone_equal(UTM.from_latlon(63.999999, 12), 33, 'V')
- self.assert_zone_equal(UTM.from_latlon(64, 12), 33, 'W')
-
- def test_below(self):
- self.assert_zone_equal(UTM.from_latlon(55.999999, 3), 31, 'U')
- self.assert_zone_equal(UTM.from_latlon(55.999999, 6), 32, 'U')
- self.assert_zone_equal(UTM.from_latlon(55.999999, 9), 32, 'U')
- self.assert_zone_equal(UTM.from_latlon(55.999999, 11.999999), 32, 'U')
- self.assert_zone_equal(UTM.from_latlon(55.999999, 12), 33, 'U')
-
- def test_above(self):
- self.assert_zone_equal(UTM.from_latlon(64, 3), 31, 'W')
- self.assert_zone_equal(UTM.from_latlon(64, 6), 32, 'W')
- self.assert_zone_equal(UTM.from_latlon(64, 9), 32, 'W')
- self.assert_zone_equal(UTM.from_latlon(64, 11.999999), 32, 'W')
- self.assert_zone_equal(UTM.from_latlon(64, 12), 33, 'W')
-
-
-class TestRightBoundaries(unittest.TestCase):
-
- def assert_zone_equal(self, result, expected_number):
- self.assertEqual(result[2], expected_number)
-
- def test_limits(self):
- self.assert_zone_equal(UTM.from_latlon(40, 0), 31)
- self.assert_zone_equal(UTM.from_latlon(40, 5.999999), 31)
- self.assert_zone_equal(UTM.from_latlon(40, 6), 32)
-
- self.assert_zone_equal(UTM.from_latlon(72, 0), 31)
- self.assert_zone_equal(UTM.from_latlon(72, 5.999999), 31)
- self.assert_zone_equal(UTM.from_latlon(72, 6), 31)
- self.assert_zone_equal(UTM.from_latlon(72, 8.999999), 31)
- self.assert_zone_equal(UTM.from_latlon(72, 9), 33)
+ result,
+ )
+
+
+ at pytest.mark.parametrize("latlon, utm, utm_kw", known_values)
+def test_to_latlon(latlon, utm, utm_kw):
+ """to_latlon should give known result with known input"""
+ result = UTM.to_latlon(*utm)
+ assert_latlon_equal(latlon, result)
+
+ result = UTM.to_latlon(*utm[0:3], **utm_kw)
+ assert_latlon_equal(latlon, result)
+
+
+ at pytest.mark.skipif(not use_numpy, reason="numpy not installed")
+ at pytest.mark.parametrize("latlon, utm, utm_kw", known_values)
+def test_to_latlon_numpy(latlon, utm, utm_kw):
+ utm = [np.array([x]) for x in utm[:2]] + list(utm[2:])
+ result = UTM.to_latlon(*utm)
+ assert_latlon_equal(latlon, result)
+
+
+ at pytest.mark.skipif(not use_numpy, reason="numpy not installed")
+def test_to_latlon_numpy_static():
+ result = UTM.to_latlon(
+ np.array([166021.44317933032, 277707.83075574087, 544268.12794623]),
+ np.array([0.0, 331796.29167519242, 663220.7198366751]),
+ 31,
+ northern=True,
+ )
+ assert_latlon_equal(
+ (np.array([0.0, 3.0, 6.0]), np.array([0.0, 1.0, 3.4])), result
+ )
+
+
+def test_from_latlon_range_ok():
+ """from_latlon should work for good values"""
+ for i in range(-8000, 8400):
+ assert UTM.from_latlon(i / 100, 0)
+ for i in range(-18000, 18000):
+ assert UTM.from_latlon(0, i / 100)
+
+
+ at pytest.mark.parametrize(
+ "lat, lon",
+ [
+ (-100, 0),
+ (-80.1, 0),
+ (84.1, 0),
+ (100, 0),
+ (0, -300),
+ (0, -180.1),
+ (0, 180.1),
+ (0, 300),
+ (-100, -300),
+ (100, -300),
+ (-100, 300),
+ (100, 300),
+ ],
+)
+def test_from_latlon_range_fails(lat, lon):
+ """from_latlon should fail with out-of-bounds input"""
+ with pytest.raises(UTM.OutOfRangeError):
+ UTM.from_latlon(lat, lon)
+
+
+ at pytest.mark.parametrize(
+ "lat, lon, force_zone_number, force_zone_letter",
+ [(40.71435, -74.00597, 70, "T"), (40.71435, -74.00597, 18, "A")],
+)
+def test_from_latlon_range_forced_fails(
+ lat, lon, force_zone_number, force_zone_letter
+):
+ """from_latlon should fail with out-of-bounds input"""
+ with pytest.raises(UTM.OutOfRangeError):
+ UTM.from_latlon(lat, lon, force_zone_number, force_zone_letter)
+
+
+def test_to_latlon_range_ok():
+ """to_latlon should work for good values"""
+ for i in range(100000, 999999, 1000):
+ assert UTM.to_latlon(i, 5000000, 32, "U")
+ for i in range(10, 10000000, 1000):
+ assert UTM.to_latlon(500000, i, 32, "U")
+ for i in range(1, 60):
+ assert UTM.to_latlon(500000, 5000000, i, "U")
+ for i in range(ord("C"), ord("X")):
+ i = chr(i)
+ if i != "I" and i != "O":
+ UTM.to_latlon(500000, 5000000, 32, i)
+
+
+ at pytest.mark.parametrize(
+ "easting, northing, zone_number, zone_letter",
+ [
+ (0, 5000000, 32, "U"),
+ (99999, 5000000, 32, "U"),
+ (1000000, 5000000, 32, "U"),
+ (100000000000, 5000000, 32, "U"),
+ (500000, -100000, 32, "U"),
+ (500000, -1, 32, "U"),
+ (500000, 10000001, 32, "U"),
+ (500000, 50000000, 32, "U"),
+ (500000, 5000000, 0, "U"),
+ (500000, 5000000, 61, "U"),
+ (500000, 5000000, 1000, "U"),
+ (500000, 5000000, 32, "A"),
+ (500000, 5000000, 32, "B"),
+ (500000, 5000000, 32, "I"),
+ (500000, 5000000, 32, "O"),
+ (500000, 5000000, 32, "Y"),
+ (500000, 5000000, 32, "Z"),
+ ],
+)
+def test_to_latlon_range_checks(easting, northing, zone_number, zone_letter):
+ """to_latlon should fail with out-of-bounds input"""
+ with pytest.raises(UTM.OutOfRangeError):
+ UTM.to_latlon(0, 5000000, 32, "U")
+
+
+ at pytest.mark.parametrize(
+ "lat, lon, expected_number, expected_letter",
+ [
+ # test inside:
+ (56, 3, 32, "V"),
+ (56, 6, 32, "V"),
+ (56, 9, 32, "V"),
+ (56, 11.999999, 32, "V"),
+ (60, 3, 32, "V"),
+ (60, 6, 32, "V"),
+ (60, 9, 32, "V"),
+ (60, 11.999999, 32, "V"),
+ (63.999999, 3, 32, "V"),
+ (63.999999, 6, 32, "V"),
+ (63.999999, 9, 32, "V"),
+ (63.999999, 11.999999, 32, "V"),
+ # test left of:
+ (55.999999, 2.999999, 31, "U"),
+ (56, 2.999999, 31, "V"),
+ (60, 2.999999, 31, "V"),
+ (63.999999, 2.999999, 31, "V"),
+ (64, 2.999999, 31, "W"),
+ # test right of:
+ (55.999999, 12, 33, "U"),
+ (56, 12, 33, "V"),
+ (60, 12, 33, "V"),
+ (63.999999, 12, 33, "V"),
+ (64, 12, 33, "W"),
+ # test below:
+ (55.999999, 3, 31, "U"),
+ (55.999999, 6, 32, "U"),
+ (55.999999, 9, 32, "U"),
+ (55.999999, 11.999999, 32, "U"),
+ (55.999999, 12, 33, "U"),
+ # test above:
+ (64, 3, 31, "W"),
+ (64, 6, 32, "W"),
+ (64, 9, 32, "W"),
+ (64, 11.999999, 32, "W"),
+ (64, 12, 33, "W"),
+ # test edge:
+ (0, 180, 1, "N"),
+ (0, -180, 1, "N"),
+ (84, 180, 1, "X"),
+ (84, -180, 1, "X"),
+ ],
+)
+def test_from_latlon_zones(lat, lon, expected_number, expected_letter):
+ result = UTM.from_latlon(lat, lon)
+ assert result[2] == expected_number
+ assert result[3].upper() == expected_letter.upper()
+
+
+ at pytest.mark.parametrize(
+ "lat, lon, expected_number",
+ [
+ (40, 0, 31),
+ (40, 5.999999, 31),
+ (40, 6, 32),
+ (72, 0, 31),
+ (72, 5.999999, 31),
+ (72, 6, 31),
+ (72, 8.999999, 31),
+ (72, 9, 33),
+ ],
+)
+def test_limits(lat, lon, expected_number):
+ assert UTM.from_latlon(lat, lon)[2] == expected_number
+
+
+ at pytest.mark.parametrize(
+ "zone_number, zone_letter",
+ [
+ (10, "C"),
+ (10, "X"),
+ (10, "p"),
+ (10, "q"),
+ (20, "X"),
+ (1, "X"),
+ (60, "e"),
+ ],
+)
+def test_valid_zones(zone_number, zone_letter):
+ # should not raise any exceptions
+ assert UTM.check_valid_zone(zone_number, zone_letter) is None
+
+
+ at pytest.mark.parametrize(
+ "zone_number, zone_letter", [(-100, "C"), (20, "I"), (20, "O"), (0, "O")]
+)
+def test_invalid_zones(zone_number, zone_letter):
+ with pytest.raises(UTM.OutOfRangeError):
+ UTM.check_valid_zone(zone_number, zone_letter)
+
+
+ at pytest.mark.parametrize(
+ "lat, lon, utm, utm_kw, expected_number, expected_letter",
+ [
+ (40.71435, -74.00597, 19, "T", 19, "T"),
+ (40.71435, -74.00597, 17, "T", 17, "T"),
+ (40.71435, -74.00597, 18, "u", 18, "U"),
+ (40.71435, -74.00597, 18, "S", 18, "S"),
+ ],
+)
+def test_force_zone(lat, lon, utm, utm_kw, expected_number, expected_letter):
+ # test forcing zone ranges
+ # NYC should be zone 18T
+ result = UTM.from_latlon(lat, lon, utm, utm_kw)
+ assert result[2] == expected_number
+ assert result[3].upper() == expected_letter.upper()
+
+
+def assert_equal_lat(result, expected_lat, northern=None):
+ args = result[:3] if northern else result[:4]
+ lat, _ = UTM.to_latlon(*args, northern=northern, strict=False)
+ assert lat == pytest.approx(expected_lat, abs=0.001)
+
+
+def assert_equal_lon(result, expected_lon):
+ _, lon = UTM.to_latlon(*result[:4], strict=False)
+ assert lon == pytest.approx(expected_lon, abs=0.001)
+
+
+def test_force_east():
+ # Force point just west of anti-meridian to east zone 1
+ assert_equal_lon(UTM.from_latlon(0, 179.9, 1, "N"), 179.9)
+
+
+def test_force_west():
+ # Force point just east of anti-meridian to west zone 60
+ assert_equal_lon(UTM.from_latlon(0, -179.9, 60, "N"), -179.9)
+
+
+def test_force_north():
+ # Force southern point to northern zone letter
+ assert_equal_lat(UTM.from_latlon(-0.1, 0, 31, 'N'), -0.1)
+
+ # Again, using force northern
+ assert_equal_lat(
+ UTM.from_latlon(-0.1, 0, 31, force_northern=True), -0.1, northern=True)
+
+
+def test_force_south():
+ # Force northern point to southern zone letter
+ assert_equal_lat(UTM.from_latlon(0.1, 0, 31, 'M'), 0.1)
+
+ # Again, using force northern as False
+ assert_equal_lat(
+ UTM.from_latlon(0.1, 0, 31, force_northern=True), 0.1, northern=True)
+
+
+ at pytest.mark.skipif(not use_numpy, reason="numpy not installed")
+def test_no_force_numpy():
+ # Point above and below equator
+ lats = np.array([-0.1, 0.1])
+ with pytest.raises(ValueError,
+ match="latitudes must all have the same sign"):
+ UTM.from_latlon(lats, np.array([0, 0]))
+
+
+ at pytest.mark.skipif(not use_numpy, reason="numpy not installed")
+ at pytest.mark.parametrize("zone", ('N', 'M'))
+def test_force_numpy(zone):
+ # Point above and below equator
+ lats = np.array([-0.1, 0.1])
+
+ result = UTM.from_latlon(
+ lats, np.array([0, 0]), force_zone_letter=zone)
+ for expected_lat, easting, northing in zip(lats, *result[:2]):
+ assert_equal_lat(
+ (easting, northing, result[2], result[3]), expected_lat)
+
+
+ at pytest.mark.skipif(not use_numpy, reason="numpy not installed")
+ at pytest.mark.parametrize("force_northern", (True, False))
+def test_force_numpy_force_northern_true(force_northern):
+ # Point above and below equator
+ lats = np.array([-0.1, 0.1])
+
+ result = UTM.from_latlon(
+ lats, np.array([0, 0]), force_northern=force_northern)
+ for expected_lat, easting, northing in zip(lats, *result[:2]):
+ assert_equal_lat(
+ (easting, northing, result[2], result[3]), expected_lat,
+ northern=force_northern)
+
+
+def test_force_both():
+ # Force both letter and northern not allowed
+ with pytest.raises(ValueError, match="set either force_zone_letter or "
+ "force_northern, but not both"):
+ UTM.from_latlon(-0.1, 0, 31, 'N', True)
+
+
+def test_version():
+ assert isinstance(UTM.__version__, str) and "." in UTM.__version__
+
+
+ at pytest.mark.skipif(not use_numpy, reason="numpy not installed")
+def test_numpy_args_not_modified():
+ TEST_EASTING = 387358.0
+ TEST_NORTHING = 8145567.0
+ easting = np.array(TEST_EASTING)
+ northing = np.array(TEST_NORTHING)
+ zone = 55
+ letter = "K"
+ UTM.to_latlon(easting, northing, zone, letter)
+ assert easting == TEST_EASTING
+ assert northing == TEST_NORTHING
-class TestValidZones(unittest.TestCase):
- def test_valid_zones(self):
- # should not raise any exceptions
- UTM.check_valid_zone(10, 'C')
- UTM.check_valid_zone(10, 'X')
- UTM.check_valid_zone(10, 'p')
- UTM.check_valid_zone(10, 'q')
- UTM.check_valid_zone(20, 'X')
- UTM.check_valid_zone(1, 'X')
- UTM.check_valid_zone(60, 'e')
-
- def test_invalid_zones(self):
- self.assertRaises(UTM.OutOfRangeError, UTM.check_valid_zone, -100, 'C')
- self.assertRaises(UTM.OutOfRangeError, UTM.check_valid_zone, 20, 'I')
- self.assertRaises(UTM.OutOfRangeError, UTM.check_valid_zone, 20, 'O')
- self.assertRaises(UTM.OutOfRangeError, UTM.check_valid_zone, 0, 'O')
-
-
-class TestForcingZones(unittest.TestCase):
- def assert_zone_equal(self, result, expected_number, expected_letter):
- self.assertEqual(result[2], expected_number)
- self.assertEqual(result[3].upper(), expected_letter.upper())
-
- def test_force_zone(self):
- # test forcing zone ranges
- # NYC should be zone 18T
- self.assert_zone_equal(UTM.from_latlon(40.71435, -74.00597, 19, 'T'), 19, 'T')
- self.assert_zone_equal(UTM.from_latlon(40.71435, -74.00597, 17, 'T'), 17, 'T')
- self.assert_zone_equal(UTM.from_latlon(40.71435, -74.00597, 18, 'u'), 18, 'U')
- self.assert_zone_equal(UTM.from_latlon(40.71435, -74.00597, 18, 'S'), 18, 'S')
-
-
-class TestForcingAntiMeridian(unittest.TestCase):
- def assert_equal_lon(self, result, expected_lon):
- _, lon = UTM.to_latlon(*result[:4], strict=False)
- self.assertAlmostEqual(lon, expected_lon, 4)
-
- def test_force_east(self):
- # Force point just west of anti-meridian to east zone 1
- self.assert_equal_lon(
- UTM.from_latlon(0, 179.9, 1, 'N'), 179.9)
-
- def test_force_west(self):
- # Force point just east of anti-meridian to west zone 60
- self.assert_equal_lon(
- UTM.from_latlon(0, -179.9, 60, 'N'), -179.9)
-
-
-if __name__ == '__main__':
- unittest.main()
+ at pytest.mark.parametrize(
+ "zone_number, expected_lon",
+ [
+ (1, -177),
+ (12, -111),
+ (16, -87),
+ (31, 3),
+ (37, 39),
+ ],
+)
+def test_zone_number_to_central_longitude(zone_number, expected_lon):
+ lon = UTM.zone_number_to_central_longitude(zone_number)
+ assert lon == expected_lon
+
+
+ at pytest.mark.parametrize(
+ "zone_letter, expected_lat",
+ [
+ ("X", 78),
+ ("C", -76),
+ ("E", -60),
+ ("F", -52),
+ ("Q", 20),
+ ],
+)
+def test_zone_letter_to_central_latitude(zone_letter, expected_lat):
+ lat = UTM.zone_letter_to_central_latitude(zone_letter)
+ assert lat == expected_lat
=====================================
utm/__init__.py
=====================================
@@ -1,2 +1,3 @@
-from utm.conversion import to_latlon, from_latlon, latlon_to_zone_number, latitude_to_zone_letter, check_valid_zone
+from utm.conversion import to_latlon, from_latlon, latlon_to_zone_number, latitude_to_zone_letter, check_valid_zone, zone_number_to_central_longitude, zone_letter_to_central_latitude
from utm.error import OutOfRangeError
+from utm._version import __version__
=====================================
utm/_version.py
=====================================
@@ -0,0 +1 @@
+__version__ = "0.8.0"
=====================================
utm/conversion.py
=====================================
@@ -1,3 +1,4 @@
+from __future__ import division
from utm.error import OutOfRangeError
# For most use cases in this module, numpy is indistinguishable
@@ -16,7 +17,7 @@ K0 = 0.9996
E = 0.00669438
E2 = E * E
E3 = E2 * E
-E_P2 = E / (1.0 - E)
+E_P2 = E / (1 - E)
SQRT_E = mathlib.sqrt(1 - E)
_E = (1 - SQRT_E) / (1 + SQRT_E)
@@ -30,10 +31,10 @@ M2 = (3 * E / 8 + 3 * E2 / 32 + 45 * E3 / 1024)
M3 = (15 * E2 / 256 + 45 * E3 / 1024)
M4 = (35 * E3 / 3072)
-P2 = (3. / 2 * _E - 27. / 32 * _E3 + 269. / 512 * _E5)
-P3 = (21. / 16 * _E2 - 55. / 32 * _E4)
-P4 = (151. / 96 * _E3 - 417. / 128 * _E5)
-P5 = (1097. / 512 * _E4)
+P2 = (3 / 2 * _E - 27 / 32 * _E3 + 269 / 512 * _E5)
+P3 = (21 / 16 * _E2 - 55 / 32 * _E4)
+P4 = (151 / 96 * _E3 - 417 / 128 * _E5)
+P5 = (1097 / 512 * _E4)
R = 6378137
@@ -50,27 +51,27 @@ def in_bounds(x, lower, upper, upper_strict=False):
return lower <= x <= upper
-def check_valid_zone(zone_number, zone_letter):
+def check_valid_zone_letter(zone_letter):
+ zone_letter = zone_letter.upper()
+ if not 'C' <= zone_letter <= 'X' or zone_letter in ['I', 'O']:
+ raise OutOfRangeError('zone letter out of range (must be between C and X)')
+
+
+def check_valid_zone_number(zone_number):
if not 1 <= zone_number <= 60:
raise OutOfRangeError('zone number out of range (must be between 1 and 60)')
- if zone_letter:
- zone_letter = zone_letter.upper()
- if not 'C' <= zone_letter <= 'X' or zone_letter in ['I', 'O']:
- raise OutOfRangeError('zone letter out of range (must be between C and X)')
+def check_valid_zone(zone_number, zone_letter):
+ check_valid_zone_number(zone_number)
+ if zone_letter:
+ check_valid_zone_letter(zone_letter)
def mixed_signs(x):
return use_numpy and mathlib.min(x) < 0 and mathlib.max(x) >= 0
-def negative(x):
- if use_numpy:
- return mathlib.max(x) < 0
- return x < 0
-
-
def mod_angle(value):
"""Returns angle in radians to be between -pi and pi"""
return (value + mathlib.pi) % (2 * mathlib.pi) - mathlib.pi
@@ -96,7 +97,8 @@ def to_latlon(easting, northing, zone_number, zone_letter=None, northern=None, s
designators can be seen in [1]_
northern: bool
- You can set True or False to set this parameter. Default is None
+ You can set True (North) or False (South) as an alternative to
+ providing a zone letter. Default is None
strict: bool
Raise an OutOfRangeError if outside of bounds
@@ -115,7 +117,6 @@ def to_latlon(easting, northing, zone_number, zone_letter=None, northern=None, s
"""
if not zone_letter and northern is None:
raise ValueError('either zone_letter or northern needs to be set')
-
elif zone_letter and northern is not None:
raise ValueError('set either zone_letter or northern, but not both')
@@ -124,18 +125,15 @@ def to_latlon(easting, northing, zone_number, zone_letter=None, northern=None, s
raise OutOfRangeError('easting out of range (must be between 100,000 m and 999,999 m)')
if not in_bounds(northing, 0, 10000000):
raise OutOfRangeError('northing out of range (must be between 0 m and 10,000,000 m)')
-
+
check_valid_zone(zone_number, zone_letter)
-
+
if zone_letter:
zone_letter = zone_letter.upper()
northern = (zone_letter >= 'N')
x = easting - 500000
- y = northing
-
- if not northern:
- y -= 10000000
+ y = northing if northern else northing - 10000000
m = y / K0
mu = m / (R * M1)
@@ -171,9 +169,9 @@ def to_latlon(easting, northing, zone_number, zone_letter=None, northern=None, s
d5 = d4 * d
d6 = d5 * d
- latitude = (p_rad - (p_tan / r) *
- (d2 / 2 -
- d4 / 24 * (5 + 3 * p_tan2 + 10 * c - 4 * c2 - 9 * E_P2)) +
+ latitude = p_rad - (p_tan / r) * (
+ d2 / 2 -
+ d4 / 24 * (5 + 3 * p_tan2 + 10 * c - 4 * c2 - 9 * E_P2) +
d6 / 720 * (61 + 90 * p_tan2 + 298 * c + 45 * p_tan4 - 252 * E_P2 - 3 * c2))
longitude = (d -
@@ -186,7 +184,7 @@ def to_latlon(easting, northing, zone_number, zone_letter=None, northern=None, s
mathlib.degrees(longitude))
-def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=None):
+def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=None, force_northern=None):
"""This function converts Latitude and Longitude to UTM coordinate
Parameters
@@ -206,6 +204,11 @@ def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=N
You may force conversion to be included within one UTM zone
letter. For more information see utmzones [1]_
+ force_northern: bool
+ You can set True (North) or False (South) as an alternative to
+ forcing with a zone letter. When set, the returned zone_letter will
+ be None. Default is None
+
Returns
-------
easting: float or NumPy array
@@ -225,10 +228,12 @@ def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=N
.. _[1]: http://www.jaworski.ca/utmzones.htm
"""
- if not in_bounds(latitude, -80.0, 84.0):
+ if not in_bounds(latitude, -80, 84):
raise OutOfRangeError('latitude out of range (must be between 80 deg S and 84 deg N)')
- if not in_bounds(longitude, -180.0, 180.0):
+ if not in_bounds(longitude, -180, 180):
raise OutOfRangeError('longitude out of range (must be between 180 deg W and 180 deg E)')
+ if force_zone_letter and force_northern is not None:
+ raise ValueError('set either force_zone_letter or force_northern, but not both')
if force_zone_number is not None:
check_valid_zone(force_zone_number, force_zone_letter)
@@ -245,11 +250,16 @@ def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=N
else:
zone_number = force_zone_number
- if force_zone_letter is None:
+ if force_zone_letter is None and force_northern is None:
zone_letter = latitude_to_zone_letter(latitude)
else:
zone_letter = force_zone_letter
+ if force_northern is None:
+ northern = (zone_letter >= 'N')
+ else:
+ northern = force_northern
+
lon_rad = mathlib.radians(longitude)
central_lon = zone_number_to_central_longitude(zone_number)
central_lon_rad = mathlib.radians(central_lon)
@@ -276,10 +286,10 @@ def from_latlon(latitude, longitude, force_zone_number=None, force_zone_letter=N
northing = K0 * (m + n * lat_tan * (a2 / 2 +
a4 / 24 * (5 - lat_tan2 + 9 * c + 4 * c**2) +
a6 / 720 * (61 - 58 * lat_tan2 + lat_tan4 + 600 * c - 330 * E_P2)))
-
- if mixed_signs(latitude):
+ check_signs = force_northern is None and force_zone_letter is None
+ if check_signs and mixed_signs(latitude):
raise ValueError("latitudes must all have the same sign")
- elif negative(latitude):
+ elif not northern:
northing += 10000000
return easting, northing, zone_number, zone_letter
@@ -306,9 +316,13 @@ def latlon_to_zone_number(latitude, longitude):
if isinstance(longitude, mathlib.ndarray):
longitude = longitude.flat[0]
+ # Normalize longitude to be in the range [-180, 180)
+ longitude = (longitude % 360 + 540) % 360 - 180
+
+ # Special zone for Norway
if 56 <= latitude < 64 and 3 <= longitude < 12:
return 32
-
+ # Special zones for Svalbard
if 72 <= latitude <= 84 and longitude >= 0:
if longitude < 9:
return 31
@@ -323,4 +337,13 @@ def latlon_to_zone_number(latitude, longitude):
def zone_number_to_central_longitude(zone_number):
+ check_valid_zone_number(zone_number)
return (zone_number - 1) * 6 - 180 + 3
+
+def zone_letter_to_central_latitude(zone_letter):
+ check_valid_zone_letter(zone_letter)
+ zone_letter = zone_letter.upper()
+ if zone_letter == 'X':
+ return 78
+ else:
+ return -76 + (ZONE_LETTERS.index(zone_letter) * 8)
View it on GitLab: https://salsa.debian.org/debian-gis-team/utm/-/compare/a0ca4fed8d181abd20e6e089bfab7b2050b14b21...168e1cdd803669a7b628b980f3879db883728f6e
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/utm/-/compare/a0ca4fed8d181abd20e6e089bfab7b2050b14b21...168e1cdd803669a7b628b980f3879db883728f6e
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20250201/b3ded10a/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list