Coverage for pygeodesy/epsg.py: 98%

100 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-04-09 11:05 -0400

1 

2# -*- coding: utf-8 -*- 

3 

4u'''European Petroleum Survey Group (EPSG) en-/decoding. 

5 

6Classes L{Epsg} and L{EPSGError} and functions to L{encode} and L{decode2} 

7(U{EPSG<https://EPSG.org>}) codes from and to U{UTM 

8<https://WikiPedia.org/wiki/Universal_Transverse_Mercator_coordinate_system>} and 

9U{UPS<https://WikiPedia.org/wiki/Universal_polar_stereographic_coordinate_system>} 

10zones. 

11 

12A pure Python implementation transcoded from I{Charles Karney}'s C++ class U{UTMUPS 

13<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1UTMUPS.html>}, 

14including coverage of UPS as zone C{0}. 

15''' 

16 

17from pygeodesy.basics import isint, isstr, _xinstanceof 

18from pygeodesy.errors import _ValueError 

19from pygeodesy.interns import NN, _N_, _NS_, _S_, _SPACE_ 

20from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY 

21from pygeodesy.namedTuples import UtmUps2Tuple 

22from pygeodesy.props import Property_RO 

23from pygeodesy.streprs import Fmt 

24from pygeodesy.units import Int 

25from pygeodesy.ups import Ups 

26from pygeodesy.utm import Utm 

27from pygeodesy.utmupsBase import _to3zBhp, _UPS_ZONE, _UTM_ZONE_MIN, \ 

28 _UTM_ZONE_MAX, _UTMUPS_ZONE_INVALID 

29 

30__all__ = _ALL_LAZY.epsg 

31__version__ = '24.08.05' 

32 

33# _EPSG_INVALID = _UTMUPS_ZONE_INVALID 

34_EPSG_N_01 = 32601 # EPSG code for UTM zone 01 N 

35_EPSG_N_60 = 32660 # EPSG code for UTM zone 60 N 

36_EPSG_N = 32661 # EPSG code for UPS pole N 

37 

38_EPSG_S_01 = 32701 # EPSG code for UTM zone 01 S 

39_EPSG_S_60 = 32760 # EPSG code for UTM zone 60 S 

40_EPSG_S = 32761 # EPSG code for UPS pole S 

41 

42 

43class Epsg(Int): 

44 '''U{EPSG<https://EPSG.org>} class, a named C{int}. 

45 ''' 

46 _band = NN 

47 _epsg = None 

48 _hemisphere = NN 

49 _utmups = None 

50 _zone = _UTMUPS_ZONE_INVALID 

51 

52 def __new__(cls, eisu, **name): 

53 '''New L{Epsg} (I{European Petroleum Survey Group}) code from a 

54 UTM/USP coordinate or other EPSG code. 

55 

56 @arg eisu: Other code (L{Epsg}, C{int}, C{str}, L{Utm} or L{Ups}). 

57 @kwarg name: Optional C{B{name}=NN} (C{str}). 

58 

59 @return: New L{Epsg}. 

60 

61 @raise TypeError: Invalid B{C{eisu}}. 

62 

63 @raise EPSGError: Invalid B{C{eisu}}. 

64 ''' 

65 if isinstance(eisu, Epsg): 

66 self = int.__new__(cls, int(eisu)) 

67 self._band = eisu.band 

68 self._epsg = self # XXX eisu 

69 self._hemisphere = eisu.hemisphere 

70 self._utmups = eisu.utmups 

71 self._zone = eisu.zone 

72 if eisu.name: 

73 self.name = eisu.name 

74 

75 elif isint(eisu): 

76 self = int.__new__(cls, eisu) 

77 self._epsg = eisu 

78 self._zone, self._hemisphere = decode2(eisu) # PYCHOK UtmUps2Tuple 

79 

80 elif isstr(eisu): 

81 self = encode(eisu) 

82 

83 else: 

84 u = eisu 

85 _xinstanceof(Utm, Ups, eisu=u) 

86 self = encode(u.zone, hemipole=u.hemisphere, band=u.band) # PYCHOK **kwds 

87 self._utmups = u 

88 if u.name: 

89 self.name = u.name 

90 

91 if name: 

92 self.rename(name) 

93 return self 

94 

95 def __repr__(self): 

96 return Fmt.PAREN(self.named, int.__repr__(self)) 

97 

98 def __str__(self): 

99 return int.__str__(self) 

100 

101 @Property_RO 

102 def band(self): 

103 '''Get the I{latitudinal} UTM or I{polar} UPS Band 

104 (C{'A'|'B'|'C'|'D'|..|'W'|'X'|'Y'|'Z'} or C{""}). 

105 ''' 

106 return self._band 

107 

108 @Property_RO 

109 def hemisphere(self): 

110 '''Get the UTM/UPS hemisphere/-pole (C{'N'|'S'}). 

111 ''' 

112 return self._hemisphere 

113 

114 @Property_RO 

115 def utmups(self): 

116 '''Get the UTM/UPS original (L{Utm}, L{Ups}). 

117 ''' 

118 return self._utmups 

119 

120 def utmupsStr(self, B=False): 

121 '''Get the UTM/UPS zone, band and hemisphere/-pole (C{str}). 

122 ''' 

123 b = self.band if B else NN 

124 h = s = self.hemisphere 

125 if h: 

126 s = _SPACE_ 

127 return NN(Fmt.zone(self.zone), b, s, h) 

128 

129 @Property_RO 

130 def zone(self): 

131 '''Get the (longitudinal) UTM/UPS zone (C{int}, C{1..60} for UTM, C{0} for UPS). 

132 ''' 

133 return self._zone 

134 

135 

136class EPSGError(_ValueError): 

137 '''EPSG encode, decode or other L{Epsg} issue. 

138 ''' 

139 pass 

140 

141 

142def decode2(epsg): 

143 '''Determine the UTM/USP zone and hemisphere from a given 

144 U{EPSG<https://EPSG.org>}. 

145 

146 @arg epsg: The EPSG (L{Epsg}, C{str} or C{scalar}). 

147 

148 @return: A L{UtmUps2Tuple}C{(zone, hemipole)}. 

149 

150 @raise EPSGError: Invalid B{C{epsg}}. 

151 

152 @note: Coverage of UPS as zone C{0} follows I{Karney}'s function U{UTMUPS::DecodeEPSG 

153 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1UTMUPS.html>}. 

154 ''' 

155 if isinstance(epsg, Epsg): 

156 z, h = epsg.zone, epsg.hemisphere 

157 

158 else: 

159 try: 

160 e = int(epsg) # int(long) OK 

161 if _EPSG_N_01 <= e <= _EPSG_N_60: 

162 z, h = int(e - _EPSG_N_01 + _UTM_ZONE_MIN), _N_ 

163 

164 elif _EPSG_S_01 <= e <= _EPSG_S_60: 

165 z, h = int(e - _EPSG_S_01 + _UTM_ZONE_MIN), _S_ 

166 

167 elif e == _EPSG_N: 

168 z, h = _UPS_ZONE, _N_ 

169 

170 elif e == _EPSG_S: 

171 z, h = _UPS_ZONE, _S_ 

172 

173 else: 

174 raise ValueError(NN) 

175 except (TypeError, ValueError) as x: 

176 raise EPSGError(epsg=epsg, cause=x) 

177 

178 return UtmUps2Tuple(z, h) 

179 

180 

181def encode(zone, hemipole=NN, band=NN): 

182 '''Determine the U{EPSG<https://EPSG.org>} code for 

183 a given UTM/UPS zone number, hemisphere/pole and/or Band. 

184 

185 @arg zone: The (longitudinal) UTM zone (C{int}, 1..60) or UPS 

186 zone (C{int}, 0) or UTM zone with/-out I{latitudinal} 

187 Band letter (C{str}, '01C'..'60X') or UPS zone 

188 with/-out I{polar} Band letter (C{str}, '00A', '00B', 

189 '00Y' or '00Z'). 

190 @kwarg hemipole: UTM/UPS hemisphere or UPS projection top/center 

191 pole (C{str}, C{'N[orth]'} or C{'S[outh]'}). 

192 @kwarg band: Optional I{latitudinal} UTM or I{polar} UPS Band 

193 letter (C{str}). 

194 

195 @return: C{EPSG} code (L{Epsg}). 

196 

197 @raise EPSGError: Invalid B{C{zone}}, B{C{hemipole}} or B{C{band}}. 

198 

199 @note: Coverage of UPS as zone C{0} follows I{Karney}'s function U{UTMUPS::EncodeEPSG 

200 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1UTMUPS.html>}. 

201 ''' 

202 try: 

203 z, B, hp = _to3zBhp(zone, band, hemipole=hemipole) # in .utmupsBase 

204 if hp not in _NS_: 

205 raise ValueError 

206 except (TypeError, ValueError) as x: 

207 raise EPSGError(zone=zone, hemipole=hemipole, band=band, cause=x) 

208 

209 if _UTM_ZONE_MIN <= z <= _UTM_ZONE_MAX: 

210 e = z - _UTM_ZONE_MIN + (_EPSG_N_01 if hp == _N_ else _EPSG_S_01) 

211 elif z == _UPS_ZONE: 

212 e = _EPSG_N if hp == _N_ else _EPSG_S 

213 else: 

214 raise EPSGError(zone=zone) 

215 

216 e = Epsg(e) 

217 e._band = B 

218 # e._hemisphere = hp 

219 return e 

220 

221 

222__all__ += _ALL_DOCS(decode2, encode) 

223 

224# **) MIT License 

225# 

226# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

227# 

228# Permission is hereby granted, free of charge, to any person obtaining a 

229# copy of this software and associated documentation files (the "Software"), 

230# to deal in the Software without restriction, including without limitation 

231# the rights to use, copy, modify, merge, publish, distribute, sublicense, 

232# and/or sell copies of the Software, and to permit persons to whom the 

233# Software is furnished to do so, subject to the following conditions: 

234# 

235# The above copyright notice and this permission notice shall be included 

236# in all copies or substantial portions of the Software. 

237# 

238# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 

239# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

240# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 

241# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 

242# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 

243# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 

244# OTHER DEALINGS IN THE SOFTWARE.