diff --git a/.travis.yml b/.travis.yml index da055a6d7..ebaa98a13 100644 --- a/.travis.yml +++ b/.travis.yml @@ -115,6 +115,12 @@ install: script: - python -c "import pyproj; pyproj.Proj('epsg:4269')" - make test-coverage + - | + if [ "$TRAVIS_PYTHON_VERSION" = "3.5" ]; then + make check-type; + else + make check; + fi # Building and uploading docs with doctr - set -e - | diff --git a/MANIFEST.in b/MANIFEST.in index 8415bd422..8ddd0dff2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,6 +7,7 @@ include pyproj/*.pyd include pyproj/*.pyx include pyproj/*.pxd include pyproj/*.pxi +include pyproj/*.pyi include test/sample.out include test/conftest.py recursive-include docs * diff --git a/Makefile b/Makefile index bd6ea012f..98ffe9368 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,10 @@ clean-cython: ## clean the cython files lint: ## check style with flake8 flake8 --max-line-length 88 setup.py pyproj/ test/ docs/ -check: lint ## flake8 black isort check +check-type: + mypy pyproj + +check: lint check-type ## flake8 black isort check black --check setup.py pyproj/ test/ docs/ isort --check --recursive -m 3 -w 88 -tc -p test setup.py pyproj/ test/ docs/ diff --git a/docs/history.rst b/docs/history.rst index c457d6149..e26e0b490 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -4,6 +4,7 @@ Change Log 2.6.0 ~~~~~ * ENH: Added Proj.get_factors() (issue #503) +* ENH: Added type hints (issue #369) 2.5.0 ~~~~~ diff --git a/pyproj/_crs.pxd b/pyproj/_crs.pxd index 509c02a27..7dfd9f079 100644 --- a/pyproj/_crs.pxd +++ b/pyproj/_crs.pxd @@ -33,7 +33,7 @@ cdef class Base: cdef readonly object name cdef readonly object _remarks cdef readonly object _scope - + cdef _set_base_info(self) cdef class _CRSParts(Base): pass diff --git a/pyproj/_crs.pyi b/pyproj/_crs.pyi new file mode 100644 index 000000000..1aa7e680d --- /dev/null +++ b/pyproj/_crs.pyi @@ -0,0 +1,236 @@ +from typing import Any, Iterable, List, Optional, Tuple, Union + +from pyproj.crs.enums import CoordinateOperationType +from pyproj.enums import ProjVersion, WktVersion + +class Axis: + name: str + abbrev: str + direction: str + unit_conversion_factor: float + unit_name: str + unit_auth_code: str + unit_code: str + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + +class AreaOfUse: + west: float + south: float + east: float + north: float + name: str + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + @property + def bounds(self) -> tuple[float]: ... + +class Base: + name: str + @property + def remarks(self) -> str: ... + @property + def scope(self) -> str: ... + def to_wkt( + self, + version: Union[WktVersion, str] = WktVersion.WKT2_2019, + pretty: bool = False, + ) -> str: ... + def to_json(self, pretty: bool = False, indentation: int = 2) -> str: ... + def to_json_dict(self) -> dict: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: Any) -> bool: ... + def is_exact_same(self, other: Any) -> bool: ... + +class _CRSParts(Base): + @classmethod + def from_user_input(cls, user_input: Any) -> "_CRSParts": ... + +class Ellipsoid(_CRSParts): + semi_major_metre: float + semi_minor_metre: float + is_semi_minor_computed: float + inverse_flattening: float + @staticmethod + def from_authority(auth_name: str, code: Union[int, str]) -> "Ellipsoid": ... + @staticmethod + def from_epsg(code: Union[int, str]) -> "Ellipsoid": ... + @staticmethod + def from_string(ellipsoid_string: str) -> "Ellipsoid": ... + @staticmethod + def from_json_dict(ellipsoid_dict: dict) -> "Ellipsoid": ... + @staticmethod + def from_json(ellipsoid_json_str: str) -> "Ellipsoid": ... + @staticmethod + def from_name( + ellipsoid_name: str, auth_name: Optional[str] = None + ) -> "Ellipsoid": ... + +class PrimeMeridian(_CRSParts): + longitude: float + unit_conversion_factor: str + unit_name: str + @staticmethod + def from_authority(auth_name: str, code: Union[int, str]) -> "PrimeMeridian": ... + @staticmethod + def from_epsg(code: Union[int, str]) -> "PrimeMeridian": ... + @staticmethod + def from_string(prime_meridian_string: str) -> "PrimeMeridian": ... + @staticmethod + def from_json_dict(prime_meridian_dict: dict) -> "PrimeMeridian": ... + @staticmethod + def from_json(prime_meridian_json_str: str) -> "PrimeMeridian": ... + @staticmethod + def from_name( + prime_meridian_name: str, auth_name: Optional[str] = None + ) -> "PrimeMeridian": ... + +class Datum(_CRSParts): + type_name: str + @property + def ellipsoid(self) -> Optional[Ellipsoid]: ... + @property + def prime_meridian(self) -> Optional[PrimeMeridian]: ... + @staticmethod + def from_authority(auth_name: str, code: Union[int, str]) -> "Datum": ... + @staticmethod + def from_epsg(code: Union[int, str]) -> "Datum": ... + @staticmethod + def from_string(datum_string: str) -> "Datum": ... + @staticmethod + def from_json_dict(datum_dict: dict) -> "Datum": ... + @staticmethod + def from_json(datum_json_str: str) -> "Datum": ... + @staticmethod + def from_name(datum_name: str, auth_name: Optional[str] = None) -> "Datum": ... + +class CoordinateSystem(_CRSParts): + def __init__(self) -> None: ... + @property + def axis_list(self) -> Iterable[Axis]: ... + @staticmethod + def from_string(coordinate_system_string: str) -> "CoordinateSystem": ... + @staticmethod + def from_json_dict(coordinate_system_dict: dict) -> "CoordinateSystem": ... + @staticmethod + def from_json(coordinate_system_json_str: str) -> "CoordinateSystem": ... + +class Param: + name: str + auth_name: str + code: str + value: str + unit_conversion_factor: float + unit_name: str + unit_auth_name: str + unit_code: str + unit_category: str + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + +class Grid: + short_name: str + full_name: str + package_name: str + url: str + direct_download: str + open_license: str + available: str + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + +class CoordinateOperation(_CRSParts): + method_name: str + method_auth_name: str + method_code: str + accuracy: float + is_instantiable: bool + has_ballpark_transformation: bool + type_name: str + @property + def params(self) -> Iterable[Param]: ... + @property + def grids(self) -> Iterable[Grid]: ... + @property + def area_of_use(self) -> Optional[AreaOfUse]: ... + @property + def towgs84(self) -> Iterable[float]: ... + @property + def operations(self) -> Tuple["CoordinateOperation"]: ... + def __init__(self) -> None: ... + def __repr__(self) -> str: ... + @staticmethod + def from_authority( + auth_name: str, code: Union[int, str] + ) -> "CoordinateOperation": ... + @staticmethod + def from_epsg(code: Union[int, str]) -> "CoordinateOperation": ... + @staticmethod + def from_string(ellipsoid_string: str) -> "CoordinateOperation": ... + @staticmethod + def from_json_dict(ellipsoid_dict: dict) -> "CoordinateOperation": ... + @staticmethod + def from_json(ellipsoid_json_str: str) -> "CoordinateOperation": ... + def to_proj4( + self, version: Union[ProjVersion, int] = ProjVersion.PROJ_5 + ) -> str: ... + @staticmethod + def from_name( + coordinate_operation_name: str, + auth_name: Optional[str] = None, + coordinate_operation_type: Union[ + CoordinateOperationType, str + ] = CoordinateOperationType.CONVERSION, + ) -> "CoordinateOperation": ... + +class _CRS(Base): + srs: str + type_name: str + def __init__(self, proj_string: str) -> None: ... + @property + def ellipsoid(self) -> Optional[Ellipsoid]: ... + @property + def area_of_use(self) -> Optional[AreaOfUse]: ... + @property + def axis_info(self) -> List[Axis]: ... + @property + def prime_meridian(self) -> Optional[PrimeMeridian]: ... + @property + def datum(self) -> Optional[Datum]: ... + @property + def sub_crs_list(self) -> Iterable["_CRS"]: ... + @property + def source_crs(self) -> Optional["_CRS"]: ... + @property + def target_crs(self) -> Optional["_CRS"]: ... + @property + def geodetic_crs(self) -> Optional["_CRS"]: ... + @property + def coordinate_system(self) -> Optional[CoordinateSystem]: ... + @property + def coordinate_operation(self) -> Optional[CoordinateOperation]: ... + def to_proj4( + self, version: Union[ProjVersion, int] = ProjVersion.PROJ_5 + ) -> str: ... + def to_epsg(self, min_confidence: int = 70) -> Optional[int]: ... + def to_authority( + self, auth_name: Optional[str] = None, min_confidence: int = 70 + ): ... + @property + def is_geographic(self) -> bool: ... + @property + def is_projected(self) -> bool: ... + @property + def is_vertical(self) -> bool: ... + @property + def is_bound(self) -> bool: ... + @property + def is_engineering(self) -> bool: ... + @property + def is_geocentric(self) -> bool: ... + def equals(self, other: Any, ignore_axis_order: bool) -> bool: ... + +def is_proj(proj_string: str) -> bool: ... +def is_wkt(proj_string: str) -> bool: ... +def _load_proj_json(in_proj_json: str) -> dict: ... diff --git a/pyproj/_crs.pyx b/pyproj/_crs.pyx index b9d810cc5..a3132e50e 100644 --- a/pyproj/_crs.pyx +++ b/pyproj/_crs.pyx @@ -389,7 +389,7 @@ cdef class Base: if self.context != NULL: proj_context_destroy(self.context) - def _set_base_info(self): + cdef _set_base_info(self): """ Set the name of the PJ """ @@ -410,7 +410,10 @@ cdef class Base: """ .. versionadded:: 2.4.0 - str: Remarks about object. + Returns + ------- + str: + Remarks about object. """ return self._remarks @@ -419,7 +422,10 @@ cdef class Base: """ .. versionadded:: 2.4.0 - str: Scope of object. + Returns + ------- + str: + Scope of object. """ return self._scope @@ -446,7 +452,7 @@ cdef class Base: Returns ------- - str: The WKT string. + str """ return _to_wkt(self.context, self.projobj, version, pretty=pretty) @@ -465,7 +471,7 @@ cdef class Base: Returns ------- - str: The JSON string. + str """ cdef const char* options[3] multiline = b"MULTILINE=NO" @@ -494,7 +500,7 @@ cdef class Base: Returns ------- - dict: The JSON dictionary. + dict """ return json.loads(self.to_json()) @@ -619,7 +625,8 @@ cdef class CoordinateSystem(_CRSParts): """ Returns ------- - list[Axis]: The Axis list for the coordinate system. + list[Axis]: + The Axis list for the coordinate system. """ if self._axis_list is not None: return self._axis_list @@ -1594,7 +1601,8 @@ cdef class Datum(_CRSParts): """ Returns ------- - Ellipsoid: The ellipsoid object with associated attributes. + Ellipsoid: + The ellipsoid object with associated attributes. """ if self._ellipsoid is not None: return None if self._ellipsoid is False else self._ellipsoid @@ -1617,7 +1625,8 @@ cdef class Datum(_CRSParts): """ Returns ------- - PrimeMeridian: The CRS prime meridian object with associated attributes. + PrimeMeridian: + The CRS prime meridian object with associated attributes. """ if self._prime_meridian is not None: return None if self._prime_meridian is False else self._prime_meridian @@ -2137,7 +2146,8 @@ cdef class CoordinateOperation(_CRSParts): """ Returns ------- - list[Param]: The coordinate operation parameters. + List[Param]: + The coordinate operation parameters. """ if self._params is not None: return self._params @@ -2163,7 +2173,8 @@ cdef class CoordinateOperation(_CRSParts): """ Returns ------- - list[Grid]: The coordinate operation grids. + List[Grid]: + The coordinate operation grids. """ if self._grids is not None: return self._grids @@ -2189,7 +2200,8 @@ cdef class CoordinateOperation(_CRSParts): """ Returns ------- - AreaOfUse: The area of use object with associated attributes. + AreaOfUse: + The area of use object with associated attributes. """ if self._area_of_use is not None: return self._area_of_use @@ -2208,7 +2220,9 @@ cdef class CoordinateOperation(_CRSParts): Returns ------- - str: The PROJ string. + str: + The PROJ string. + """ return _to_proj4(self.context, self.projobj, version) @@ -2217,8 +2231,9 @@ cdef class CoordinateOperation(_CRSParts): """ Returns ------- - list(float): A list of 3 or 7 towgs84 values if they exist. - Otherwise an empty list. + List[float]: + A list of 3 or 7 towgs84 values if they exist. + """ if self._towgs84 is not None: return self._towgs84 @@ -2244,7 +2259,11 @@ cdef class CoordinateOperation(_CRSParts): """ .. versionadded:: 2.4.0 - tuple[CoordinateOperation]: The operations in a concatenated operation. + Returns + ------- + Tuple[CoordinateOperation]: + The operations in a concatenated operation. + """ if self._operations is not None: return self._operations @@ -2327,7 +2346,8 @@ cdef class _CRS(Base): """ Returns ------- - list[Axis]: The list of axis information. + List[Axis]: + The list of axis information. """ return self.coordinate_system.axis_list if self.coordinate_system else [] @@ -2336,7 +2356,8 @@ cdef class _CRS(Base): """ Returns ------- - AreaOfUse: The area of use object with associated attributes. + AreaOfUse: + The area of use object with associated attributes. """ if self._area_of_use is not None: return self._area_of_use @@ -2350,7 +2371,8 @@ cdef class _CRS(Base): Returns ------- - Ellipsoid: The ellipsoid object with associated attributes. + Ellipsoid: + The ellipsoid object with associated attributes. """ if self._ellipsoid is not None: return None if self._ellipsoid is False else self._ellipsoid @@ -2375,7 +2397,8 @@ cdef class _CRS(Base): Returns ------- - PrimeMeridian: The prime meridian object with associated attributes. + PrimeMeridian: + The prime meridian object with associated attributes. """ if self._prime_meridian is not None: return None if self._prime_meridian is True else self._prime_meridian @@ -2400,7 +2423,7 @@ cdef class _CRS(Base): Returns ------- - Datum: The datum. + Datum """ if self._datum is not None: return None if self._datum is False else self._datum @@ -2430,7 +2453,7 @@ cdef class _CRS(Base): Returns ------- - CoordinateSystem: The coordinate system. + CoordinateSystem """ if self._coordinate_system is not None: return None if self._coordinate_system is False else self._coordinate_system @@ -2459,7 +2482,7 @@ cdef class _CRS(Base): Returns ------- - CoordinateOperation: The coordinate operation. + CoordinateOperation """ if self._coordinate_operation is not None: return ( @@ -2487,10 +2510,12 @@ cdef class _CRS(Base): @property def source_crs(self): """ + The the base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS, + or the source CRS of a CoordinateOperation. + Returns ------- - _CRS: The the base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS, - or the source CRS of a CoordinateOperation. + _CRS """ if self._source_crs is not None: return None if self._source_crs is False else self._source_crs @@ -2512,7 +2537,8 @@ cdef class _CRS(Base): Returns ------- - _CRS: The hub CRS of a BoundCRS or the target CRS of a CoordinateOperation. + _CRS: + The hub CRS of a BoundCRS or the target CRS of a CoordinateOperation. """ if self._target_crs is not None: return None if self._target_crs is False else self._target_crs @@ -2534,7 +2560,7 @@ cdef class _CRS(Base): Returns ------- - list[_CRS] + List[_CRS] """ if self._sub_crs_list is not None: return self._sub_crs_list @@ -2564,9 +2590,11 @@ cdef class _CRS(Base): """ .. versionadded:: 2.2.0 + The the geodeticCRS / geographicCRS from the CRS. + Returns ------- - _CRS: The the geodeticCRS / geographicCRS from the CRS. + _CRS """ if self._geodetic_crs is not None: return self._geodetic_crs if self. _geodetic_crs is not False else None @@ -2597,7 +2625,7 @@ cdef class _CRS(Base): Returns ------- - str: The PROJ string. + str """ warnings.warn( "You will likely lose important projection information when " @@ -2639,7 +2667,8 @@ cdef class _CRS(Base): Returns ------- - int or None: The best matching EPSG code matching the confidence level. + Optional[int]: + The best matching EPSG code matching the confidence level. """ auth_info = self.to_authority( auth_name="EPSG", @@ -2684,8 +2713,8 @@ cdef class _CRS(Base): Returns ------- - tuple(str, str) or None: The best matching (, ) - matching the confidence level. + tuple(str, str) or None: + The best matching (, ) for the confidence level. """ # get list of possible matching projections cdef PJ_OBJ_LIST *proj_list = NULL @@ -2768,7 +2797,8 @@ cdef class _CRS(Base): Returns ------- - bool: True if the CRS has this property. + bool: + True if the CRS has this property. """ if self.sub_crs_list: sub_crs = self.sub_crs_list[sub_crs_index] @@ -2792,7 +2822,8 @@ cdef class _CRS(Base): Returns ------- - bool: True if the CRS is in geographic (lon/lat) coordinates. + bool: + True if the CRS is in geographic (lon/lat) coordinates. """ return self._is_crs_property( "is_geographic", @@ -2813,7 +2844,8 @@ cdef class _CRS(Base): Returns ------- - bool: True if CRS is projected. + bool: + True if CRS is projected. """ return self._is_crs_property( "is_projected", @@ -2832,7 +2864,8 @@ cdef class _CRS(Base): Returns ------- - bool: True if CRS is vertical. + bool: + True if CRS is vertical. """ return self._is_crs_property( "is_vertical", @@ -2845,7 +2878,8 @@ cdef class _CRS(Base): """ Returns ------- - bool: True if CRS is bound. + bool: + True if CRS is bound. """ return self._type == PJ_TYPE_BOUND_CRS @@ -2856,7 +2890,8 @@ cdef class _CRS(Base): Returns ------- - bool: True if CRS is local/engineering. + bool: + True if CRS is local/engineering. """ return self._type == PJ_TYPE_ENGINEERING_CRS @@ -2868,7 +2903,8 @@ cdef class _CRS(Base): Returns ------- - bool: True if CRS is in geocentric (x/y) coordinates. + bool: + True if CRS is in geocentric (x/y) coordinates. """ if self.is_bound: return self.source_crs.is_geocentric diff --git a/pyproj/_datadir.pyi b/pyproj/_datadir.pyi new file mode 100644 index 000000000..0fde7d4fd --- /dev/null +++ b/pyproj/_datadir.pyi @@ -0,0 +1 @@ +def pyproj_global_context_initialize() -> None: ... diff --git a/pyproj/_geod.pxd b/pyproj/_geod.pxd index f8cb38b23..7e65f4ff5 100644 --- a/pyproj/_geod.pxd +++ b/pyproj/_geod.pxd @@ -28,8 +28,8 @@ cdef extern from "geodesic.h": cdef class Geod: cdef geod_geodesic _geod_geodesic cdef readonly object initstring - cdef readonly object a - cdef readonly object b - cdef readonly object f - cdef readonly object es - cdef readonly object sphere + cdef readonly double a + cdef readonly double b + cdef readonly double f + cdef readonly double es + cdef readonly bint sphere diff --git a/pyproj/_geod.pyi b/pyproj/_geod.pyi new file mode 100644 index 000000000..a996a0c01 --- /dev/null +++ b/pyproj/_geod.pyi @@ -0,0 +1,35 @@ +from typing import Any, Tuple, Type + +geodesic_version_str: str + +class Geod: + initstring: str + a: float + b: float + f: float + es: float + sphere: bool + def __init__( + self, a: float, f: float, sphere: bool, b: float, es: float + ) -> None: ... + def __reduce__(self) -> Tuple[Type["Geod"], str]: ... + def __repr__(self) -> str: ... + def _fwd( + self, lons: Any, lats: Any, az: Any, dist: Any, radians: bool = False + ) -> None: ... + def _inv( + self, lons1: Any, lats1: Any, lons2: Any, lats2: Any, radians: bool = False + ) -> None: ... + def _npts( + self, + lon1: float, + lat1: float, + lon2: float, + lat2: float, + npts: int, + radians: bool = False, + ) -> Tuple[Tuple[float], Tuple[float]]: ... + def _line_length(self, lons: Any, lats: Any, radians: bool = False) -> float: ... + def _polygon_area_perimeter( + self, lons: Any, lats: Any, radians: bool = False + ) -> Tuple[float, float]: ... diff --git a/pyproj/_geod.pyx b/pyproj/_geod.pyx index 6860fb7d8..ff6c6b7b4 100644 --- a/pyproj/_geod.pyx +++ b/pyproj/_geod.pyx @@ -12,16 +12,14 @@ geodesic_version_str = "{0}.{1}.{2}".format( ) cdef class Geod: - def __init__(self, a, f, sphere, b, es): + def __init__(self, double a, double f, bint sphere, double b, double es): geod_init(&self._geod_geodesic, a, f) self.a = a self.f = f - if isinstance(a, float) and a.is_integer(): - # convert 'a' only for initstring - a = int(a) - if f == 0.0: - f = 0 - self.initstring = pystrdecode(cstrencode("+a=%s +f=%s" % (a, f))) + # convert 'a' only for initstring + a_str = int(a) if a.is_integer() else a + f_str = int(f) if f.is_integer() else f + self.initstring = pystrdecode(cstrencode("+a=%s +f=%s" % (a_str, f_str))) self.sphere = sphere self.b = b self.es = es @@ -189,7 +187,8 @@ cdef class Geod: Returns ------- - float: The total distance. + float: + The total distance. """ cdef PyBuffWriteManager lonbuff = PyBuffWriteManager(lons) @@ -251,7 +250,8 @@ cdef class Geod: Returns ------- - (float, float): The area (meter^2) and permimeter (meters) of the polygon. + (float, float): + The area (meter^2) and permimeter (meters) of the polygon. """ cdef PyBuffWriteManager lonbuff = PyBuffWriteManager(lons) diff --git a/pyproj/_list.pyi b/pyproj/_list.pyi new file mode 100644 index 000000000..4d49bf74a --- /dev/null +++ b/pyproj/_list.pyi @@ -0,0 +1,20 @@ +from typing import Dict, List, NamedTuple, Union + +from pyproj.enums import PJType + +def get_proj_operations_map() -> Dict[str, str]: ... +def get_ellps_map() -> Dict[str, Dict[str, float]]: ... +def get_prime_meridians_map() -> Dict[str, str]: ... + +class Unit(NamedTuple): + id: str + to_meter: str + name: str + factor: float + +def get_units_map() -> Dict[str, Unit]: ... +def get_angular_units_map() -> Dict[str, Unit]: ... +def get_authorities() -> List[str]: ... +def get_codes( + auth_name: str, pj_type: Union[PJType, str], allow_deprecated: bool = False +) -> List[str]: ... diff --git a/pyproj/_list.pyx b/pyproj/_list.pyx index 9e4951f37..392af76f1 100644 --- a/pyproj/_list.pyx +++ b/pyproj/_list.pyx @@ -11,7 +11,8 @@ def get_proj_operations_map(): """ Returns ------- - dict: operations supported by PROJ. + dict: + Operations supported by PROJ. """ cdef PJ_OPERATIONS *proj_operations = proj_list_operations() cdef int iii = 0 @@ -27,7 +28,8 @@ def get_ellps_map(): """ Returns ------- - dict: ellipsoids supported by PROJ. + dict: + Ellipsoids supported by PROJ. """ cdef PJ_ELLPS *proj_ellps = proj_list_ellps() cdef int iii = 0 @@ -48,7 +50,8 @@ def get_prime_meridians_map(): """ Returns ------- - dict: prime meridians supported by PROJ. + dict: + Prime Meridians supported by PROJ. """ cdef PJ_PRIME_MERIDIANS *prime_meridians = proj_list_prime_meridians() cdef int iii = 0 @@ -67,7 +70,8 @@ def get_units_map(): """ Returns ------- - dict: units supported by PROJ + dict: + Units supported by PROJ """ cdef PJ_UNITS *proj_units = proj_list_units() cdef int iii = 0 @@ -87,7 +91,8 @@ def get_angular_units_map(): """ Returns ------- - dict: angular units supported by PROJ + dict: + Angular units supported by PROJ """ cdef PJ_UNITS *proj_units = proj_list_angular_units() cdef int iii = 0 @@ -109,7 +114,8 @@ def get_authorities(): Returns ------- - list[str]: Authorities in PROJ database. + List[str]: + Authorities in PROJ database. """ cdef PJ_CONTEXT* context = proj_context_create() pyproj_context_initialize(context, True) @@ -172,7 +178,8 @@ def get_codes(auth_name, pj_type, allow_deprecated=False): Returns ------- - list[str]: Codes associated with authorities in PROJ database. + List[str]: + Codes associated with authorities in PROJ database. """ cdef PJ_CONTEXT* context = NULL cdef PJ_TYPE cpj_type = PJ_TYPE_MAP[PJType.create(pj_type)] diff --git a/pyproj/_proj.pyi b/pyproj/_proj.pyi new file mode 100644 index 000000000..6ccf46b24 --- /dev/null +++ b/pyproj/_proj.pyi @@ -0,0 +1,33 @@ +from typing import Any, NamedTuple + +proj_version_str: str + +class Factors(NamedTuple): + meridional_scale: float + parallel_scale: float + areal_scale: float + angular_distortion: float + meridian_parallel_angle: float + meridian_convergence: float + tissot_semimajor: float + tissot_semiminor: float + dx_dlam: float + dx_dphi: float + dy_dlam: float + dy_dphi: float + +class _Proj: + srs: str + def __init__(self, proj_string: str) -> None: ... + @property + def definition(self) -> str: ... + @property + def has_inverse(self) -> bool: ... + def _fwd(self, lons: Any, lats: Any, errcheck: bool = False) -> None: ... + def _inv(self, x: Any, y: Any, errcheck: bool = False) -> None: ... + def _get_factors( + self, longitude: Any, latitude: Any, radians: bool, errcheck: bool + ) -> Factors: ... + def __repr__(self) -> str: ... + def _is_equivalent(self, other: "_Proj") -> bool: ... + def is_exact_same(self, other: Any) -> bool: ... diff --git a/pyproj/_transformer.pyi b/pyproj/_transformer.pyi new file mode 100644 index 000000000..4c37b8ce8 --- /dev/null +++ b/pyproj/_transformer.pyi @@ -0,0 +1,76 @@ +from typing import Any, Iterable, List, NamedTuple, Optional, Tuple, Union + +from pyproj._crs import _CRS, AreaOfUse, Base, CoordinateOperation +from pyproj.enums import TransformDirection + +class AreaOfInterest(NamedTuple): + west_lon_degree: float + south_lat_degree: float + east_lon_degree: float + north_lat_degree: float + +class _TransformerGroup: + _transformers: Any + _unavailable_operations: List[CoordinateOperation] + _best_available: bool + def __init__( + self, + crs_from: _CRS, + crs_to: _CRS, + skip_equivalent: bool = False, + always_xy: bool = False, + area_of_interest: Optional[AreaOfInterest] = None, + ) -> None: ... + +class _Transformer(Base): + input_geographic: bool + output_geographic: bool + is_pipeline: bool + skip_equivalent: bool + projections_equivalent: bool + projections_exact_same: bool + type_name: str + @property + def id(self) -> str: ... + @property + def description(self) -> str: ... + @property + def definition(self) -> str: ... + @property + def has_inverse(self) -> bool: ... + @property + def accuracy(self) -> float: ... + @property + def area_of_use(self) -> AreaOfUse: ... + @property + def operations(self) -> Union[Tuple[CoordinateOperation], None]: ... + @staticmethod + def from_crs( + crs_from: _CRS, + crs_to: _CRS, + skip_equivalent: bool = False, + always_xy: bool = False, + area_of_interest: Optional[AreaOfInterest] = None, + ) -> "_Transformer": ... + @staticmethod + def from_pipeline(proj_pipeline: str) -> "_Transformer": ... + def _transform( + self, + inx: Any, + iny: Any, + inz: Any, + intime: Any, + direction: Union[TransformDirection, str], + radians: bool, + errcheck: bool, + ) -> None: ... + def _transform_sequence( + self, + stride: int, + inseq: Iterable[Iterable[float]], + switch: bool, + direction: Union[TransformDirection, str], + time_3rd: bool, + radians: bool, + errcheck: bool, + ) -> None: ... diff --git a/pyproj/_transformer.pyx b/pyproj/_transformer.pyx index 1d0a60e39..df0bb9ce1 100644 --- a/pyproj/_transformer.pyx +++ b/pyproj/_transformer.pyx @@ -244,7 +244,8 @@ cdef class _Transformer(Base): """ Returns ------- - AreaOfUse: The area of use object with associated attributes. + AreaOfUse: + The area of use object with associated attributes. """ if self._area_of_use is not None: return self._area_of_use @@ -256,7 +257,8 @@ cdef class _Transformer(Base): """ .. versionadded:: 2.4.0 - tuple[CoordinateOperation]: The operations in a concatenated operation. + Tuple[CoordinateOperation]: + The operations in a concatenated operation. """ if self._operations is not None: return self._operations @@ -399,7 +401,16 @@ cdef class _Transformer(Base): @cython.boundscheck(False) @cython.wraparound(False) - def _transform(self, inx, iny, inz, intime, direction, radians, errcheck): + def _transform( + self, + object inx, + object iny, + object inz, + object intime, + object direction, + bint radians, + bint errcheck, + ): if self.projections_exact_same or (self.projections_equivalent and self.skip_equivalent): return tmp_pj_direction = _PJ_DIRECTION_MAP[TransformDirection.create(direction)] @@ -485,8 +496,14 @@ cdef class _Transformer(Base): @cython.boundscheck(False) @cython.wraparound(False) def _transform_sequence( - self, Py_ssize_t stride, object inseq, bint switch, - direction, time_3rd, radians, errcheck + self, + Py_ssize_t stride, + object inseq, + bint switch, + object direction, + bint time_3rd, + bint radians, + bint errcheck, ): if self.projections_exact_same or (self.projections_equivalent and self.skip_equivalent): return diff --git a/pyproj/crs/coordinate_operation.py b/pyproj/crs/coordinate_operation.py index 587193a85..6504f225d 100644 --- a/pyproj/crs/coordinate_operation.py +++ b/pyproj/crs/coordinate_operation.py @@ -1,5 +1,6 @@ import warnings from distutils.version import LooseVersion +from typing import Any from pyproj._crs import CoordinateOperation from pyproj._proj import proj_version_str @@ -17,12 +18,12 @@ class AlbersEqualAreaConversion(CoordinateOperation): def __new__( cls, - latitude_first_parallel, - latitude_second_parallel, - latitude_false_origin=0.0, - longitude_false_origin=0.0, - easting_false_origin=0.0, - northing_false_origin=0.0, + latitude_first_parallel: float, + latitude_second_parallel: float, + latitude_false_origin: float = 0.0, + longitude_false_origin: float = 0.0, + easting_false_origin: float = 0.0, + northing_false_origin: float = 0.0, ): """ Parameters @@ -109,10 +110,10 @@ class AzumuthalEquidistantConversion(CoordinateOperation): def __new__( cls, - latitude_natural_origin=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, + latitude_natural_origin: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, ): """ Parameters @@ -176,12 +177,12 @@ class GeostationarySatelliteConversion(CoordinateOperation): def __new__( cls, - sweep_angle_axis, - satellite_height, - latitude_natural_origin=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, + sweep_angle_axis: str, + satellite_height: float, + latitude_natural_origin: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, ): """ Parameters @@ -265,10 +266,10 @@ class LambertAzumuthalEqualAreaConversion(CoordinateOperation): def __new__( cls, - latitude_natural_origin=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, + latitude_natural_origin: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, ): """ Parameters @@ -332,12 +333,12 @@ class LambertConformalConic2SPConversion(CoordinateOperation): def __new__( cls, - latitude_first_parallel, - latitude_second_parallel, - latitude_false_origin=0.0, - longitude_false_origin=0.0, - easting_false_origin=0.0, - northing_false_origin=0.0, + latitude_first_parallel: float, + latitude_second_parallel: float, + latitude_false_origin: float = 0.0, + longitude_false_origin: float = 0.0, + easting_false_origin: float = 0.0, + northing_false_origin: float = 0.0, ): """ Parameters @@ -417,11 +418,11 @@ class LambertConformalConic1SPConversion(CoordinateOperation): def __new__( cls, - latitude_natural_origin=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, - scale_factor_natural_origin=1.0, + latitude_natural_origin: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, + scale_factor_natural_origin: float = 1.0, ): """ Parameters @@ -493,10 +494,10 @@ class LambertCylindricalEqualAreaConversion(CoordinateOperation): def __new__( cls, - latitude_first_parallel=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, + latitude_first_parallel: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, ): """ Parameters @@ -566,10 +567,10 @@ class LambertCylindricalEqualAreaScaleConversion(CoordinateOperation): def __new__( cls, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, - scale_factor_natural_origin=1.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, + scale_factor_natural_origin: float = 1.0, ): """ Parameters @@ -601,7 +602,9 @@ def __new__( ) ) if LooseVersion(proj_version_str) >= LooseVersion("6.3.1"): - return cls.from_json(CRS(proj_string).coordinate_operation.to_json()) + return cls.from_json( + CRS(proj_string).coordinate_operation.to_json() # type: ignore + ) return cls.from_string(proj_string) @@ -616,11 +619,11 @@ class MercatorAConversion(CoordinateOperation): def __new__( cls, - latitude_natural_origin=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, - scale_factor_natural_origin=1.0, + latitude_natural_origin: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, + scale_factor_natural_origin: float = 1.0, ): """ Parameters @@ -692,10 +695,10 @@ class MercatorBConversion(CoordinateOperation): def __new__( cls, - latitude_first_parallel=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, + latitude_first_parallel: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, ): """ Parameters @@ -759,13 +762,13 @@ class HotineObliqueMercatorBConversion(CoordinateOperation): def __new__( cls, - latitude_projection_centre, - longitude_projection_centre, - azimuth_initial_line, - angle_from_rectified_to_skew_grid, - scale_factor_on_initial_line=1.0, - easting_projection_centre=0.0, - northing_projection_centre=0.0, + latitude_projection_centre: float, + longitude_projection_centre: float, + azimuth_initial_line: float, + angle_from_rectified_to_skew_grid: float, + scale_factor_on_initial_line: float = 1.0, + easting_projection_centre: float = 0.0, + northing_projection_centre: float = 0.0, ): """ Parameters @@ -852,10 +855,10 @@ class OrthographicConversion(CoordinateOperation): def __new__( cls, - latitude_natural_origin=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, + latitude_natural_origin: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, ): """ Parameters @@ -919,11 +922,11 @@ class PolarStereographicAConversion(CoordinateOperation): def __new__( cls, - latitude_natural_origin, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, - scale_factor_natural_origin=1.0, + latitude_natural_origin: float, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, + scale_factor_natural_origin: float = 1.0, ): """ Parameters @@ -996,10 +999,10 @@ class PolarStereographicBConversion(CoordinateOperation): def __new__( cls, - latitude_standard_parallel=0.0, - longitude_origin=0.0, - false_easting=0.0, - false_northing=0.0, + latitude_standard_parallel: float = 0.0, + longitude_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, ): """ Parameters @@ -1062,7 +1065,10 @@ class SinusoidalConversion(CoordinateOperation): """ def __new__( - cls, longitude_natural_origin=0.0, false_easting=0.0, false_northing=0.0 + cls, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, ): """ Parameters @@ -1115,11 +1121,11 @@ class StereographicConversion(CoordinateOperation): def __new__( cls, - latitude_natural_origin=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, - scale_factor_natural_origin=1.0, + latitude_natural_origin: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, + scale_factor_natural_origin: float = 1.0, ): """ Parameters @@ -1187,7 +1193,7 @@ class UTMConversion(CoordinateOperation): https://proj.org/operations/projections/utm.html """ - def __new__(cls, zone, hemisphere="N"): + def __new__(cls, zone: str, hemisphere: str = "N"): """ Parameters ---------- @@ -1212,11 +1218,11 @@ class TransverseMercatorConversion(CoordinateOperation): def __new__( cls, - latitude_natural_origin=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, - scale_factor_natural_origin=1.0, + latitude_natural_origin: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, + scale_factor_natural_origin: float = 1.0, ): """ Parameters @@ -1288,12 +1294,12 @@ class VerticalPerspectiveConversion(CoordinateOperation): def __new__( cls, - viewpoint_height, - latitude_topocentric_origin=0.0, - longitude_topocentric_origin=0.0, - ellipsoidal_height_topocentric_origin=0.0, - false_easting=0.0, - false_northing=0.0, + viewpoint_height: float, + latitude_topocentric_origin: float = 0.0, + longitude_topocentric_origin: float = 0.0, + ellipsoidal_height_topocentric_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, ): """ Parameters @@ -1371,14 +1377,14 @@ class RotatedLatitudeLongitudeConversion(CoordinateOperation): https://proj.org/operations/projections/ob_tran.html """ - def __new__(cls, o_lat_p, o_lon_p, lon_0=0.0): + def __new__(cls, o_lat_p: float, o_lon_p: float, lon_0: float = 0.0): """ Parameters ---------- o_lat_p: float Latitude of the North pole of the unrotated source CRS, expressed in the rotated geographic CRS. - o_lon_p: + o_lon_p: float Longitude of the North pole of the unrotated source CRS, expressed in the rotated geographic CRS. lon_0: float, optional @@ -1410,11 +1416,11 @@ class EquidistantCylindricalConversion(CoordinateOperation): def __new__( cls, - latitude_first_parallel=0.0, - latitude_natural_origin=0.0, - longitude_natural_origin=0.0, - false_easting=0.0, - false_northing=0.0, + latitude_first_parallel: float = 0.0, + latitude_natural_origin: float = 0.0, + longitude_natural_origin: float = 0.0, + false_easting: float = 0.0, + false_northing: float = 0.0, ): """ Parameters @@ -1487,18 +1493,20 @@ class ToWGS84Transformation(CoordinateOperation): def __new__( cls, - source_crs, - x_axis_translation=0, - y_axis_translation=0, - z_axis_translation=0, - x_axis_rotation=0, - y_axis_rotation=0, - z_axis_rotation=0, - scale_difference=0, + source_crs: Any, + x_axis_translation: float = 0, + y_axis_translation: float = 0, + z_axis_translation: float = 0, + x_axis_rotation: float = 0, + y_axis_rotation: float = 0, + z_axis_rotation: float = 0, + scale_difference: float = 0, ): """ Parameters ---------- + source_crs: Any + Input to create the Source CRS. x_axis_translation: float, optional X-axis translation. Defaults to 0.0. y_axis_translation: float, optional diff --git a/pyproj/crs/coordinate_system.py b/pyproj/crs/coordinate_system.py index 6674438b4..1ba13f94d 100644 --- a/pyproj/crs/coordinate_system.py +++ b/pyproj/crs/coordinate_system.py @@ -1,3 +1,5 @@ +from typing import Union + from pyproj._crs import CoordinateSystem from pyproj.crs.enums import ( Cartesian2DCSAxis, @@ -55,7 +57,10 @@ class Ellipsoidal2DCS(CoordinateSystem): This generates an Ellipsoidal 2D Coordinate System """ - def __new__(cls, axis=Ellipsoidal2DCSAxis.LONGITUDE_LATITUDE): + def __new__( + cls, + axis: Union[Ellipsoidal2DCSAxis, str] = Ellipsoidal2DCSAxis.LONGITUDE_LATITUDE, + ): """ Parameters ---------- @@ -122,7 +127,12 @@ class Ellipsoidal3DCS(CoordinateSystem): This generates an Ellipsoidal 3D Coordinate System """ - def __new__(cls, axis=Ellipsoidal3DCSAxis.LONGITUDE_LATITUDE_HEIGHT): + def __new__( + cls, + axis: Union[ + Ellipsoidal3DCSAxis, str + ] = Ellipsoidal3DCSAxis.LONGITUDE_LATITUDE_HEIGHT, + ): """ Parameters ---------- @@ -265,7 +275,9 @@ class Cartesian2DCS(CoordinateSystem): This generates an Cartesian 2D Coordinate System """ - def __new__(cls, axis=Cartesian2DCSAxis.EASTING_NORTHING): + def __new__( + cls, axis: Union[Cartesian2DCSAxis, str] = Cartesian2DCSAxis.EASTING_NORTHING + ): """ Parameters ---------- @@ -346,7 +358,7 @@ class VerticalCS(CoordinateSystem): This generates an Vertical Coordinate System """ - def __new__(cls, axis=VerticalCSAxis.GRAVITY_HEIGHT): + def __new__(cls, axis: Union[VerticalCSAxis, str] = VerticalCSAxis.GRAVITY_HEIGHT): """ Parameters ---------- diff --git a/pyproj/crs/crs.py b/pyproj/crs/crs.py index c93b1859b..00ef701cc 100644 --- a/pyproj/crs/crs.py +++ b/pyproj/crs/crs.py @@ -4,10 +4,12 @@ to the coordinate reference system (CRS) information. Original Author: Alan D. Snow [github.com/snowman2] (2019) + """ import json import re import warnings +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union from pyproj._crs import ( # noqa _CRS, @@ -35,7 +37,7 @@ from pyproj.geod import Geod -def _prepare_from_dict(projparams): +def _prepare_from_dict(projparams: dict) -> str: # check if it is a PROJ JSON dict if "proj" not in projparams and "init" not in projparams: return json.dumps(projparams) @@ -55,7 +57,7 @@ def _prepare_from_dict(projparams): return _prepare_from_string(" ".join(pjargs)) -def _prepare_from_string(in_crs_string): +def _prepare_from_string(in_crs_string: str) -> str: if not in_crs_string: raise CRSError("CRS is empty or invalid: {!r}".format(in_crs_string)) elif "{" in in_crs_string: @@ -73,7 +75,7 @@ def _prepare_from_string(in_crs_string): # make sure the projection starts with +proj or +init starting_params = ("+init", "+proj", "init", "proj") if not in_crs_string.startswith(starting_params): - kvpairs = [] + kvpairs = [] # type: List[str] first_item_inserted = False for kvpair in in_crs_string.split(): if not first_item_inserted and (kvpair.startswith(starting_params)): @@ -106,11 +108,11 @@ def _prepare_from_string(in_crs_string): return in_crs_string -def _prepare_from_authority(auth_name, auth_code): +def _prepare_from_authority(auth_name: str, auth_code: Union[str, int]): return "{}:{}".format(auth_name, auth_code) -def _prepare_from_epsg(auth_code): +def _prepare_from_epsg(auth_code: Union[str, int]): return _prepare_from_authority("epsg", auth_code) @@ -137,7 +139,7 @@ class CRS(_CRS): """ - def __init__(self, projparams=None, **kwargs): + def __init__(self, projparams: Any = None, **kwargs) -> None: """ Initialize a CRS class instance with: - PROJ string @@ -286,14 +288,14 @@ def __init__(self, projparams=None, **kwargs): elif isinstance(projparams, (list, tuple)) and len(projparams) == 2: projstring = _prepare_from_authority(*projparams) elif hasattr(projparams, "to_wkt"): - projstring = projparams.to_wkt() + projstring = projparams.to_wkt() # type: ignore else: raise CRSError("Invalid CRS input: {!r}".format(projparams)) super().__init__(projstring) @staticmethod - def from_authority(auth_name, code): + def from_authority(auth_name: str, code: Union[str, int]) -> "CRS": """ .. versionadded:: 2.2.0 @@ -313,7 +315,7 @@ def from_authority(auth_name, code): return CRS(_prepare_from_authority(auth_name, code)) @staticmethod - def from_epsg(code): + def from_epsg(code: Union[str, int]) -> "CRS": """Make a CRS from an EPSG code Parameters @@ -328,7 +330,7 @@ def from_epsg(code): return CRS(_prepare_from_epsg(code)) @staticmethod - def from_proj4(in_proj_string): + def from_proj4(in_proj_string: str) -> "CRS": """ .. versionadded:: 2.2.0 @@ -348,7 +350,7 @@ def from_proj4(in_proj_string): return CRS(_prepare_from_string(in_proj_string)) @staticmethod - def from_wkt(in_wkt_string): + def from_wkt(in_wkt_string: str) -> "CRS": """ .. versionadded:: 2.2.0 @@ -368,7 +370,7 @@ def from_wkt(in_wkt_string): return CRS(_prepare_from_string(in_wkt_string)) @staticmethod - def from_string(in_crs_string): + def from_string(in_crs_string: str) -> "CRS": """ Make a CRS from: @@ -389,7 +391,7 @@ def from_string(in_crs_string): """ return CRS(_prepare_from_string(in_crs_string)) - def to_string(self): + def to_string(self) -> str: """ .. versionadded:: 2.2.0 @@ -401,7 +403,7 @@ def to_string(self): Returns ------- - str: String representation of the CRS. + str """ auth_info = self.to_authority(min_confidence=100) if auth_info: @@ -409,7 +411,7 @@ def to_string(self): return self.srs @staticmethod - def from_user_input(value): + def from_user_input(value: str) -> "CRS": """ Initialize a CRS class instance with: - PROJ string @@ -436,23 +438,23 @@ def from_user_input(value): return value return CRS(value) - def get_geod(self): + def get_geod(self) -> Optional[Geod]: """ Returns ------- - pyproj.geod.Geod: Geod object based on the ellipsoid. + pyproj.geod.Geod: + Geod object based on the ellipsoid. """ if self.ellipsoid is None: return None - in_kwargs = { - "a": self.ellipsoid.semi_major_metre, - "rf": self.ellipsoid.inverse_flattening, - "b": self.ellipsoid.semi_minor_metre, - } - return Geod(**in_kwargs) + return Geod( + a=self.ellipsoid.semi_major_metre, + rf=self.ellipsoid.inverse_flattening, + b=self.ellipsoid.semi_minor_metre, + ) @staticmethod - def from_dict(proj_dict): + def from_dict(proj_dict: dict) -> "CRS": """ .. versionadded:: 2.2.0 @@ -470,7 +472,7 @@ def from_dict(proj_dict): return CRS(_prepare_from_dict(proj_dict)) @staticmethod - def from_json(crs_json): + def from_json(crs_json: str) -> "CRS": """ .. versionadded:: 2.4.0 @@ -488,7 +490,7 @@ def from_json(crs_json): return CRS.from_json_dict(_load_proj_json(crs_json)) @staticmethod - def from_json_dict(crs_dict): + def from_json_dict(crs_dict: dict) -> "CRS": """ .. versionadded:: 2.4.0 @@ -505,7 +507,7 @@ def from_json_dict(crs_dict): """ return CRS(json.dumps(crs_dict)) - def to_dict(self): + def to_dict(self) -> dict: """ .. versionadded:: 2.2.0 @@ -517,7 +519,8 @@ def to_dict(self): Returns ------- - dict: PROJ params in dict format. + dict: + PROJ params in dict format. """ @@ -547,7 +550,11 @@ def parse(val): return {key: value for key, value in items if value is not False} - def to_cf(self, wkt_version=WktVersion.WKT2_2019, errcheck=False): + def to_cf( + self, + wkt_version: Union[WktVersion, str] = WktVersion.WKT2_2019, + errcheck: bool = False, + ) -> dict: """ .. versionadded:: 2.2.0 @@ -568,15 +575,20 @@ def to_cf(self, wkt_version=WktVersion.WKT2_2019, errcheck=False): Returns ------- - dict: CF-1.8 version of the projection. + dict: + CF-1.8 version of the projection. """ unknown_names = ("unknown", "undefined") - cf_dict = {"crs_wkt": self.to_wkt(wkt_version)} + cf_dict = {"crs_wkt": self.to_wkt(wkt_version)} # type: Dict[str, Any] # handle bound CRS - if self.is_bound and self.coordinate_operation.towgs84: - sub_cf = self.source_crs.to_cf(errcheck=errcheck) + if ( + self.is_bound + and self.coordinate_operation + and self.coordinate_operation.towgs84 + ): + sub_cf = self.source_crs.to_cf(errcheck=errcheck) # type: ignore sub_cf.pop("crs_wkt") cf_dict.update(sub_cf) cf_dict["towgs84"] = self.coordinate_operation.towgs84 @@ -595,7 +607,7 @@ def to_cf(self, wkt_version=WktVersion.WKT2_2019, errcheck=False): vert_json = self.to_json_dict() if "geoid_model" in vert_json: cf_dict["geoid_name"] = vert_json["geoid_model"]["name"] - if self.datum.name not in unknown_names: + if self.datum and self.datum.name not in unknown_names: cf_dict["geopotential_datum_name"] = self.datum.name return cf_dict @@ -624,14 +636,14 @@ def to_cf(self, wkt_version=WktVersion.WKT2_2019, errcheck=False): self.coordinate_operation.method_name.lower() ](self.coordinate_operation) ) - if self.datum.name not in unknown_names: + if self.datum and self.datum.name not in unknown_names: cf_dict["horizontal_datum_name"] = self.datum.name else: cf_dict["grid_mapping_name"] = "latitude_longitude" return cf_dict # handle projected CRS - if self.is_projected and self.datum.name not in unknown_names: + if self.is_projected and self.datum and self.datum.name not in unknown_names: cf_dict["horizontal_datum_name"] = self.datum.name coordinate_operation = None if not self.is_bound and self.is_projected: @@ -664,7 +676,7 @@ def to_cf(self, wkt_version=WktVersion.WKT2_2019, errcheck=False): return cf_dict @staticmethod - def from_cf(in_cf, errcheck=False): + def from_cf(in_cf: dict, errcheck=False) -> "CRS": """ .. versionadded:: 2.2.0 @@ -703,7 +715,7 @@ def from_cf(in_cf, errcheck=False): try: geographic_conversion_method = _GEOGRAPHIC_GRID_MAPPING_NAME_MAP[ grid_mapping_name - ] + ] # type: Optional[Callable] except KeyError: geographic_conversion_method = None @@ -711,7 +723,7 @@ def from_cf(in_cf, errcheck=False): if datum: geographic_crs = GeographicCRS( name=geographic_crs_name or "undefined", datum=datum, - ) + ) # type: CRS elif geographic_crs_name: geographic_crs = CRS(geographic_crs_name) else: @@ -761,7 +773,7 @@ def from_cf(in_cf, errcheck=False): name="undefined", components=[bound_crs or projected_crs, vertical_crs] ) - def is_exact_same(self, other, ignore_axis_order=False): + def is_exact_same(self, other: Any, ignore_axis_order: bool = False) -> bool: """ Check if the CRS objects are the exact same. @@ -782,7 +794,7 @@ def is_exact_same(self, other, ignore_axis_order=False): return False return super().is_exact_same(other) - def equals(self, other, ignore_axis_order=False): + def equals(self, other: Any, ignore_axis_order: bool = False) -> bool: """ .. versionadded:: 2.5.0 @@ -810,70 +822,79 @@ def equals(self, other, ignore_axis_order=False): return super().equals(other, ignore_axis_order=ignore_axis_order) @property - def geodetic_crs(self): + def geodetic_crs(self) -> Optional["CRS"]: """ .. versionadded:: 2.2.0 Returns ------- - CRS: The the geodeticCRS / geographicCRS from the CRS. + CRS: + The the geodeticCRS / geographicCRS from the CRS. + """ - if super().geodetic_crs is None: + geodetic_crs = super().geodetic_crs + if geodetic_crs is None: return None - return CRS(super().geodetic_crs.srs) + return CRS(geodetic_crs.srs) @property - def source_crs(self): + def source_crs(self) -> Optional["CRS"]: """ + The the base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS, + or the source CRS of a CoordinateOperation. + Returns ------- - CRS: The the base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS, - or the source CRS of a CoordinateOperation. + CRS """ - if super().source_crs is None: + source_crs = super().source_crs + if source_crs is None: return None - return CRS(super().source_crs.srs) + return CRS(source_crs.srs) @property - def target_crs(self): + def target_crs(self) -> Optional["CRS"]: """ .. versionadded:: 2.2.0 Returns ------- - CRS: The hub CRS of a BoundCRS or the target CRS of a CoordinateOperation. + CRS: + The hub CRS of a BoundCRS or the target CRS of a CoordinateOperation. + """ - if super().target_crs is None: + target_crs = super().target_crs + if target_crs is None: return None - return CRS(super().target_crs.srs) + return CRS(target_crs.srs) @property - def sub_crs_list(self): + def sub_crs_list(self) -> List["CRS"]: """ If the CRS is a compound CRS, it will return a list of sub CRS objects. Returns ------- - list[CRS] + List[CRS] """ return [CRS(sub_crs.srs) for sub_crs in super().sub_crs_list] - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return self.equals(other) - def __reduce__(self): + def __reduce__(self) -> Tuple[Type["CRS"], Tuple[str]]: """special method that allows CRS instance to be pickled""" return self.__class__, (self.srs,) - def __hash__(self): + def __hash__(self) -> int: return hash(self.to_wkt()) - def __str__(self): + def __str__(self) -> str: return self.srs - def __repr__(self): + def __repr__(self) -> str: # get axis/coordinate system information - axis_info_list = [] + axis_info_list = [] # type: List[str] def extent_axis(axis_list): for axis_info in axis_list: @@ -884,7 +905,7 @@ def extent_axis(axis_list): if self.axis_info: extent_axis(self.axis_info) coordinate_system_name = str(self.coordinate_system) - elif self.is_bound: + elif self.is_bound and self.source_crs: extent_axis(self.source_crs.axis_info) coordinate_system_name = str(self.source_crs.coordinate_system) source_crs_repr = "Source CRS: {}\n".format(self.source_crs.name) @@ -955,10 +976,10 @@ class GeographicCRS(CRS): def __init__( self, - name="undefined", - datum="urn:ogc:def:datum:EPSG::6326", - ellipsoidal_cs=Ellipsoidal2DCS(), - ): + name: str = "undefined", + datum: Any = "urn:ogc:def:datum:EPSG::6326", + ellipsoidal_cs: Any = Ellipsoidal2DCS(), + ) -> None: """ Parameters ---------- @@ -992,8 +1013,12 @@ class DerivedGeographicCRS(CRS): """ def __init__( - self, base_crs, conversion, ellipsoidal_cs=Ellipsoidal2DCS(), name="undefined", - ): + self, + base_crs: Any, + conversion: Any, + ellipsoidal_cs: Any = Ellipsoidal2DCS(), + name: str = "undefined", + ) -> None: """ Parameters ---------- @@ -1034,11 +1059,11 @@ class ProjectedCRS(CRS): def __init__( self, - conversion, - name="undefined", - cartesian_cs=Cartesian2DCS(), - geodetic_crs=GeographicCRS(), - ): + conversion: Any, + name: str = "undefined", + cartesian_cs: Any = Cartesian2DCS(), + geodetic_crs: Any = GeographicCRS(), + ) -> None: """ Parameters ---------- @@ -1080,7 +1105,13 @@ class VerticalCRS(CRS): """ - def __init__(self, name, datum, vertical_cs=VerticalCS(), geoid_model=None): + def __init__( + self, + name: str, + datum: Any, + vertical_cs: Any = VerticalCS(), + geoid_model: str = None, + ) -> None: """ Parameters ---------- @@ -1117,7 +1148,7 @@ class CompoundCRS(CRS): This class is for building a Compound CRS. """ - def __init__(self, name, components): + def __init__(self, name: str, components: List[Any]) -> None: """ Parameters ---------- @@ -1147,7 +1178,7 @@ class BoundCRS(CRS): This class is for building a Bound CRS. """ - def __init__(self, source_crs, target_crs, transformation): + def __init__(self, source_crs: Any, target_crs: Any, transformation: Any) -> None: """ Parameters ---------- diff --git a/pyproj/crs/datum.py b/pyproj/crs/datum.py index 7d81982df..26ec2b2cd 100644 --- a/pyproj/crs/datum.py +++ b/pyproj/crs/datum.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional, Union + from pyproj._crs import Datum, Ellipsoid, PrimeMeridian @@ -8,7 +10,12 @@ class CustomDatum(Datum): Class to build a datum based on an ellipsoid and prime meridian. """ - def __new__(cls, name="undefined", ellipsoid="WGS 84", prime_meridian="Greenwich"): + def __new__( + cls, + name: str = "undefined", + ellipsoid: Any = "WGS 84", + prime_meridian: Any = "Greenwich", + ): """ Parameters ---------- @@ -41,11 +48,11 @@ class CustomEllipsoid(Ellipsoid): def __new__( cls, - name="undefined", - semi_major_axis=None, - inverse_flattening=None, - semi_minor_axis=None, - radius=None, + name: str = "undefined", + semi_major_axis: Optional[float] = None, + inverse_flattening: Optional[float] = None, + semi_minor_axis: Optional[float] = None, + radius: Optional[float] = None, ): """ Parameters @@ -68,7 +75,7 @@ def __new__( "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", "type": "Ellipsoid", "name": name, - } + } # type: Dict[str, Union[float, str]] if semi_major_axis is not None: ellipsoid_json["semi_major_axis"] = semi_major_axis if inverse_flattening is not None: @@ -87,7 +94,7 @@ class CustomPrimeMeridian(PrimeMeridian): Class to build a prime meridian based on a longitude. """ - def __new__(cls, longitude, name="undefined"): + def __new__(cls, longitude: float, name: str = "undefined"): """ Parameters ---------- diff --git a/pyproj/datadir.py b/pyproj/datadir.py index fecf68710..d239c2587 100644 --- a/pyproj/datadir.py +++ b/pyproj/datadir.py @@ -11,7 +11,7 @@ _VALIDATED_PROJ_DATA = None -def set_data_dir(proj_data_dir): +def set_data_dir(proj_data_dir: str) -> None: """ Set the data directory for PROJ to use. @@ -33,7 +33,7 @@ def set_data_dir(proj_data_dir): pyproj_global_context_initialize() -def append_data_dir(proj_data_dir): +def append_data_dir(proj_data_dir: str) -> None: """ Add an additional data directory for PROJ to use. @@ -45,7 +45,7 @@ def append_data_dir(proj_data_dir): set_data_dir(os.pathsep.join([get_data_dir(), proj_data_dir])) -def get_data_dir(): +def get_data_dir() -> str: """ The order of preference for the data directory is: @@ -57,7 +57,8 @@ def get_data_dir(): Returns ------- - str: The valid data directory. + str: + The valid data directory. """ # to avoid re-validating diff --git a/pyproj/exceptions.py b/pyproj/exceptions.py index 89711ba9c..f946b7f8a 100644 --- a/pyproj/exceptions.py +++ b/pyproj/exceptions.py @@ -9,7 +9,7 @@ class ProjError(RuntimeError): internal_proj_error = None - def __init__(self, error_message): + def __init__(self, error_message: str) -> None: if self.internal_proj_error is not None: error_message = ( "{error_message}: (Internal Proj Error: {internal_proj_error})" @@ -21,7 +21,7 @@ def __init__(self, error_message): super().__init__(error_message) @staticmethod - def clear(): + def clear() -> None: """ This will clear the internal PROJ erro message. """ diff --git a/pyproj/geod.py b/pyproj/geod.py index 54407eb40..7792c6ba8 100644 --- a/pyproj/geod.py +++ b/pyproj/geod.py @@ -32,6 +32,7 @@ __all__ = ["Geod", "pj_ellps", "geodesic_version_str"] import math +from typing import Any, Dict, List, Optional, Tuple, Union from pyproj._geod import Geod as _Geod from pyproj._geod import geodesic_version_str @@ -70,7 +71,7 @@ class Geod(_Geod): """ - def __init__(self, initstring=None, **kwargs): + def __init__(self, initstring: Optional[str] = None, **kwargs) -> None: """ initialize a Geod class instance. @@ -127,7 +128,7 @@ def __init__(self, initstring=None, **kwargs): """ # if initparams is a proj-type init string, # convert to dict. - ellpsd = {} + ellpsd = {} # type: Dict[str, Union[str, float]] if initstring is not None: for kvpair in initstring.split(): # Actually only +a and +b are needed @@ -137,21 +138,22 @@ def __init__(self, initstring=None, **kwargs): k, v = kvpair.split("=") k = k.lstrip("+") if k in ["a", "b", "rf", "f", "es", "e"]: - v = float(v) - ellpsd[k] = v + ellpsd[k] = float(v) + else: + ellpsd[k] = v # merge this dict with kwargs dict. kwargs = dict(list(kwargs.items()) + list(ellpsd.items())) sphere = False if "ellps" in kwargs: # ellipse name given, look up in pj_ellps dict ellps_dict = pj_ellps[kwargs["ellps"]] - a = ellps_dict["a"] + a = ellps_dict["a"] # type: float if ellps_dict["description"] == "Normal Sphere": sphere = True if "b" in ellps_dict: - b = ellps_dict["b"] - es = 1.0 - (b * b) / (a * a) - f = (a - b) / a + b = ellps_dict["b"] # type: float + es = 1.0 - (b * b) / (a * a) # type: float + f = (a - b) / a # type: float elif "rf" in ellps_dict: f = 1.0 / ellps_dict["rf"] b = a * (1.0 - f) @@ -195,7 +197,9 @@ def __init__(self, initstring=None, **kwargs): super().__init__(a, f, sphere, b, es) - def fwd(self, lons, lats, az, dist, radians=False): + def fwd( + self, lons: Any, lats: Any, az: Any, dist: Any, radians=False + ) -> Tuple[Any, Any, Any]: """ Forward transformation @@ -238,7 +242,9 @@ def fwd(self, lons, lats, az, dist, radians=False): outz = _convertback(zisfloat, zislist, zistuple, inz) return outx, outy, outz - def inv(self, lons1, lats1, lons2, lats2, radians=False): + def inv( + self, lons1: Any, lats1: Any, lons2: Any, lats2: Any, radians=False + ) -> Tuple[Any, Any, Any]: """ Inverse transformation @@ -280,7 +286,15 @@ def inv(self, lons1, lats1, lons2, lats2, radians=False): outz = _convertback(zisfloat, zislist, zistuple, inz) return outx, outy, outz - def npts(self, lon1, lat1, lon2, lat2, npts, radians=False): + def npts( + self, + lon1: float, + lat1: float, + lon2: float, + lat2: float, + npts: int, + radians: bool = False, + ) -> List: """ Given a single initial point and terminus point, returns a list of longitude/latitude pairs describing npts equally @@ -355,7 +369,7 @@ def npts(self, lon1, lat1, lon2, lat2, npts, radians=False): lons, lats = super()._npts(lon1, lat1, lon2, lat2, npts, radians=radians) return list(zip(lons, lats)) - def line_length(self, lons, lats, radians=False): + def line_length(self, lons: Any, lats: Any, radians: bool = False) -> float: """ .. versionadded:: 2.3.0 @@ -383,14 +397,15 @@ def line_length(self, lons, lats, radians=False): Returns ------- - float: The total length of the line. + float: + The total length of the line. """ # process inputs, making copies that support buffer API. inx, xisfloat, xislist, xistuple = _copytobuffer(lons) iny, yisfloat, yislist, yistuple = _copytobuffer(lats) return self._line_length(inx, iny, radians=radians) - def line_lengths(self, lons, lats, radians=False): + def line_lengths(self, lons: Any, lats: Any, radians: bool = False) -> Any: """ .. versionadded:: 2.3.0 @@ -426,7 +441,9 @@ def line_lengths(self, lons, lats, radians=False): line_lengths = _convertback(xisfloat, xislist, xistuple, inx) return line_lengths if xisfloat else line_lengths[:-1] - def polygon_area_perimeter(self, lons, lats, radians=False): + def polygon_area_perimeter( + self, lons: Any, lats: Any, radians: bool = False + ) -> Tuple[float, float]: """ .. versionadded:: 2.3.0 @@ -472,7 +489,7 @@ def polygon_area_perimeter(self, lons, lats, radians=False): _copytobuffer(lons)[0], _copytobuffer(lats)[0], radians=radians ) - def geometry_length(self, geometry, radians=False): + def geometry_length(self, geometry, radians: bool = False) -> float: """ .. versionadded:: 2.3.0 @@ -501,11 +518,11 @@ def geometry_length(self, geometry, radians=False): Returns ------- - float: The total geodesic length of the geometry (meters). - + float: + The total geodesic length of the geometry (meters). """ try: - return self.line_length(*geometry.xy, radians=radians) + return self.line_length(*geometry.xy, radians=radians) # type: ignore except (AttributeError, NotImplementedError): pass if hasattr(geometry, "exterior"): @@ -517,7 +534,9 @@ def geometry_length(self, geometry, radians=False): return total_length raise GeodError("Invalid geometry provided.") - def geometry_area_perimeter(self, geometry, radians=False): + def geometry_area_perimeter( + self, geometry, radians: bool = False + ) -> Tuple[float, float]: """ .. versionadded:: 2.3.0 @@ -568,7 +587,9 @@ def geometry_area_perimeter(self, geometry, radians=False): The geodesic area (meters^2) and permimeter (meters) of the polygon. """ try: - return self.polygon_area_perimeter(*geometry.xy, radians=radians) + return self.polygon_area_perimeter( # type: ignore + *geometry.xy, radians=radians, + ) except (AttributeError, NotImplementedError): pass # polygon @@ -592,7 +613,7 @@ def geometry_area_perimeter(self, geometry, radians=False): return total_area, total_perimeter raise GeodError("Invalid geometry provided.") - def __repr__(self): + def __repr__(self) -> str: # search for ellipse name for (ellps, vals) in pj_ellps.items(): if self.a == vals["a"]: @@ -608,7 +629,7 @@ def __repr__(self): # no ellipse name found, call super class return super().__repr__() - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: """ equality operator == for Geod objects diff --git a/pyproj/proj.py b/pyproj/proj.py index 3cebf27ba..3d818c0b3 100644 --- a/pyproj/proj.py +++ b/pyproj/proj.py @@ -36,6 +36,7 @@ CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """ import re import warnings +from typing import Any, Optional, Tuple, Type from pyproj._list import get_proj_operations_map from pyproj._proj import Factors, _Proj, proj_version_str # noqa: F401 @@ -82,7 +83,9 @@ class Proj(_Proj): """ - def __init__(self, projparams=None, preserve_units=True, **kwargs): + def __init__( + self, projparams: Any = None, preserve_units: bool = True, **kwargs + ) -> None: """ initialize a Proj class instance. @@ -167,7 +170,7 @@ def __init__(self, projparams=None, preserve_units=True, **kwargs): projstring = re.sub(r"\s\+?type=crs", "", projstring) super().__init__(cstrencode(projstring.strip())) - def __call__(self, *args, **kw): + def __call__(self, *args, **kw) -> Tuple[Any, Any]: # ,lon,lat,inverse=False,errcheck=False): """ Calling a Proj class instance with the arguments lon, lat will @@ -202,8 +205,12 @@ def __call__(self, *args, **kw): return outx, outy def get_factors( - self, longitude, latitude, radians=False, errcheck=False, - ): + self, + longitude: Any, + latitude: Any, + radians: bool = False, + errcheck: bool = False, + ) -> Factors: """ .. versionadded:: 2.6.0 @@ -268,33 +275,32 @@ def get_factors( dy_dphi=_convertback(xisfloat, xislist, xistuple, factors.dy_dphi), ) - def definition_string(self): + def definition_string(self) -> str: """Returns formal definition string for projection >>> Proj("epsg:4326").definition_string() 'proj=longlat datum=WGS84 no_defs ellps=WGS84 towgs84=0,0,0' - >>> """ return pystrdecode(self.definition) - def to_latlong_def(self): + def to_latlong_def(self) -> Optional[str]: """return the definition string of the geographic (lat/lon) coordinate version of the current projection""" - return self.crs.geodetic_crs.to_proj4(4) + return self.crs.geodetic_crs.to_proj4(4) if self.crs.geodetic_crs else None - def to_latlong(self): + def to_latlong(self) -> "Proj": """return a new Proj instance which is the geographic (lat/lon) coordinate version of the current projection""" return Proj(self.crs.geodetic_crs) - def __reduce__(self): + def __reduce__(self) -> Tuple[Type["Proj"], Tuple[str]]: """special method that allows pyproj.Proj instance to be pickled""" return self.__class__, (self.crs.srs,) - def __repr__(self): + def __repr__(self) -> str: return "Proj('{srs}', preserve_units=True)".format(srs=self.srs) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: if not isinstance(other, Proj): return False return self._is_equivalent(other) diff --git a/pyproj/transformer.py b/pyproj/transformer.py index 471ac7855..b59d3d6ed 100644 --- a/pyproj/transformer.py +++ b/pyproj/transformer.py @@ -24,11 +24,12 @@ "TransformerGroup", "AreaOfInterest", ] - from array import array from itertools import chain, islice +from typing import Any, Iterable, Iterator, List, Optional, Tuple, Union from pyproj import CRS, Proj +from pyproj._crs import AreaOfUse, CoordinateOperation from pyproj._transformer import AreaOfInterest, _Transformer, _TransformerGroup # noqa from pyproj.compat import cstrencode from pyproj.enums import TransformDirection, WktVersion @@ -54,12 +55,12 @@ class TransformerGroup(_TransformerGroup): def __init__( self, - crs_from, - crs_to, - skip_equivalent=False, - always_xy=False, - area_of_interest=None, - ): + crs_from: Any, + crs_to: Any, + skip_equivalent: bool = False, + always_xy: bool = False, + area_of_interest: Optional[AreaOfInterest] = None, + ) -> None: """Get all possible transformations from a :obj:`pyproj.crs.CRS` or input used to create one. @@ -104,7 +105,7 @@ def __init__( self._transformers[iii] = Transformer(transformer) @property - def transformers(self): + def transformers(self) -> List["Transformer"]: """ list[:obj:`Transformer`]: List of available :obj:`Transformer` @@ -113,7 +114,7 @@ def transformers(self): return self._transformers @property - def unavailable_operations(self): + def unavailable_operations(self) -> List[CoordinateOperation]: """ list[:obj:`pyproj.crs.CoordinateOperation`]: List of :obj:`pyproj.crs.CoordinateOperation` that are not @@ -122,13 +123,13 @@ def unavailable_operations(self): return self._unavailable_operations @property - def best_available(self): + def best_available(self) -> bool: """ bool: If True, the best possible transformer is available. """ return self._best_available - def __repr__(self): + def __repr__(self) -> str: return ( "\n" "- transformers: {transformers}\n" @@ -152,7 +153,7 @@ class Transformer: """ - def __init__(self, base_transformer=None): + def __init__(self, base_transformer: Optional[_Transformer] = None) -> None: if not isinstance(base_transformer, _Transformer): ProjError.clear() raise ProjError( @@ -162,86 +163,96 @@ def __init__(self, base_transformer=None): self._transformer = base_transformer @property - def name(self): + def name(self) -> str: """ str: Name of the projection. """ return self._transformer.id @property - def description(self): + def description(self) -> str: """ str: Description of the projection. """ return self._transformer.description @property - def definition(self): + def definition(self) -> str: """ str: Definition of the projection. """ return self._transformer.definition @property - def has_inverse(self): + def has_inverse(self) -> bool: """ bool: True if an inverse mapping exists. """ return self._transformer.has_inverse @property - def accuracy(self): + def accuracy(self) -> float: """ float: Expected accuracy of the transformation. -1 if unknown. """ return self._transformer.accuracy @property - def area_of_use(self): + def area_of_use(self) -> AreaOfUse: """ .. versionadded:: 2.3.0 Returns ------- - AreaOfUse: The area of use object with associated attributes. + AreaOfUse: + The area of use object with associated attributes. """ return self._transformer.area_of_use @property - def remarks(self): + def remarks(self) -> str: """ .. versionadded:: 2.4.0 - str: Remarks about object. + Returns + ------- + str: + Remarks about object. """ return self._transformer.remarks @property - def scope(self): + def scope(self) -> str: """ .. versionadded:: 2.4.0 - str: Scope of object. + Returns + ------- + str: + Scope of object. """ return self._transformer.scope @property - def operations(self): + def operations(self) -> Optional[Tuple[CoordinateOperation]]: """ .. versionadded:: 2.4.0 - tuple[CoordinateOperation]: The operations in a concatenated operation. + Returns + ------- + Tuple[CoordinateOperation]: + The operations in a concatenated operation. """ return self._transformer.operations @staticmethod def from_proj( - proj_from, - proj_to, - skip_equivalent=False, - always_xy=False, - area_of_interest=None, - ): + proj_from: Any, + proj_to: Any, + skip_equivalent: bool = False, + always_xy: bool = False, + area_of_interest: Optional[AreaOfInterest] = None, + ) -> "Transformer": """Make a Transformer from a :obj:`pyproj.proj.Proj` or input used to create one. .. versionadded:: 2.1.2 skip_equivalent @@ -267,7 +278,7 @@ def from_proj( Returns ------- - :obj:`Transformer` + Transformer """ if not isinstance(proj_from, Proj): @@ -285,8 +296,12 @@ def from_proj( @staticmethod def from_crs( - crs_from, crs_to, skip_equivalent=False, always_xy=False, area_of_interest=None - ): + crs_from: Any, + crs_to: Any, + skip_equivalent: bool = False, + always_xy: bool = False, + area_of_interest: Optional[AreaOfInterest] = None, + ) -> "Transformer": """Make a Transformer from a :obj:`pyproj.crs.CRS` or input used to create one. .. versionadded:: 2.1.2 skip_equivalent @@ -312,7 +327,7 @@ def from_crs( Returns ------- - :obj:`Transformer` + Transformer """ return Transformer( @@ -326,7 +341,7 @@ def from_crs( ) @staticmethod - def from_pipeline(proj_pipeline): + def from_pipeline(proj_pipeline: str) -> "Transformer": """Make a Transformer from a PROJ pipeline string. https://proj.org/operations/pipeline.html @@ -345,14 +360,14 @@ def from_pipeline(proj_pipeline): def transform( self, - xx, - yy, - zz=None, - tt=None, - radians=False, - errcheck=False, - direction=TransformDirection.FORWARD, - ): + xx: Any, + yy: Any, + zz: Any = None, + tt: Any = None, + radians: bool = False, + errcheck: bool = False, + direction: Union[TransformDirection, str] = TransformDirection.FORWARD, + ) -> Any: """ Transform points between two coordinate systems. @@ -450,20 +465,24 @@ def transform( outy = _convertback(yisfloat, yislist, xistuple, iny) return_data = (outx, outy) if inz is not None: - return_data += (_convertback(zisfloat, zislist, zistuple, inz),) + return_data += ( # type: ignore + _convertback(zisfloat, zislist, zistuple, inz), + ) if intime is not None: - return_data += (_convertback(tisfloat, tislist, tistuple, intime),) + return_data += ( # type: ignore + _convertback(tisfloat, tislist, tistuple, intime), + ) return return_data def itransform( self, - points, - switch=False, - time_3rd=False, - radians=False, - errcheck=False, - direction=TransformDirection.FORWARD, - ): + points: Any, + switch: bool = False, + time_3rd: bool = False, + radians: bool = False, + errcheck: bool = False, + direction: Union[TransformDirection, str] = TransformDirection.FORWARD, + ) -> Iterator[Iterable]: """ Iterator/generator version of the function pyproj.Transformer.transform. @@ -580,7 +599,11 @@ def itransform( for pt in zip(*([iter(buff)] * stride)): yield pt - def to_wkt(self, version=WktVersion.WKT2_2019, pretty=False): + def to_wkt( + self, + version: Union[WktVersion, str] = WktVersion.WKT2_2019, + pretty: bool = False, + ): """ Convert the projection to a WKT string. @@ -603,11 +626,12 @@ def to_wkt(self, version=WktVersion.WKT2_2019, pretty=False): Returns ------- - str: The WKT string. + str: + The WKT string. """ return self._transformer.to_wkt(version=version, pretty=pretty) - def to_json(self, pretty=False, indentation=2): + def to_json(self, pretty: bool = False, indentation: int = 2) -> str: """ Convert the projection to a JSON string. @@ -622,11 +646,12 @@ def to_json(self, pretty=False, indentation=2): Returns ------- - str: The JSON string. + str: + The JSON string. """ return self._transformer.to_json(pretty=pretty, indentation=indentation) - def to_json_dict(self): + def to_json_dict(self) -> dict: """ Convert the projection to a JSON dictionary. @@ -634,14 +659,15 @@ def to_json_dict(self): Returns ------- - dict: The JSON dictionary. + dict: + The JSON dictionary. """ return self._transformer.to_json_dict() - def __str__(self): + def __str__(self) -> str: return self.definition - def __repr__(self): + def __repr__(self) -> str: return ( "<{type_name}: {name}>\nDescription: {description}\n" "Area of Use:\n{area_of_use}" @@ -654,16 +680,16 @@ def __repr__(self): def transform( - p1, - p2, - x, - y, - z=None, - tt=None, - radians=False, - errcheck=False, - skip_equivalent=False, - always_xy=False, + p1: Any, + p2: Any, + x: Any, + y: Any, + z: Any = None, + tt: Any = None, + radians: bool = False, + errcheck: bool = False, + skip_equivalent: bool = False, + always_xy: bool = False, ): """ .. versionadded:: 2.1.2 skip_equivalent @@ -744,15 +770,15 @@ def transform( def itransform( - p1, - p2, - points, - switch=False, - time_3rd=False, - radians=False, - errcheck=False, - skip_equivalent=False, - always_xy=False, + p1: Any, + p2: Any, + points: Iterable[Iterable], + switch: bool = False, + time_3rd: bool = False, + radians: bool = False, + errcheck: bool = False, + skip_equivalent: bool = False, + always_xy: bool = False, ): """ .. versionadded:: 2.1.2 skip_equivalent diff --git a/pyproj/utils.py b/pyproj/utils.py index 15777f3c1..026bfa75b 100644 --- a/pyproj/utils.py +++ b/pyproj/utils.py @@ -1,17 +1,23 @@ from array import array +from typing import Any, Tuple -def _copytobuffer_return_scalar(x): +def _copytobuffer_return_scalar(xx: Any) -> Tuple[array, bool, bool, bool]: + """ + Parameters + ----------- + xx: float or 0-d numpy array + """ try: # inx,isfloat,islist,istuple - return array("d", (float(x),)), True, False, False + return array("d", (float(xx),)), True, False, False except Exception: raise TypeError("input must be an array, list, tuple or scalar") -def _copytobuffer(x): +def _copytobuffer(xx: Any) -> Tuple[Any, bool, bool, bool]: """ - return a copy of x as an object that supports the python Buffer + return a copy of xx as an object that supports the python Buffer API (python array if input is float, list or tuple, numpy array if input is a numpy array). returns copyofx, isfloat, islist, istuple (islist is True if input is a list, istuple is true if @@ -23,52 +29,52 @@ def _copytobuffer(x): istuple = False # first, if it's a numpy array scalar convert to float # (array scalars don't support buffer API) - if hasattr(x, "shape"): - if x.shape == (): - return _copytobuffer_return_scalar(x) + if hasattr(xx, "shape"): + if xx.shape == (): + return _copytobuffer_return_scalar(xx) else: try: # typecast numpy arrays to double. # (this makes a copy - which is crucial # since buffer is modified in place) - x.dtype.char + xx.dtype.char # Basemap issue # https://github.com/matplotlib/basemap/pull/223/files # (deal with input array in fortran order) - inx = x.copy(order="C").astype("d") + inx = xx.copy(order="C").astype("d") # inx,isfloat,islist,istuple return inx, False, False, False except Exception: try: # perhaps they are Numeric/numarrays? # sorry, not tested yet. # i don't know Numeric/numarrays has `shape'. - x.typecode() - inx = x.astype("d") + xx.typecode() + inx = xx.astype("d") # inx,isfloat,islist,istuple return inx, False, False, False except Exception: raise TypeError("input must be an array, list, tuple or scalar") else: # perhaps they are regular python arrays? - if hasattr(x, "typecode"): - # x.typecode - inx = array("d", x) + if hasattr(xx, "typecode"): + # xx.typecode + inx = array("d", xx) # try to convert to python array # a list. - elif type(x) == list: - inx = array("d", x) + elif type(xx) == list: + inx = array("d", xx) islist = True # a tuple. - elif type(x) == tuple: - inx = array("d", x) + elif type(xx) == tuple: + inx = array("d", xx) istuple = True # a scalar? else: - return _copytobuffer_return_scalar(x) + return _copytobuffer_return_scalar(xx) return inx, isfloat, islist, istuple -def _convertback(isfloat, islist, istuple, inx): +def _convertback(isfloat: bool, islist: bool, istuple: bool, inx: Any) -> Any: # if inputs were lists, tuples or floats, convert back to original type. if isfloat: return inx[0] diff --git a/requirements-dev.txt b/requirements-dev.txt index 7bc6e2364..f8b4ca19b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,6 +2,7 @@ cython>=0.28.4 black; python_version >= '3.6' flake8 mock +mypy numpy pylint pytest>3.6