1

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

3

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

5

6 Classes 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

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

10 zones.

11

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

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

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

15'''

16

17 from pygeodesy.basics import isint, isstr, _xinstanceof

18 from pygeodesy.errors import _ValueError

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

20 from pygeodesy.lazily import _ALL_LAZY, _ALL_OTHER

21 from pygeodesy.namedTuples import UtmUps2Tuple

22 from pygeodesy.props import Property_RO

23 from pygeodesy.streprs import Fmt

24 from pygeodesy.units import Int

25 from pygeodesy.ups import Ups

26 from pygeodesy.utm import Utm

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

28 _UTM_ZONE_MAX, _UTMUPS_ZONE_INVALID

29

30__all__ = _ALL_LAZY.epsg

31__version__ = '22.09.24'

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

43 class 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=NN):

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

58 @return: New L{Epsg}.

59

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

61

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

63 '''

64 if isinstance(eisu, Epsg):

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

66 self._band = eisu.band

67 self._epsg = self # XXX eisu

68 self._hemisphere = eisu.hemisphere

69 self._utmups = eisu.utmups

70 self._zone = eisu.zone

71 if eisu.name:

72 self.name = eisu.name

73

74 elif isint(eisu):

75 self = int.__new__(cls, eisu)

76 self._epsg = eisu

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

78

79 elif isstr(eisu):

80 self = encode(eisu)

81

82 else:

83 u = eisu

84 _xinstanceof(Utm, Ups, eisu=u)

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

86 self._utmups = u

87 if u.name:

88 self.name = u.name

89

90 if name:

91 self.name = name

92 return self

93

94 def __repr__(self):

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

96

97 def __str__(self):

98 return int.__str__(self)

99

100 @Property_RO

101 def band(self):

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

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

104 '''

105 return self._band

106

107 @Property_RO

108 def hemisphere(self):

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

110 '''

111 return self._hemisphere

112

113 @Property_RO

114 def utmups(self):

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

116 '''

117 return self._utmups

118

119 def utmupsStr(self, B=False):

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

121 '''

122 b = self.band if B else NN

123 h = s = self.hemisphere

124 if h:

125 s = _SPACE_

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

127

128 @Property_RO

129 def zone(self):

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

131 '''

132 return self._zone

133

134

135 class EPSGError(_ValueError):

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

137 '''

138 pass

139

140

141 def decode2(epsg):

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

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

144

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

146

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

148

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

150

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

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

153 '''

154 if isinstance(epsg, Epsg):

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

156

157 else:

158 try:

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

160 if _EPSG_N_01 <= e <= _EPSG_N_60:

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

162

163 elif _EPSG_S_01 <= e <= _EPSG_S_60:

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

165

166 elif e == _EPSG_N:

167 z, h = _UPS_ZONE, _N_

168

169 elif e == _EPSG_S:

170 z, h = _UPS_ZONE, _S_

171

172 else:

173 raise ValueError(NN)

174 except (TypeError, ValueError) as x:

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

176

177 return UtmUps2Tuple(z, h)

178

179

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

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

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

183

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

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

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

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

188 '00Y' or '00Z').

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

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

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

192 letter (C{str}).

193

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

195

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

197

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

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

200 '''

201 try:

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

203 if hp not in _NS_:

204 raise ValueError

205 except (TypeError, ValueError) as x:

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

207

208 if _UTM_ZONE_MIN <= z <= _UTM_ZONE_MAX:

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

210 elif z == _UPS_ZONE:

211 e = _EPSG_N if hp == _N_ else _EPSG_S

212 else:

213 raise EPSGError(zone=zone)

214

215 e = Epsg(e)

216 e._band = B

217 # e._hemisphere = hp

218 return e

219

220

221__all__ += _ALL_OTHER(decode2, encode)

222

223# **) MIT License

224#

225# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.

226#

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

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

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

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

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

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

233#

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

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

236#

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

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

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

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

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

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

243# OTHER DEALINGS IN THE SOFTWARE.