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://www.EPSG-Registry.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
13U{UTMUPS<https://GeographicLib.SourceForge.io/html/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_LAZY, _ALL_OTHER
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__ = '21.06.09'
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://www.EPSG-Registry.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 (latitudinal) UTM/UPS Band (C{'A'|'B'|'C'|'D'..'W'|'X'|'Y'|'Z'} or C{""}).
103        '''
104        return self._band
105
106    @Property_RO
107    def hemisphere(self):
108        '''Get the UTM/UPS hemisphere/-pole (C{'N'|'S'}).
109        '''
110        return self._hemisphere
111
112    @Property_RO
113    def utmups(self):
114        '''Get the UTM/UPS original (L{Utm}, L{Ups}).
115        '''
116        return self._utmups
117
118    def utmupsStr(self, B=False):
119        '''Get the UTM/UPS zone, band and hemisphere/-pole (C{str}).
120        '''
121        b = self.band if B else NN
122        h = s = self.hemisphere
123        if h:
124            s = _SPACE_
125        return NN(Fmt.zone(self.zone), b, s, h)
126
127    @Property_RO
128    def zone(self):
129        '''Get the (longitudinal) UTM/UPS zone (C{int}, C{1..60} for UTM, C{0} for UPS).
130        '''
131        return self._zone
132
133
134class EPSGError(_ValueError):
135    '''EPSG encode, decode or other L{Epsg} issue.
136    '''
137    pass
138
139
140def decode2(epsg):
141    '''Determine the UTM/USP zone and hemisphere from a given
142       U{EPSG<https://www.EPSG-Registry.org>}.
143
144       @arg epsg: The EPSG (L{Epsg}, C{str} or C{scalar}).
145
146       @return: A L{UtmUps2Tuple}C{(zone, hemipole)}.
147
148       @raise EPSGError: Invalid B{C{epsg}}.
149
150       @note: Coverage of UPS as zone C{0} follows I{Karney}'s function U{UTMUPS::DecodeEPSG
151              <https://GeographicLib.SourceForge.io/html/classGeographicLib_1_1UTMUPS.html>}.
152    '''
153    if isinstance(epsg, Epsg):
154        z, h = epsg.zone, epsg.hemisphere
155
156    else:
157        try:
158            e = int(epsg)  # int(long) OK
159            if _EPSG_N_01 <= e <= _EPSG_N_60:
160                z, h = int(e - _EPSG_N_01 + _UTM_ZONE_MIN), _N_
161
162            elif _EPSG_S_01 <= e <= _EPSG_S_60:
163                z, h = int(e - _EPSG_S_01 + _UTM_ZONE_MIN), _S_
164
165            elif e == _EPSG_N:
166                z, h = _UPS_ZONE, _N_
167
168            elif e == _EPSG_S:
169                z, h = _UPS_ZONE, _S_
170
171            else:
172                raise ValueError
173        except (TypeError, ValueError) as x:
174            raise EPSGError(epsg=epsg, txt=str(x))
175
176    return UtmUps2Tuple(z, h)
177
178
179def encode(zone, hemipole=NN, band=NN):
180    '''Determine the U{EPSG<https://www.EPSG-Registry.org>} code for
181       a given UTM/UPS zone number, hemisphere/pole and/or Band.
182
183       @arg zone: The (longitudinal) UTM zone (C{int}, 1..60) or UPS
184                  zone (C{int}, 0) or UTM zone with/-out (latitudinal)
185                  Band letter (C{str}, '01C'..'60X') or UPS zone
186                  with/-out (polar) Band letter (C{str}, '00A', '00B',
187                  '00Y' or '00Z').
188       @kwarg hemipole: UTM/UPS hemisphere or UPS projection top/center
189                        pole (C{str}, C{'N[orth]'} or C{'S[outh]'}).
190       @kwarg band: Optional (latitudinal) UTM or (polar) UPS Band
191                    letter (C{str}).
192
193       @return: C{EPSG} code (L{Epsg}).
194
195       @raise EPSGError: Invalid B{C{zone}}, B{C{hemipole}} or B{C{band}}.
196
197       @note: Coverage of UPS as zone C{0} follows I{Karney}'s function U{UTMUPS::EncodeEPSG
198              <https://GeographicLib.SourceForge.io/html/classGeographicLib_1_1UTMUPS.html>}.
199    '''
200    try:
201        z, B, hp = _to3zBhp(zone, band, hemipole=hemipole)  # in .ellipsoidalBase
202        if hp not in _NS_:
203            raise ValueError
204    except (TypeError, ValueError) as x:
205        raise EPSGError(zone=zone, hemipole=hemipole, band=band, txt=str(x))
206
207    if _UTM_ZONE_MIN <= z <= _UTM_ZONE_MAX:
208        e = z - _UTM_ZONE_MIN + (_EPSG_N_01 if hp == _N_ else _EPSG_S_01)
209    elif z == _UPS_ZONE:
210        e = _EPSG_N if hp == _N_ else _EPSG_S
211    else:
212        raise EPSGError(zone=zone)
213
214    e = Epsg(e)
215    e._band = B
216    # e._hemisphere = hp
217    return e
218
219
220__all__ += _ALL_OTHER(decode2, encode)
221
222# **) MIT License
223#
224# Copyright (C) 2016-2021 -- mrJean1 at Gmail -- All Rights Reserved.
225#
226# Permission is hereby granted, free of charge, to any person obtaining a
227# copy of this software and associated documentation files (the "Software"),
228# to deal in the Software without restriction, including without limitation
229# the rights to use, copy, modify, merge, publish, distribute, sublicense,
230# and/or sell copies of the Software, and to permit persons to whom the
231# Software is furnished to do so, subject to the following conditions:
232#
233# The above copyright notice and this permission notice shall be included
234# in all copies or substantial portions of the Software.
235#
236# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
237# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
238# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
239# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
240# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
241# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
242# OTHER DEALINGS IN THE SOFTWARE.
243