Coverage for pygeodesy/utmups.py: 98%
87 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-01 11:43 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-01 11:43 -0400
2# -*- coding: utf-8 -*-
4u'''I{Karney}'s UTM and UPS utilities.
6Functions L{parseUTMUPS5}, L{toUtmUps8}, L{UtmUps} and L{utmupsZoneBand5}
7to handle both I{Universal Transverse Mercator} (U{UTM
8<https://WikiPedia.org/wiki/Universal_Transverse_Mercator_coordinate_system>})
9and I{Universal Polar Stereographic} (U{UPS
10<https://WikiPedia.org/wiki/Universal_polar_stereographic_coordinate_system>})
11coordinates.
13A pure Python implementation, partially transcoded from C++ class U{UTMUPS
14<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1UTMUPS.html>}
15by I{Charles Karney}.
16'''
17# from pygeodesy.basics import map1 # from .namedTuples
18# from pygeodesy.datums import _WGS84 # from .utmupsBase
19from pygeodesy.errors import _IsnotError, RangeError, _ValueError, _xkwds_pop2
20from pygeodesy.interns import NN, _easting_, _MGRS_, _northing_, _NS_, \
21 _outside_, _range_, _SPACE_, _UPS_, _UTM_
22from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
23from pygeodesy.named import modulename
24from pygeodesy.namedTuples import UtmUps5Tuple, UtmUps8Tuple, map1
25# from pygeodesy.streprs import Fmt # from .utmupsBase
26from pygeodesy.units import Northing, _100km
27from pygeodesy.ups import parseUPS5, toUps8, Ups, UPSError, upsZoneBand5
28from pygeodesy.utm import parseUTM5, toUtm8, Utm, UTMError, utmZoneBand5
29from pygeodesy.utmupsBase import Fmt, _to4lldn, _to3zBhp, _UPS_ZONE, \
30 _UPS_ZONE_STR, _UTMUPS_ZONE_MIN, \
31 _UTMUPS_ZONE_MAX, _WGS84
33__all__ = _ALL_LAZY.utmups
34__version__ = '24.05.30'
36_MGRS_TILE = _100km # in .mgrs.Mgrs.tile
38_UPS_N_MAX = Northing( 27 * _MGRS_TILE)
39_UPS_N_MIN = Northing( 13 * _MGRS_TILE)
40_UPS_S_MAX = Northing( 32 * _MGRS_TILE)
41_UPS_S_MIN = Northing( 8 * _MGRS_TILE)
43_UTM_C_MAX = Northing( 9 * _MGRS_TILE)
44_UTM_C_MIN = Northing( 1 * _MGRS_TILE)
45_UTM_N_MAX = Northing( 95 * _MGRS_TILE)
46_UTM_N_MIN = Northing( 0 * _MGRS_TILE)
47_UTM_S_MAX = Northing(100 * _MGRS_TILE)
48_UTM_S_MIN = Northing( 10 * _MGRS_TILE)
50_UTM_N_SHIFT = _UTM_S_MAX - _UTM_N_MIN # South minus North UTM northing
53class _UpsMinMax(object): # XXX _NamedEnum or _NamedTuple
54 # UPS ranges for North, South pole
55 eMax = _UPS_N_MAX, _UPS_S_MAX
56 eMin = _UPS_N_MIN, _UPS_S_MIN
57 nMax = _UPS_N_MAX, _UPS_S_MAX
58 nMin = _UPS_N_MIN, _UPS_S_MIN
61class _UtmMinMax(object): # XXX _NamedEnum or _NamedTuple
62 # UTM ranges for Northern, Southern hemisphere
63 eMax = _UTM_C_MAX, _UTM_C_MAX
64 eMin = _UTM_C_MIN, _UTM_C_MIN
65 nMax = _UTM_N_MAX, Northing(_UTM_N_MAX + _UTM_N_SHIFT)
66 nMin = Northing(_UTM_S_MIN - _UTM_N_SHIFT), _UTM_S_MIN
69class UTMUPSError(_ValueError): # XXX (UTMError, UPSError)
70 '''Universal Transverse Mercator/Universal Polar Stereographic
71 (UTM/UPS) parse, validate or other issue.
72 '''
73 pass
76def parseUTMUPS5(strUTMUPS, datum=_WGS84, Utm=Utm, Ups=Ups, **name):
77 '''Parse a string representing a UTM or UPS coordinate, consisting
78 of C{"zone[band] hemisphere/pole easting northing"}.
80 @arg strUTMUPS: A UTM or UPS coordinate (C{str}).
81 @kwarg datum: Optional datum to use (L{Datum}).
82 @kwarg Utm: Optional class to return the UTM coordinate (L{Utm})
83 or C{None}.
84 @kwarg Ups: Optional class to return the UPS coordinate (L{Ups})
85 or C{None}.
86 @kwarg name: Optional B{C{Utm}} or B{C{Ups}} C{B{name}=NN} (C{str}).
88 @return: The UTM or UPS instance (B{C{Utm}} or B{C{Ups}}) or a
89 L{UtmUps5Tuple}C{(zone, hemipole, easting, northing, band)}
90 if B{C{Utm}} respectively B{C{Ups}} or both are C{None}.
91 The C{hemipole} is C{'N'|'S'}, the UTM hemisphere or UPS
92 pole, the UPS projection top/center.
94 @raise UTMUPSError: Invalid B{C{strUTMUPS}}.
96 @see: Functions L{pygeodesy.parseUTM5} and L{pygeodesy.parseUPS5}.
97 '''
98 try:
99 try:
100 u = parseUTM5(strUTMUPS, datum=datum, Utm=Utm, name=name)
101 except UTMError:
102 u = parseUPS5(strUTMUPS, datum=datum, Ups=Ups, name=name)
103 except (UTMError, UPSError) as x:
104 raise UTMUPSError(strUTMUPS=strUTMUPS, cause=x)
105 return u
108def toUtmUps8(latlon, lon=None, datum=None, falsed=True, Utm=Utm, Ups=Ups,
109 pole=NN, **name_cmoff):
110 '''Convert a lat-/longitude point to a UTM or UPS coordinate.
112 @arg latlon: Latitude (C{degrees}) or an (ellipsoidal)
113 geodetic C{LatLon} point.
114 @kwarg lon: Optional longitude (C{degrees}) or C{None}.
115 @kwarg datum: Optional datum to use this UTM coordinate,
116 overriding B{C{latlon}}'s datum (C{Datum}).
117 @kwarg falsed: False both easting and northing (C{bool}).
118 @kwarg Utm: Optional class to return the UTM coordinate (L{Utm})
119 or C{None}.
120 @kwarg Ups: Optional class to return the UPS coordinate (L{Ups})
121 or C{None}.
122 @kwarg pole: Optional top/center of UPS (stereographic)
123 projection (C{str}, C{'N[orth]'} or C{'S[outh]'}).
124 @kwarg name_cmoff: Optional B{C{Utm}} or B{C{Ups}} C{B{name}=NN}
125 (C{str}) and DEPRECATED keyword argument C{B{cmoff}=True}
126 to offset the longitude from the zone's central meridian
127 (C{bool}), use B{C{falsed}} instead and I{for UTM only}.
129 @return: The UTM or UPS coordinate (B{C{Utm}} respectively B{C{Ups}})
130 or a L{UtmUps8Tuple}C{(zone, hemipole, easting, northing,
131 band, datum, gamma, scale)} if B{C{Utm}} respectively
132 B{C{Ups}} is C{None} or B{C{cmoff}} is C{False}.
134 @raise RangeError: If B{C{lat}} outside the valid UTM or UPS bands
135 or if B{C{lat}} or B{C{lon}} outside the valid
136 range and L{pygeodesy.rangerrors} set to C{True}.
138 @raise TypeError: If B{C{latlon}} is not ellipsoidal or B{C{lon}}
139 value is missing of B{C{datum}} is invalid.
141 @raise UTMUPSError: UTM or UPS validation failed.
143 @raise ValueError: Invalid B{C{lat}} or B{C{lon}}.
145 @see: Functions L{pygeodesy.toUtm8} and L{pygeodesy.toUps8}.
146 '''
147 f, name = _xkwds_pop2(name_cmoff, cmoff=falsed)
148 lat, lon, d, n = _to4lldn(latlon, lon, datum, name)
149 z, _, p, lat, lon = utmupsZoneBand5(lat, lon)
150 if z == _UPS_ZONE:
151 u = toUps8(lat, lon, datum=d, falsed=f, Ups=Ups, name=n, pole=pole or p)
152 else:
153 u = toUtm8(lat, lon, datum=d, falsed=f, Utm=Utm, name=n)
154 return u
157def UtmUps(zone, hemipole, easting, northing, band=NN, datum=_WGS84,
158 falsed=True, **name):
159 '''Class-like function to create a UTM/UPS coordinate.
161 @kwarg zone: The UTM zone with/-out I{longitudinal} Band or UPS zone C{0}
162 or C{"00"} with/-out I{polar} Band (C{str} or C{int}).
163 @kwarg hemipole: UTM hemisphere or UPS top/center of projection
164 (C{str}, C{'N[orth]'} or C{'S[outh]'}).
165 @arg easting: Easting, see B{C{falsed}} (C{meter}).
166 @arg northing: Northing, see B{C{falsed}} (C{meter}).
167 @kwarg band: Optional, UTM I{latitudinal} C{'C'|'D'|..|'W'|'X'} or UPS
168 I{polar} Band letter C{'A'|'B'|'Y'|'Z'} Band letter (C{str}).
169 @kwarg datum: The coordinate's datum (L{Datum}).
170 @kwarg falsed: If C{True}, both B{C{easting}} and B{C{northing}} are
171 falsed (C{bool}).
172 @kwarg name: Optional L{Utm} or L{Ups} C{B{name}=NN} (C{str}).
174 @return: New UTM or UPS instance (L{Utm} or L{Ups}).
176 @raise TypeError: Invalid B{C{datum}}.
178 @raise UTMUPSError: UTM or UPS validation failed.
180 @see: Classes L{Utm} and L{Ups} and I{Karney}'s U{UTMUPS
181 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1UTMUPS.html>}.
182 '''
183 z, B, hp = _to3zBhp(zone, band, hemipole=hemipole)
184 U = Ups if z in (_UPS_ZONE, _UPS_ZONE_STR) else Utm
185 return U(z, hp, easting, northing, band=B, datum=datum,
186 falsed=falsed, **name)
189def utmupsValidate(coord, falsed=False, MGRS=False, Error=UTMUPSError):
190 '''Check a UTM or UPS coordinate.
192 @arg coord: The UTM or UPS coordinate (L{Utm}, L{Etm}, L{Ups}
193 or C{5+Tuple}).
194 @kwarg falsed: C{5+Tuple} easting and northing are falsed
195 (C{bool}), ignored otherwise.
196 @kwarg MGRS: Increase easting and northing ranges (C{bool}).
197 @kwarg Error: Optional error to raise, overriding the default
198 (L{UTMUPSError}).
200 @return: C{None} if validation passed.
202 @raise Error: Validation failed.
204 @see: Function L{utmupsValidateOK}.
205 '''
207 def _en(en, lo, hi, ename): # U, Error
208 try:
209 if lo <= float(en) <= hi:
210 return
211 except (TypeError, ValueError):
212 pass
213 t = _SPACE_(_outside_, U, _range_, _range_(lo, hi))
214 raise Error(ename, en, txt=t)
216 if isinstance(coord, (Ups, Utm)):
217 hemi = coord.hemisphere
218 enMM = coord.falsed
219 elif isinstance(coord, (UtmUps5Tuple, UtmUps8Tuple)):
220 hemi = coord.hemipole
221 enMM = falsed
222 else:
223 raise _IsnotError(Error=Error, coord=coord, *map1(modulename,
224 Utm, Ups, UtmUps5Tuple, UtmUps8Tuple))
225 band = coord.band
226 zone = coord.zone
228 z, B, h = _to3zBhp(zone, band, hemipole=hemi)
230 if z == _UPS_ZONE: # UPS
231 u, U, M = _MODS.ups, _UPS_, _UpsMinMax
232 else: # UTM
233 u, U, M = _MODS.utm, _UTM_, _UtmMinMax
235 if MGRS:
236 U, s = _MGRS_, _MGRS_TILE
237 else:
238 s = 0
240 i = _NS_.find(h)
241 if i < 0 or z < _UTMUPS_ZONE_MIN \
242 or z > _UTMUPS_ZONE_MAX \
243 or B not in u._Bands:
244 t = Fmt.PAREN(U, repr(_SPACE_(NN(Fmt.zone(z), B), h)))
245 raise Error(coord=t, zone=zone, band=band, hemisphere=hemi)
247 if enMM:
248 _en(coord.easting, M.eMin[i] - s, M.eMax[i] + s, _easting_) # PYCHOK .eMax .eMin
249 _en(coord.northing, M.nMin[i] - s, M.nMax[i] + s, _northing_) # PYCHOK .nMax .nMin
252def utmupsValidateOK(coord, falsed=False, ok=True):
253 '''Check a UTM or UPS coordinate.
255 @arg coord: The UTM or UPS coordinate (L{Utm}, L{Ups} or C{5+Tuple}).
256 @kwarg falsed: Use C{B{falsed}=True} if the C{5+Tuple} easting and
257 northing are falsed (C{bool}).
258 @kwarg ok: Result to return if validation passed (B{C{ok}}).
260 @return: B{C{ok}} if validation passed, otherwise the L{UTMUPSError}.
262 @see: Function L{utmupsValidate}.
263 '''
264 try:
265 utmupsValidate(coord, falsed=falsed)
266 return ok
267 except UTMUPSError as x:
268 return x
271def utmupsZoneBand5(lat, lon, cmoff=False, **name):
272 '''Return the UTM/UPS zone number, Band letter, hemisphere/pole
273 and clipped lat- and longitude for a given location.
275 @arg lat: Latitude in degrees (C{scalar} or C{str}).
276 @arg lon: Longitude in degrees (C{scalar} or C{str}).
277 @kwarg cmoff: If C{True}, offset longitude from the zone's central
278 meridian, I{for UTM only} (C{bool}).
279 @kwarg name: Optional C{B{name}=NN} (C{str}).
281 @return: A L{UtmUpsLatLon5Tuple}C{(zone, band, hemipole, lat, lon)}
282 where C{hemipole} is C{'N'|'S'}, the UTM hemisphere or UPS
283 pole, the UPS projection top/center.
285 @raise RangeError: If B{C{lat}} outside the valid UTM or UPS bands
286 or if B{C{lat}} or B{C{lon}} outside the valid
287 range and L{pygeodesy.rangerrors} set to C{True}.
289 @raise ValueError: Invalid B{C{lat}} or B{C{lon}}.
291 @see: Functions L{pygeodesy.utmZoneBand5} and L{pygeodesy.upsZoneBand5}.
292 '''
293 try:
294 return utmZoneBand5(lat, lon, cmoff=cmoff, **name)
295 except RangeError:
296 return upsZoneBand5(lat, lon, **name)
298# **) MIT License
299#
300# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
301#
302# Permission is hereby granted, free of charge, to any person obtaining a
303# copy of this software and associated documentation files (the "Software"),
304# to deal in the Software without restriction, including without limitation
305# the rights to use, copy, modify, merge, publish, distribute, sublicense,
306# and/or sell copies of the Software, and to permit persons to whom the
307# Software is furnished to do so, subject to the following conditions:
308#
309# The above copyright notice and this permission notice shall be included
310# in all copies or substantial portions of the Software.
311#
312# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
313# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
314# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
315# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
316# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
317# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
318# OTHER DEALINGS IN THE SOFTWARE.