microproduct-l-sar/dem-L-SAR/ISCEApp/_internal/pyproj/_crs.pyx

3206 lines
91 KiB
Cython
Raw Permalink Normal View History

2024-01-03 01:42:21 +00:00
import json
import re
import warnings
from collections import OrderedDict, namedtuple
from pyproj._compat cimport cstrdecode, cstrencode
from pyproj._datadir cimport (
_clear_proj_error,
pyproj_context_create,
pyproj_context_destroy,
)
from pyproj.aoi import AreaOfUse
from pyproj.crs.datum import CustomEllipsoid
from pyproj.crs.enums import CoordinateOperationType, DatumType
from pyproj.enums import ProjVersion, WktVersion
from pyproj.exceptions import CRSError
from pyproj.geod import pj_ellps
from pyproj.utils import NumpyEncoder
# This is for looking up the ellipsoid parameters
# based on the long name
cdef dict _PJ_ELLPS_NAME_MAP = {
ellps["description"]: ellps_id for ellps_id, ellps in pj_ellps.items()
}
cdef str decode_or_undefined(const char* instring):
pystr = cstrdecode(instring)
if pystr is None:
return "undefined"
return pystr
def is_wkt(str proj_string not None):
"""
.. versionadded:: 2.0.0
Check if the input projection string is in the Well-Known Text format.
Parameters
----------
proj_string: str
The projection string.
Returns
-------
bool: True if the string is in the Well-Known Text format
"""
cdef bytes b_proj_string = cstrencode(proj_string)
return proj_context_guess_wkt_dialect(NULL, b_proj_string) != PJ_GUESSED_NOT_WKT
def is_proj(str proj_string not None):
"""
.. versionadded:: 2.2.2
Check if the input projection string is in the PROJ format.
Parameters
----------
proj_string: str
The projection string.
Returns
-------
bool: True if the string is in the PROJ format
"""
return not is_wkt(proj_string) and "=" in proj_string
cdef _to_wkt(
PJ_CONTEXT* context,
PJ* projobj,
object version,
bint pretty,
bool output_axis_rule=None,
):
"""
Convert a PJ object to a wkt string.
Parameters
----------
context: PJ_CONTEXT*
projobj: PJ*
wkt_out_type: PJ_WKT_TYPE
pretty: bool
output_axis_rule: bool or None
Return
------
str or None
"""
# get the output WKT format
supported_wkt_types = {
WktVersion.WKT2_2015: PJ_WKT2_2015,
WktVersion.WKT2_2015_SIMPLIFIED: PJ_WKT2_2015_SIMPLIFIED,
WktVersion.WKT2_2018: PJ_WKT2_2019,
WktVersion.WKT2_2018_SIMPLIFIED: PJ_WKT2_2019_SIMPLIFIED,
WktVersion.WKT2_2019: PJ_WKT2_2019,
WktVersion.WKT2_2019_SIMPLIFIED: PJ_WKT2_2019_SIMPLIFIED,
WktVersion.WKT1_GDAL: PJ_WKT1_GDAL,
WktVersion.WKT1_ESRI: PJ_WKT1_ESRI
}
cdef PJ_WKT_TYPE wkt_out_type
wkt_out_type = supported_wkt_types[WktVersion.create(version)]
cdef const char* options_wkt[3]
cdef bytes multiline = b"MULTILINE=NO"
if pretty:
multiline = b"MULTILINE=YES"
cdef bytes output_axis = b"OUTPUT_AXIS=AUTO"
if output_axis_rule is False:
output_axis = b"OUTPUT_AXIS=NO"
elif output_axis_rule is True:
output_axis = b"OUTPUT_AXIS=YES"
options_wkt[0] = multiline
options_wkt[1] = output_axis
options_wkt[2] = NULL
cdef const char* proj_string
proj_string = proj_as_wkt(
context,
projobj,
wkt_out_type,
options_wkt,
)
_clear_proj_error()
return cstrdecode(proj_string)
cdef _to_proj4(
PJ_CONTEXT* context,
PJ* projobj,
object version,
bint pretty,
):
"""
Convert the projection to a PROJ string.
Parameters
----------
context: PJ_CONTEXT*
projobj: PJ*
version: pyproj.enums.ProjVersion
The version of the PROJ string output.
pretty: bool
Returns
-------
str: The PROJ string.
"""
# get the output PROJ string format
supported_prj_types = {
ProjVersion.PROJ_4: PJ_PROJ_4,
ProjVersion.PROJ_5: PJ_PROJ_5,
}
cdef PJ_PROJ_STRING_TYPE proj_out_type
proj_out_type = supported_prj_types[ProjVersion.create(version)]
cdef const char* options[2]
cdef bytes multiline = b"MULTILINE=NO"
if pretty:
multiline = b"MULTILINE=YES"
options[0] = multiline
options[1] = NULL
# convert projection to string
cdef const char* proj_string
proj_string = proj_as_proj_string(
context,
projobj,
proj_out_type,
options,
)
_clear_proj_error()
return cstrdecode(proj_string)
cdef tuple _get_concatenated_operations(
PJ_CONTEXT* context, PJ* concatenated_operation
):
"""
For a PJ* of type concatenated operation, get the operations
"""
cdef int step_count = proj_concatoperation_get_step_count(
context,
concatenated_operation,
)
cdef PJ* operation = NULL
cdef PJ_CONTEXT* sub_context = NULL
cdef int iii = 0
operations = []
for iii in range(step_count):
sub_context = pyproj_context_create()
operation = proj_concatoperation_get_step(
sub_context,
concatenated_operation,
iii,
)
operations.append(CoordinateOperation.create(sub_context, operation))
_clear_proj_error()
return tuple(operations)
cdef PJ * _from_name(
PJ_CONTEXT* context,
str name_string,
str auth_name,
PJ_TYPE pj_type,
):
"""
Create an object from a name.
Parameters
----------
context: PJ_CONTEXT*
The context to use to create the object.
name_string: str
Name of object to create.
auth_name: str
The authority name to refine search.
If None, will search all authorities.
pj_type: PJ_TYPE
The type of PJ * to create.
Returns
-------
PJ *
"""
cdef PJ_TYPE[1] pj_types = [pj_type]
cdef char* c_auth_name = NULL
cdef bytes b_auth_name
if auth_name is not None:
b_auth_name = cstrencode(auth_name)
c_auth_name = b_auth_name
cdef PJ_OBJ_LIST *pj_list = proj_create_from_name(
context,
c_auth_name,
cstrencode(name_string),
<PJ_TYPE*>&pj_types,
1,
False,
1,
NULL,
)
if pj_list == NULL or proj_list_get_count(pj_list) <= 0:
proj_list_destroy(pj_list)
return NULL
cdef PJ* datum_pj = proj_list_get(context, pj_list, 0)
proj_list_destroy(pj_list)
return datum_pj
def _load_proj_json(str in_proj_json):
try:
return json.loads(in_proj_json)
except ValueError:
raise CRSError("Invalid JSON")
cdef class Axis:
"""
.. versionadded:: 2.0.0
Coordinate System Axis
Attributes
----------
name: str
abbrev: str
direction: str
unit_conversion_factor: float
unit_name: str
unit_auth_code: str
unit_code: str
"""
def __cinit__(self):
self.name = "undefined"
self.abbrev = "undefined"
self.direction = "undefined"
self.unit_conversion_factor = float("NaN")
self.unit_name = "undefined"
self.unit_auth_code = "undefined"
self.unit_code = "undefined"
def __str__(self):
return f"{self.abbrev}[{self.direction}]: {self.name} ({self.unit_name})"
def __repr__(self):
return (
f"Axis(name={self.name}, abbrev={self.abbrev}, "
f"direction={self.direction}, unit_auth_code={self.unit_auth_code}, "
f"unit_code={self.unit_code}, unit_name={self.unit_name})"
)
@staticmethod
cdef Axis create(PJ_CONTEXT* context, PJ* projobj, int index):
cdef:
Axis axis_info = Axis()
const char * name = NULL
const char * abbrev = NULL
const char * direction = NULL
const char * unit_name = NULL
const char * unit_auth_code = NULL
const char * unit_code = NULL
if not proj_cs_get_axis_info(
context,
projobj,
index,
&name,
&abbrev,
&direction,
&axis_info.unit_conversion_factor,
&unit_name,
&unit_auth_code,
&unit_code):
return None
axis_info.name = decode_or_undefined(name)
axis_info.abbrev = decode_or_undefined(abbrev)
axis_info.direction = decode_or_undefined(direction)
axis_info.unit_name = decode_or_undefined(unit_name)
axis_info.unit_auth_code = decode_or_undefined(unit_auth_code)
axis_info.unit_code = decode_or_undefined(unit_code)
return axis_info
cdef create_area_of_use(PJ_CONTEXT* context, PJ* projobj):
cdef:
double west = float("nan")
double south = float("nan")
double east = float("nan")
double north = float("nan")
const char * area_name = NULL
if not proj_get_area_of_use(
context,
projobj,
&west,
&south,
&east,
&north,
&area_name):
return None
return AreaOfUse(
west=west,
south=south,
east=east,
north=north,
name=decode_or_undefined(area_name),
)
cdef class Base:
def __cinit__(self):
self.projobj = NULL
self.context = NULL
self.name = "undefined"
self._scope = None
self._remarks = None
def __dealloc__(self):
"""destroy projection definition"""
if self.projobj != NULL:
proj_destroy(self.projobj)
if self.context != NULL:
pyproj_context_destroy(self.context)
cdef _set_base_info(self):
"""
Set the name of the PJ
"""
# get proj information
cdef const char* proj_name = proj_get_name(self.projobj)
self.name = decode_or_undefined(proj_name)
cdef const char* scope = proj_get_scope(self.projobj)
if scope != NULL and scope != "":
self._scope = scope
cdef const char* remarks = proj_get_remarks(self.projobj)
if remarks != NULL and remarks != "":
self._remarks = remarks
@property
def remarks(self):
"""
.. versionadded:: 2.4.0
Returns
-------
str:
Remarks about object.
"""
return self._remarks
@property
def scope(self):
"""
.. versionadded:: 2.4.0
Returns
-------
str:
Scope of object.
"""
return self._scope
def to_wkt(self, version=WktVersion.WKT2_2019, pretty=False, output_axis_rule=None):
"""
Convert the projection to a WKT string.
Version options:
- WKT2_2015
- WKT2_2015_SIMPLIFIED
- WKT2_2019
- WKT2_2019_SIMPLIFIED
- WKT1_GDAL
- WKT1_ESRI
.. versionadded:: 3.6.0 output_axis_rule
Parameters
----------
version: pyproj.enums.WktVersion, default=pyproj.enums.WktVersion.WKT2_2019
The version of the WKT output.
pretty: bool, default=False
If True, it will set the output to be a multiline string.
output_axis_rule: bool, optional, default=None
If True, it will set the axis rule on any case. If false, never.
None for AUTO, that depends on the CRS and version.
Returns
-------
str
"""
return _to_wkt(self.context, self.projobj, version, pretty=pretty, output_axis_rule=output_axis_rule)
def to_json(self, bint pretty=False, int indentation=2):
"""
.. versionadded:: 2.4.0
Convert the object to a JSON string.
Parameters
----------
pretty: bool, default=False
If True, it will set the output to be a multiline string.
indentation: int, default=2
If pretty is True, it will set the width of the indentation.
Returns
-------
str
"""
cdef const char* options[3]
multiline = b"MULTILINE=NO"
if pretty:
multiline = b"MULTILINE=YES"
indentation_width = cstrencode(f"INDENTATION_WIDTH={indentation:.0f}")
options[0] = multiline
options[1] = indentation_width
options[2] = NULL
cdef const char* proj_json_string = proj_as_projjson(
self.context,
self.projobj,
options,
)
return cstrdecode(proj_json_string)
def to_json_dict(self):
"""
.. versionadded:: 2.4.0
Convert the object to a JSON dictionary.
Returns
-------
dict
"""
return json.loads(self.to_json())
def __str__(self):
return self.name
def __repr__(self):
return self.to_wkt(pretty=True)
def _is_exact_same(self, Base other):
return proj_is_equivalent_to_with_ctx(
self.context, self.projobj, other.projobj, PJ_COMP_STRICT) == 1
def _is_equivalent(self, Base other):
return proj_is_equivalent_to_with_ctx(
self.context, self.projobj, other.projobj, PJ_COMP_EQUIVALENT) == 1
def __eq__(self, other):
if not isinstance(other, Base):
return False
return self._is_equivalent(other)
def is_exact_same(self, other):
"""Compares projection objects to see if they are exactly the same."""
if not isinstance(other, Base):
return False
return self._is_exact_same(other)
cdef class _CRSParts(Base):
@classmethod
def from_user_input(cls, user_input):
"""
.. versionadded:: 2.5.0
Create cls from user input:
- PROJ JSON string
- PROJ JSON dict
- WKT string
- An authority string
- An EPSG integer code
- An iterable of ("auth_name", "auth_code")
- An object with a `to_json` method.
Parameters
----------
user_input: str, dict, int, Iterable[str, str]
Input to create cls.
Returns
-------
cls
"""
if isinstance(user_input, str):
prepared = cls.from_string(user_input)
elif isinstance(user_input, dict):
prepared = cls.from_json_dict(user_input)
elif isinstance(user_input, int) and hasattr(cls, "from_epsg"):
prepared = cls.from_epsg(user_input)
elif (
isinstance(user_input, (list, tuple))
and len(user_input) == 2
and hasattr(cls, "from_authority")
):
prepared = cls.from_authority(*user_input)
elif hasattr(user_input, "to_json"):
prepared = cls.from_json(user_input.to_json())
else:
raise CRSError(f"Invalid {cls.__name__} input: {user_input!r}")
return prepared
def __eq__(self, other):
try:
other = self.from_user_input(other)
except CRSError:
return False
return self._is_equivalent(other)
cdef dict _COORD_SYSTEM_TYPE_MAP = {
PJ_CS_TYPE_UNKNOWN: "unknown",
PJ_CS_TYPE_CARTESIAN: "cartesian",
PJ_CS_TYPE_ELLIPSOIDAL: "ellipsoidal",
PJ_CS_TYPE_VERTICAL: "vertical",
PJ_CS_TYPE_SPHERICAL: "spherical",
PJ_CS_TYPE_ORDINAL: "ordinal",
PJ_CS_TYPE_PARAMETRIC: "parametric",
PJ_CS_TYPE_DATETIMETEMPORAL: "datetimetemporal",
PJ_CS_TYPE_TEMPORALCOUNT: "temporalcount",
PJ_CS_TYPE_TEMPORALMEASURE: "temporalmeasure",
}
cdef class CoordinateSystem(_CRSParts):
"""
.. versionadded:: 2.2.0
Coordinate System for CRS
Attributes
----------
name: str
The name of the coordinate system.
"""
def __cinit__(self):
self._axis_list = None
def __init__(self):
raise RuntimeError("CoordinateSystem is not initializable.")
@staticmethod
cdef CoordinateSystem create(PJ_CONTEXT* context, PJ* coord_system_pj):
cdef CoordinateSystem coord_system = CoordinateSystem.__new__(CoordinateSystem)
coord_system.context = context
coord_system.projobj = coord_system_pj
cdef PJ_COORDINATE_SYSTEM_TYPE cs_type = proj_cs_get_type(
coord_system.context,
coord_system.projobj,
)
coord_system.name = _COORD_SYSTEM_TYPE_MAP[cs_type]
return coord_system
@property
def axis_list(self):
"""
Returns
-------
list[Axis]:
The Axis list for the coordinate system.
"""
if self._axis_list is not None:
return self._axis_list
self._axis_list = []
cdef int num_axes = 0
num_axes = proj_cs_get_axis_count(
self.context,
self.projobj
)
for axis_idx from 0 <= axis_idx < num_axes:
self._axis_list.append(
Axis.create(
self.context,
self.projobj,
axis_idx
)
)
return self._axis_list
@staticmethod
def from_string(str coordinate_system_string not None):
"""
.. versionadded:: 2.5.0
.. note:: Only works with PROJ JSON.
Create a Coordinate System from a string.
Parameters
----------
coordinate_system_string: str
Coordinate System string.
Returns
-------
CoordinateSystem
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* coordinate_system_pj = proj_create(
context,
cstrencode(coordinate_system_string)
)
if coordinate_system_pj == NULL or proj_cs_get_type(
context,
coordinate_system_pj,
) == PJ_CS_TYPE_UNKNOWN:
proj_destroy(coordinate_system_pj)
pyproj_context_destroy(context)
raise CRSError(
"Invalid coordinate system string: "
f"{coordinate_system_string}"
)
_clear_proj_error()
return CoordinateSystem.create(context, coordinate_system_pj)
@staticmethod
def from_json_dict(dict coordinate_system_dict not None):
"""
.. versionadded:: 2.5.0
Create Coordinate System from a JSON dictionary.
Parameters
----------
coordinate_system_dict: str
Coordinate System dictionary.
Returns
-------
CoordinateSystem
"""
return CoordinateSystem.from_string(
json.dumps(coordinate_system_dict, cls=NumpyEncoder)
)
@staticmethod
def from_json(str coordinate_system_json_str not None):
"""
.. versionadded:: 2.5.0
Create Coordinate System from a JSON string.
Parameters
----------
coordinate_system_json_str: str
Coordinate System JSON string.
Returns
-------
CoordinateSystem
"""
return CoordinateSystem.from_json_dict(
_load_proj_json(coordinate_system_json_str)
)
def to_cf(self, bint rotated_pole=False):
"""
.. versionadded:: 3.0.0
This converts a :obj:`pyproj.crs.CoordinateSystem` axis
to a list of Climate and Forecast (CF) Version 1.8 dicts.
Parameters
----------
rotated_pole: bool, default=False
If True, the geographic coordinates are on a rotated pole grid.
This corresponds to the rotated_latitude_longitude grid_mapping_name.
Returns
-------
list[dict]:
CF-1.8 version of the CoordinateSystem.
"""
axis_list = self.to_json_dict()["axis"]
cf_params = []
def get_linear_unit(axis):
try:
return f'{axis["unit"]["conversion_factor"]} metre'
except TypeError:
return axis["unit"]
if self.name == "cartesian":
for axis in axis_list:
if axis["name"].lower() == "easting":
cf_axis = "X"
else:
cf_axis = "Y"
cf_params.append(dict(
axis=cf_axis,
long_name=axis["name"],
standard_name=f"projection_{cf_axis.lower()}_coordinate",
units=get_linear_unit(axis),
))
elif self.name == "ellipsoidal":
for axis in axis_list:
if axis["abbreviation"].upper() in ("D", "H"):
cf_params.append(dict(
standard_name="height_above_reference_ellipsoid",
long_name=axis["name"],
units=axis["unit"],
positive=axis["direction"],
axis="Z",
))
else:
if "longitude" in axis["name"].lower():
cf_axis = "X"
name = "longitude"
else:
cf_axis = "Y"
name = "latitude"
if rotated_pole:
cf_params.append(dict(
standard_name=f"grid_{name}",
long_name=f"{name} in rotated pole grid",
units="degrees",
axis=cf_axis,
))
else:
cf_params.append(dict(
standard_name=name,
long_name=f"{name} coordinate",
units=f'degrees_{axis["direction"]}',
axis=cf_axis,
))
elif self.name == "vertical":
for axis in axis_list:
cf_params.append(dict(
standard_name="height_above_reference_ellipsoid",
long_name=axis["name"],
units=get_linear_unit(axis),
positive=axis["direction"],
axis="Z",
))
return cf_params
cdef class Ellipsoid(_CRSParts):
"""
.. versionadded:: 2.0.0
Ellipsoid for CRS
Attributes
----------
name: str
The name of the ellipsoid.
is_semi_minor_computed: int
1 if True, 0 if False
semi_major_metre: float
The semi major axis in meters of the ellipsoid.
semi_minor_metre: float
The semi minor axis in meters of the ellipsoid.
inverse_flattening: float
The inverse flattening of the ellipsoid.
"""
def __cinit__(self):
# load in ellipsoid information if applicable
self.semi_major_metre = float("NaN")
self.semi_minor_metre = float("NaN")
self.is_semi_minor_computed = False
self.inverse_flattening = float("NaN")
def __init__(self):
raise RuntimeError(
"Ellipsoid can only be initialized like 'Ellipsoid.from_*()'."
)
@staticmethod
cdef Ellipsoid create(PJ_CONTEXT* context, PJ* ellipsoid_pj):
cdef Ellipsoid ellips = Ellipsoid.__new__(Ellipsoid)
ellips.context = context
ellips.projobj = ellipsoid_pj
cdef int is_semi_minor_computed = 0
proj_ellipsoid_get_parameters(
context,
ellipsoid_pj,
&ellips.semi_major_metre,
&ellips.semi_minor_metre,
&is_semi_minor_computed,
&ellips.inverse_flattening,
)
ellips.is_semi_minor_computed = is_semi_minor_computed == 1
ellips._set_base_info()
_clear_proj_error()
return ellips
@staticmethod
def from_authority(str auth_name not None, code not None):
"""
.. versionadded:: 2.2.0
Create an Ellipsoid from an authority code.
Parameters
----------
auth_name: str
Name of the authority.
code: str or int
The code used by the authority.
Returns
-------
Ellipsoid
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* ellipsoid_pj = proj_create_from_database(
context,
cstrencode(auth_name),
cstrencode(str(code)),
PJ_CATEGORY_ELLIPSOID,
False,
NULL,
)
if ellipsoid_pj == NULL:
pyproj_context_destroy(context)
raise CRSError(f"Invalid authority or code ({auth_name}, {code})")
_clear_proj_error()
return Ellipsoid.create(context, ellipsoid_pj)
@staticmethod
def from_epsg(code not None):
"""
.. versionadded:: 2.2.0
Create an Ellipsoid from an EPSG code.
Parameters
----------
code: str or int
The code used by the EPSG.
Returns
-------
Ellipsoid
"""
return Ellipsoid.from_authority("EPSG", code)
@staticmethod
def _from_string(str ellipsoid_string not None):
"""
Create an Ellipsoid from a string.
Examples:
- urn:ogc:def:ellipsoid:EPSG::7001
- ELLIPSOID["Airy 1830",6377563.396,299.3249646,
LENGTHUNIT["metre",1],
ID["EPSG",7001]]
Parameters
----------
ellipsoid_string: str
Ellipsoid string.
Returns
-------
Ellipsoid
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* ellipsoid_pj = proj_create(
context,
cstrencode(ellipsoid_string)
)
if ellipsoid_pj == NULL or proj_get_type(ellipsoid_pj) != PJ_TYPE_ELLIPSOID:
proj_destroy(ellipsoid_pj)
pyproj_context_destroy(context)
raise CRSError(
f"Invalid ellipsoid string: {ellipsoid_string}"
)
_clear_proj_error()
return Ellipsoid.create(context, ellipsoid_pj)
@staticmethod
def from_string(str ellipsoid_string not None):
"""
.. versionadded:: 2.2.0
Create an Ellipsoid from a string.
Examples:
- urn:ogc:def:ellipsoid:EPSG::7001
- ELLIPSOID["Airy 1830",6377563.396,299.3249646,
LENGTHUNIT["metre",1],
ID["EPSG",7001]]
- WGS 84
Parameters
----------
ellipsoid_string: str
Ellipsoid string.
Returns
-------
Ellipsoid
"""
try:
return Ellipsoid._from_string(ellipsoid_string)
except CRSError as crs_err:
try:
return Ellipsoid.from_name(ellipsoid_string)
except CRSError:
raise crs_err
@staticmethod
def from_json_dict(dict ellipsoid_dict not None):
"""
.. versionadded:: 2.4.0
Create Ellipsoid from a JSON dictionary.
Parameters
----------
ellipsoid_dict: str
Ellipsoid dictionary.
Returns
-------
Ellipsoid
"""
return Ellipsoid._from_string(json.dumps(ellipsoid_dict, cls=NumpyEncoder))
@staticmethod
def from_json(str ellipsoid_json_str not None):
"""
.. versionadded:: 2.4.0
Create Ellipsoid from a JSON string.
Parameters
----------
ellipsoid_json_str: str
Ellipsoid JSON string.
Returns
-------
Ellipsoid
"""
return Ellipsoid.from_json_dict(_load_proj_json(ellipsoid_json_str))
@staticmethod
def _from_name(
str ellipsoid_name,
str auth_name,
):
"""
.. versionadded:: 2.5.0
Create a Ellipsoid from a name.
Parameters
----------
ellipsoid_name: str
Ellipsoid name.
auth_name: str
The authority name to refine search (e.g. 'EPSG').
If None, will search all authorities.
Returns
-------
Ellipsoid
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* ellipsoid_pj = _from_name(
context,
ellipsoid_name,
auth_name,
PJ_TYPE_ELLIPSOID,
)
if ellipsoid_pj == NULL:
pyproj_context_destroy(context)
raise CRSError(f"Invalid ellipsoid name: {ellipsoid_name}")
_clear_proj_error()
return Ellipsoid.create(context, ellipsoid_pj)
@staticmethod
def from_name(
str ellipsoid_name not None,
str auth_name=None,
):
"""
.. versionadded:: 2.5.0
Create a Ellipsoid from a name.
Examples:
- WGS 84
Parameters
----------
ellipsoid_name: str
Ellipsoid name.
auth_name: str, optional
The authority name to refine search (e.g. 'EPSG').
If None, will search all authorities.
Returns
-------
Ellipsoid
"""
try:
return Ellipsoid._from_name(
ellipsoid_name=ellipsoid_name,
auth_name=auth_name,
)
except CRSError:
if auth_name not in ("PROJ", None):
raise
pass
# add support for past names for PROJ ellipsoids
try:
ellipsoid_params = pj_ellps[
_PJ_ELLPS_NAME_MAP.get(ellipsoid_name, ellipsoid_name)
]
except KeyError:
raise CRSError(f"Invalid ellipsoid name: {ellipsoid_name}")
return CustomEllipsoid(
name=ellipsoid_params["description"],
semi_major_axis=ellipsoid_params["a"],
semi_minor_axis=ellipsoid_params.get("b"),
inverse_flattening=ellipsoid_params.get("rf"),
)
cdef class PrimeMeridian(_CRSParts):
"""
.. versionadded:: 2.0.0
Prime Meridian for CRS
Attributes
----------
name: str
The name of the prime meridian.
unit_name: str
The unit name for the prime meridian.
"""
def __cinit__(self):
self.unit_name = None
def __init__(self):
raise RuntimeError(
"PrimeMeridian can only be initialized like 'PrimeMeridian.from_*()'."
)
@staticmethod
cdef PrimeMeridian create(PJ_CONTEXT* context, PJ* prime_meridian_pj):
cdef PrimeMeridian prime_meridian = PrimeMeridian.__new__(PrimeMeridian)
prime_meridian.context = context
prime_meridian.projobj = prime_meridian_pj
cdef const char * unit_name
proj_prime_meridian_get_parameters(
prime_meridian.context,
prime_meridian.projobj,
&prime_meridian.longitude,
&prime_meridian.unit_conversion_factor,
&unit_name,
)
prime_meridian.unit_name = decode_or_undefined(unit_name)
prime_meridian._set_base_info()
_clear_proj_error()
return prime_meridian
@staticmethod
def from_authority(str auth_name not None, code not None):
"""
.. versionadded:: 2.2.0
Create a PrimeMeridian from an authority code.
Parameters
----------
auth_name: str
Name of the authority.
code: str or int
The code used by the authority.
Returns
-------
PrimeMeridian
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* prime_meridian_pj = proj_create_from_database(
context,
cstrencode(auth_name),
cstrencode(str(code)),
PJ_CATEGORY_PRIME_MERIDIAN,
False,
NULL,
)
if prime_meridian_pj == NULL:
pyproj_context_destroy(context)
raise CRSError(f"Invalid authority or code ({auth_name}, {code})")
_clear_proj_error()
return PrimeMeridian.create(context, prime_meridian_pj)
@staticmethod
def from_epsg(code not None):
"""
.. versionadded:: 2.2.0
Create a PrimeMeridian from an EPSG code.
Parameters
----------
code: str or int
The code used by EPSG.
Returns
-------
PrimeMeridian
"""
return PrimeMeridian.from_authority("EPSG", code)
@staticmethod
def _from_string(str prime_meridian_string not None):
"""
Create an PrimeMeridian from a string.
Examples:
- urn:ogc:def:meridian:EPSG::8901
- PRIMEM["Greenwich",0,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8901]]
Parameters
----------
prime_meridian_string: str
prime meridian string.
Returns
-------
PrimeMeridian
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* prime_meridian_pj = proj_create(
context,
cstrencode(prime_meridian_string)
)
if (
prime_meridian_pj == NULL or
proj_get_type(prime_meridian_pj) != PJ_TYPE_PRIME_MERIDIAN
):
proj_destroy(prime_meridian_pj)
pyproj_context_destroy(context)
raise CRSError(
f"Invalid prime meridian string: {prime_meridian_string}"
)
_clear_proj_error()
return PrimeMeridian.create(context, prime_meridian_pj)
@staticmethod
def from_string(str prime_meridian_string not None):
"""
.. versionadded:: 2.2.0
Create an PrimeMeridian from a string.
Examples:
- urn:ogc:def:meridian:EPSG::8901
- PRIMEM["Greenwich",0,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8901]]
- Greenwich
Parameters
----------
prime_meridian_string: str
prime meridian string.
Returns
-------
PrimeMeridian
"""
try:
return PrimeMeridian._from_string(prime_meridian_string)
except CRSError as crs_err:
try:
return PrimeMeridian.from_name(prime_meridian_string)
except CRSError:
raise crs_err
@staticmethod
def from_json_dict(dict prime_meridian_dict not None):
"""
.. versionadded:: 2.4.0
Create PrimeMeridian from a JSON dictionary.
Parameters
----------
prime_meridian_dict: str
PrimeMeridian dictionary.
Returns
-------
PrimeMeridian
"""
return PrimeMeridian._from_string(
json.dumps(prime_meridian_dict, cls=NumpyEncoder)
)
@staticmethod
def from_json(str prime_meridian_json_str not None):
"""
.. versionadded:: 2.4.0
Create PrimeMeridian from a JSON string.
Parameters
----------
prime_meridian_json_str: str
PrimeMeridian JSON string.
Returns
-------
PrimeMeridian
"""
return PrimeMeridian.from_json_dict(_load_proj_json(prime_meridian_json_str))
@staticmethod
def from_name(
str prime_meridian_name not None,
str auth_name=None,
):
"""
.. versionadded:: 2.5.0
Create a Prime Meridian from a name.
Examples:
- Greenwich
Parameters
----------
prime_meridian_name: str
Prime Meridian name.
auth_name: str, optional
The authority name to refine search (e.g. 'EPSG').
If None, will search all authorities.
Returns
-------
PrimeMeridian
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* prime_meridian_pj = _from_name(
context,
prime_meridian_name,
auth_name,
PJ_TYPE_PRIME_MERIDIAN,
)
if prime_meridian_pj == NULL:
pyproj_context_destroy(context)
raise CRSError(
f"Invalid prime meridian name: {prime_meridian_name}"
)
_clear_proj_error()
return PrimeMeridian.create(context, prime_meridian_pj)
cdef dict _DATUM_TYPE_MAP = {
PJ_TYPE_GEODETIC_REFERENCE_FRAME: "Geodetic Reference Frame",
PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME: "Dynamic Geodetic Reference Frame",
PJ_TYPE_VERTICAL_REFERENCE_FRAME: "Vertical Reference Frame",
PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME: "Dynamic Vertical Reference Frame",
PJ_TYPE_DATUM_ENSEMBLE: "Datum Ensemble",
PJ_TYPE_TEMPORAL_DATUM: "Temporal Datum",
PJ_TYPE_ENGINEERING_DATUM: "Engineering Datum",
PJ_TYPE_PARAMETRIC_DATUM: "Parametric Datum",
}
cdef dict _PJ_DATUM_TYPE_MAP = {
DatumType.DATUM_ENSEMBLE: PJ_TYPE_DATUM_ENSEMBLE,
DatumType.GEODETIC_REFERENCE_FRAME: PJ_TYPE_GEODETIC_REFERENCE_FRAME,
DatumType.DYNAMIC_GEODETIC_REFERENCE_FRAME:
PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME,
DatumType.VERTICAL_REFERENCE_FRAME: PJ_TYPE_VERTICAL_REFERENCE_FRAME,
DatumType.DYNAMIC_VERTICAL_REFERENCE_FRAME:
PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME,
}
cdef class Datum(_CRSParts):
"""
.. versionadded:: 2.2.0
Datum for CRS. If it is a compound CRS it is the horizontal datum.
Attributes
----------
name: str
The name of the datum.
"""
def __cinit__(self):
self._ellipsoid = None
self._prime_meridian = None
def __init__(self):
raise RuntimeError(
"Datum can only be initialized like 'Datum.from_*()'."
)
@staticmethod
cdef Datum create(PJ_CONTEXT* context, PJ* datum_pj):
cdef Datum datum = Datum.__new__(Datum)
datum.context = context
datum.projobj = datum_pj
datum._set_base_info()
datum.type_name = _DATUM_TYPE_MAP[proj_get_type(datum.projobj)]
return datum
@staticmethod
def _from_authority(str auth_name not None, code not None, PJ_CATEGORY category):
"""
Create a Datum from an authority code.
Parameters
----------
auth_name: str
Name of the authority.
code: str or int
The code used by the authority.
Returns
-------
Datum
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* datum_pj = proj_create_from_database(
context,
cstrencode(auth_name),
cstrencode(str(code)),
category,
False,
NULL,
)
if datum_pj == NULL:
pyproj_context_destroy(context)
raise CRSError(f"Invalid authority or code ({auth_name}, {code})")
_clear_proj_error()
return Datum.create(context, datum_pj)
@staticmethod
def from_authority(str auth_name not None, code not None):
"""
Create a Datum from an authority code.
Parameters
----------
auth_name: str
Name of the authority.
code: str or int
The code used by the authority.
Returns
-------
Datum
"""
try:
return Datum._from_authority(auth_name, code, PJ_CATEGORY_DATUM_ENSEMBLE)
except CRSError:
return Datum._from_authority(auth_name, code, PJ_CATEGORY_DATUM)
@staticmethod
def from_epsg(code not None):
"""
Create a Datum from an EPSG code.
Parameters
----------
code: str or int
The code used by EPSG.
Returns
-------
Datum
"""
return Datum.from_authority("EPSG", code)
@staticmethod
def _from_string(str datum_string not None):
"""
Create a Datum from a string.
Examples:
- urn:ogc:def:datum:EPSG::6326
- DATUM["World Geodetic System 1984",
ELLIPSOID["WGS 84",6378137,298.257223563,
LENGTHUNIT["metre",1]],
ID["EPSG",6326]]
Parameters
----------
datum_string: str
Datum string.
Returns
-------
Datum
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* datum_pj = proj_create(
context,
cstrencode(datum_string)
)
if (
datum_pj == NULL or
proj_get_type(datum_pj) not in _DATUM_TYPE_MAP
):
proj_destroy(datum_pj)
pyproj_context_destroy(context)
raise CRSError(f"Invalid datum string: {datum_string}")
_clear_proj_error()
return Datum.create(context, datum_pj)
@staticmethod
def from_string(str datum_string not None):
"""
Create a Datum from a string.
Examples:
- urn:ogc:def:datum:EPSG::6326
- DATUM["World Geodetic System 1984",
ELLIPSOID["WGS 84",6378137,298.257223563,
LENGTHUNIT["metre",1]],
ID["EPSG",6326]]
- World Geodetic System 1984
Parameters
----------
datum_string: str
Datum string.
Returns
-------
Datum
"""
try:
return Datum._from_string(datum_string)
except CRSError as crs_err:
try:
return Datum.from_name(datum_string)
except CRSError:
raise crs_err
@staticmethod
def _from_name(
str datum_name,
str auth_name,
object datum_type,
):
"""
.. versionadded:: 2.5.0
Create a Datum from a name.
Parameters
----------
datum_name: str
Datum name.
auth_name: str
The authority name to refine search (e.g. 'EPSG').
If None, will search all authorities.
datum_type: DatumType
The datum type to create.
Returns
-------
Datum
"""
pj_datum_type = _PJ_DATUM_TYPE_MAP[datum_type]
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* datum_pj = _from_name(
context,
datum_name,
auth_name,
<PJ_TYPE>pj_datum_type,
)
if datum_pj == NULL:
pyproj_context_destroy(context)
raise CRSError(f"Invalid datum name: {datum_name}")
_clear_proj_error()
return Datum.create(context, datum_pj)
@staticmethod
def from_name(
str datum_name not None,
str auth_name=None,
datum_type=None,
):
"""
.. versionadded:: 2.5.0
Create a Datum from a name.
Examples:
- WGS 84
- World Geodetic System 1984
Parameters
----------
datum_name: str
Datum name.
auth_name: str, optional
The authority name to refine search (e.g. 'EPSG').
If None, will search all authorities.
datum_type: DatumType, optional
The datum type to create. If it is None, it uses any datum type.
Returns
-------
Datum
"""
if datum_type is None:
# try creating name from all datum types
first_error = None
for datum_type in _PJ_DATUM_TYPE_MAP:
try:
return Datum.from_name(
datum_name=datum_name,
auth_name=auth_name,
datum_type=datum_type,
)
except CRSError as err:
if first_error is None:
first_error = err
raise first_error
datum_type = DatumType.create(datum_type)
return Datum._from_name(
datum_name=datum_name,
auth_name=auth_name,
datum_type=datum_type,
)
@staticmethod
def from_json_dict(dict datum_dict not None):
"""
.. versionadded:: 2.4.0
Create Datum from a JSON dictionary.
Parameters
----------
datum_dict: str
Datum dictionary.
Returns
-------
Datum
"""
return Datum._from_string(json.dumps(datum_dict, cls=NumpyEncoder))
@staticmethod
def from_json(str datum_json_str not None):
"""
.. versionadded:: 2.4.0
Create Datum from a JSON string.
Parameters
----------
datum_json_str: str
Datum JSON string.
Returns
-------
Datum
"""
return Datum.from_json_dict(_load_proj_json(datum_json_str))
@property
def ellipsoid(self):
"""
Returns
-------
Ellipsoid:
The ellipsoid object with associated attributes.
"""
if self._ellipsoid is not None:
return None if self._ellipsoid is False else self._ellipsoid
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* ellipsoid_pj = proj_get_ellipsoid(
context,
self.projobj,
)
_clear_proj_error()
if ellipsoid_pj == NULL:
pyproj_context_destroy(context)
self._ellipsoid = False
return None
self._ellipsoid = Ellipsoid.create(context, ellipsoid_pj)
return self._ellipsoid
@property
def prime_meridian(self):
"""
Returns
-------
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
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* prime_meridian_pj = proj_get_prime_meridian(
context,
self.projobj,
)
_clear_proj_error()
if prime_meridian_pj == NULL:
pyproj_context_destroy(context)
self._prime_meridian = False
return None
self._prime_meridian = PrimeMeridian.create(
context,
prime_meridian_pj,
)
return self._prime_meridian
cdef class Param:
"""
.. versionadded:: 2.2.0
Coordinate operation parameter.
Attributes
----------
name: str
The name of the parameter.
auth_name: str
The authority name of the parameter (i.e. EPSG).
code: str
The code of the parameter (i.e. 9807).
value: str or double
The value of the parameter.
unit_conversion_factor: double
The factor to convert to meters.
unit_name: str
The name of the unit.
unit_auth_name: str
The authority name of the unit (i.e. EPSG).
unit_code: str
The code of the unit (i.e. 9807).
unit_category: str
The category of the unit (unknown, none, linear,
angular, scale, time or parametric).
"""
def __cinit__(self):
self.name = "undefined"
self.auth_name = "undefined"
self.code = "undefined"
self.value = "undefined"
self.unit_conversion_factor = float("nan")
self.unit_name = "undefined"
self.unit_auth_name = "undefined"
self.unit_code = "undefined"
self.unit_category = "undefined"
@staticmethod
cdef Param create(PJ_CONTEXT* context, PJ* projobj, int param_idx):
cdef:
Param param = Param()
const char *out_name
const char *out_auth_name
const char *out_code
const char *out_value
const char *out_value_string
const char *out_unit_name
const char *out_unit_auth_name
const char *out_unit_code
const char *out_unit_category
double value_double
proj_coordoperation_get_param(
context,
projobj,
param_idx,
&out_name,
&out_auth_name,
&out_code,
&value_double,
&out_value_string,
&param.unit_conversion_factor,
&out_unit_name,
&out_unit_auth_name,
&out_unit_code,
&out_unit_category
)
param.name = decode_or_undefined(out_name)
param.auth_name = decode_or_undefined(out_auth_name)
param.code = decode_or_undefined(out_code)
param.unit_name = decode_or_undefined(out_unit_name)
param.unit_auth_name = decode_or_undefined(out_unit_auth_name)
param.unit_code = decode_or_undefined(out_unit_code)
param.unit_category = decode_or_undefined(out_unit_category)
value_string = cstrdecode(out_value_string)
param.value = value_double if value_string is None else value_string
return param
def __str__(self):
return f"{self.auth_name}:{self.auth_code}"
def __repr__(self):
return (
f"Param(name={self.name}, auth_name={self.auth_name}, code={self.code}, "
f"value={self.value}, unit_name={self.unit_name}, "
f"unit_auth_name={self.unit_auth_name}, unit_code={self.unit_code}, "
f"unit_category={self.unit_category})"
)
cdef class Grid:
"""
.. versionadded:: 2.2.0
Coordinate operation grid.
Attributes
----------
short_name: str
The short name of the grid.
full_name: str
The full name of the grid.
package_name: str
The package name where the grid might be found.
url: str
The grid URL or the package URL where the grid might be found.
direct_download: int
If 1, *url* can be downloaded directly.
open_license: int
If 1, the grid is released with an open license.
available: int
If 1, the grid is available at runtime.
"""
def __cinit__(self):
self.short_name = "undefined"
self.full_name = "undefined"
self.package_name = "undefined"
self.url = "undefined"
self.direct_download = False
self.open_license = False
self.available = False
@staticmethod
cdef Grid create(PJ_CONTEXT* context, PJ* projobj, int grid_idx):
cdef:
Grid grid = Grid()
const char *out_short_name
const char *out_full_name
const char *out_package_name
const char *out_url
int direct_download = 0
int open_license = 0
int available = 0
proj_coordoperation_get_grid_used(
context,
projobj,
grid_idx,
&out_short_name,
&out_full_name,
&out_package_name,
&out_url,
&direct_download,
&open_license,
&available
)
grid.short_name = decode_or_undefined(out_short_name)
grid.full_name = decode_or_undefined(out_full_name)
grid.package_name = decode_or_undefined(out_package_name)
grid.url = decode_or_undefined(out_url)
grid.direct_download = direct_download == 1
grid.open_license = open_license == 1
grid.available = available == 1
_clear_proj_error()
return grid
def __str__(self):
return self.full_name
def __repr__(self):
return (
f"Grid(short_name={self.short_name}, full_name={self.full_name}, "
f"package_name={self.package_name}, url={self.url}, "
f"direct_download={self.direct_download}, "
f"open_license={self.open_license}, available={self.available})"
)
cdef dict _COORDINATE_OPERATION_TYPE_MAP = {
PJ_TYPE_UNKNOWN: "Unknown",
PJ_TYPE_CONVERSION: "Conversion",
PJ_TYPE_TRANSFORMATION: "Transformation",
PJ_TYPE_CONCATENATED_OPERATION: "Concatenated Operation",
PJ_TYPE_OTHER_COORDINATE_OPERATION: "Other Coordinate Operation",
}
cdef dict _PJ_COORDINATE_OPERATION_TYPE_MAP = {
CoordinateOperationType.CONVERSION: PJ_TYPE_CONVERSION,
CoordinateOperationType.TRANSFORMATION: PJ_TYPE_TRANSFORMATION,
CoordinateOperationType.CONCATENATED_OPERATION: PJ_TYPE_CONCATENATED_OPERATION,
CoordinateOperationType.OTHER_COORDINATE_OPERATION:
PJ_TYPE_OTHER_COORDINATE_OPERATION,
}
cdef class CoordinateOperation(_CRSParts):
"""
.. versionadded:: 2.2.0
Coordinate operation for CRS.
Attributes
----------
name: str
The name of the method(projection) with authority information.
method_name: str
The method (projection) name.
method_auth_name: str
The method authority name.
method_code: str
The method code.
is_instantiable: int
If 1, a coordinate operation can be instantiated as a PROJ pipeline.
This also checks that referenced grids are available.
has_ballpark_transformation: int
If 1, the coordinate operation has a ballpark transformation,
that is a very approximate one, due to lack of more accurate transformations.
accuracy: float
The accuracy (in metre) of a coordinate operation.
"""
def __cinit__(self):
self._params = None
self._grids = None
self._area_of_use = None
self.method_name = "undefined"
self.method_auth_name = "undefined"
self.method_code = "undefined"
self.is_instantiable = False
self.has_ballpark_transformation = False
self.accuracy = float("nan")
self._towgs84 = None
self._operations = None
def __init__(self):
raise RuntimeError(
"CoordinateOperation can only be initialized like "
"CoordinateOperation.from_*()'."
)
@staticmethod
cdef CoordinateOperation create(PJ_CONTEXT* context, PJ* coord_operation_pj):
cdef CoordinateOperation coord_operation = CoordinateOperation.__new__(
CoordinateOperation
)
coord_operation.context = context
coord_operation.projobj = coord_operation_pj
cdef const char *out_method_name = NULL
cdef const char *out_method_auth_name = NULL
cdef const char *out_method_code = NULL
proj_coordoperation_get_method_info(
coord_operation.context,
coord_operation.projobj,
&out_method_name,
&out_method_auth_name,
&out_method_code
)
coord_operation._set_base_info()
coord_operation.method_name = decode_or_undefined(out_method_name)
coord_operation.method_auth_name = decode_or_undefined(out_method_auth_name)
coord_operation.method_code = decode_or_undefined(out_method_code)
coord_operation.accuracy = proj_coordoperation_get_accuracy(
coord_operation.context,
coord_operation.projobj
)
coord_operation.is_instantiable = proj_coordoperation_is_instantiable(
coord_operation.context,
coord_operation.projobj
) == 1
coord_operation.has_ballpark_transformation = \
proj_coordoperation_has_ballpark_transformation(
coord_operation.context,
coord_operation.projobj
) == 1
cdef PJ_TYPE operation_type = proj_get_type(coord_operation.projobj)
coord_operation.type_name = _COORDINATE_OPERATION_TYPE_MAP[operation_type]
_clear_proj_error()
return coord_operation
@staticmethod
def from_authority(
str auth_name not None,
code not None,
bint use_proj_alternative_grid_names=False,
):
"""
Create a CoordinateOperation from an authority code.
Parameters
----------
auth_name: str
Name of the authority.
code: str or int
The code used by the authority.
use_proj_alternative_grid_names: bool, default=False
Use the PROJ alternative grid names.
Returns
-------
CoordinateOperation
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* coord_operation_pj = proj_create_from_database(
context,
cstrencode(auth_name),
cstrencode(str(code)),
PJ_CATEGORY_COORDINATE_OPERATION,
use_proj_alternative_grid_names,
NULL,
)
if coord_operation_pj == NULL:
pyproj_context_destroy(context)
raise CRSError(f"Invalid authority or code ({auth_name}, {code})")
_clear_proj_error()
return CoordinateOperation.create(context, coord_operation_pj)
@staticmethod
def from_epsg(code not None, bint use_proj_alternative_grid_names= False):
"""
Create a CoordinateOperation from an EPSG code.
Parameters
----------
code: str or int
The code used by EPSG.
use_proj_alternative_grid_names: bool, default=False
Use the PROJ alternative grid names.
Returns
-------
CoordinateOperation
"""
return CoordinateOperation.from_authority(
"EPSG", code, use_proj_alternative_grid_names
)
@staticmethod
def _from_string(str coordinate_operation_string not None):
"""
Create a CoordinateOperation from a string.
Example:
- urn:ogc:def:coordinateOperation:EPSG::1671
Parameters
----------
coordinate_operation_string: str
Coordinate operation string.
Returns
-------
CoordinateOperation
"""
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* coord_operation_pj = proj_create(
context,
cstrencode(coordinate_operation_string)
)
if (
coord_operation_pj == NULL or
proj_get_type(coord_operation_pj) not in (
PJ_TYPE_CONVERSION,
PJ_TYPE_TRANSFORMATION,
PJ_TYPE_CONCATENATED_OPERATION,
PJ_TYPE_OTHER_COORDINATE_OPERATION,
)
):
proj_destroy(coord_operation_pj)
pyproj_context_destroy(context)
raise CRSError(
"Invalid coordinate operation string: "
f"{coordinate_operation_string}"
)
_clear_proj_error()
return CoordinateOperation.create(context, coord_operation_pj)
@staticmethod
def from_string(str coordinate_operation_string not None):
"""
Create a CoordinateOperation from a string.
Example:
- urn:ogc:def:coordinateOperation:EPSG::1671
- UTM zone 14N
- +proj=utm +zone=14
Parameters
----------
coordinate_operation_string: str
Coordinate operation string.
Returns
-------
CoordinateOperation
"""
try:
return CoordinateOperation._from_string(coordinate_operation_string)
except CRSError as crs_err:
try:
return CoordinateOperation.from_name(coordinate_operation_string)
except CRSError:
raise crs_err
@staticmethod
def from_json_dict(dict coordinate_operation_dict not None):
"""
Create CoordinateOperation from a JSON dictionary.
.. versionadded:: 2.4.0
Parameters
----------
coordinate_operation_dict: str
CoordinateOperation dictionary.
Returns
-------
CoordinateOperation
"""
return CoordinateOperation._from_string(
json.dumps(coordinate_operation_dict, cls=NumpyEncoder)
)
@staticmethod
def from_json(str coordinate_operation_json_str not None):
"""
Create CoordinateOperation from a JSON string.
.. versionadded:: 2.4.0
Parameters
----------
coordinate_operation_json_str: str
CoordinateOperation JSON string.
Returns
-------
CoordinateOperation
"""
return CoordinateOperation.from_json_dict(
_load_proj_json(coordinate_operation_json_str
))
@staticmethod
def from_name(
str coordinate_operation_name not None,
str auth_name=None,
coordinate_operation_type not None=CoordinateOperationType.CONVERSION,
):
"""
.. versionadded:: 2.5.0
Create a Coordinate Operation from a name.
Examples:
- UTM zone 14N
Parameters
----------
coordinate_operation_name: str
Coordinate Operation name.
auth_name: str, optional
The authority name to refine search (e.g. 'EPSG').
If None, will search all authorities.
coordinate_operation_type: CoordinateOperationType, optional
The coordinate operation type to create. Default is
``pyproj.crs.enums.CoordinateOperationType.CONVERSION``
Returns
-------
CoordinateOperation
"""
pj_coordinate_operation_type = _PJ_COORDINATE_OPERATION_TYPE_MAP[
CoordinateOperationType.create(coordinate_operation_type)
]
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* coordinate_operation_pj = _from_name(
context,
coordinate_operation_name,
auth_name,
<PJ_TYPE>pj_coordinate_operation_type,
)
if coordinate_operation_pj == NULL:
pyproj_context_destroy(context)
raise CRSError(
"Invalid coordinate operation name: "
f"{coordinate_operation_name}"
)
_clear_proj_error()
return CoordinateOperation.create(context, coordinate_operation_pj)
@property
def params(self):
"""
Returns
-------
list[Param]:
The coordinate operation parameters.
"""
if self._params is not None:
return self._params
self._params = []
cdef int num_params = 0
num_params = proj_coordoperation_get_param_count(
self.context,
self.projobj
)
for param_idx from 0 <= param_idx < num_params:
self._params.append(
Param.create(
self.context,
self.projobj,
param_idx
)
)
_clear_proj_error()
return self._params
@property
def grids(self):
"""
Returns
-------
list[Grid]:
The coordinate operation grids.
"""
if self._grids is not None:
return self._grids
self._grids = []
cdef int num_grids = 0
num_grids = proj_coordoperation_get_grid_used_count(
self.context,
self.projobj
)
for grid_idx from 0 <= grid_idx < num_grids:
self._grids.append(
Grid.create(
self.context,
self.projobj,
grid_idx
)
)
_clear_proj_error()
return self._grids
@property
def area_of_use(self):
"""
Returns
-------
AreaOfUse:
The area of use object with associated attributes.
"""
if self._area_of_use is not None:
return self._area_of_use
self._area_of_use = create_area_of_use(self.context, self.projobj)
return self._area_of_use
def to_proj4(self, version not None=ProjVersion.PROJ_5, bint pretty=False):
"""
Convert the projection to a PROJ string.
.. versionadded:: 3.1.0 pretty
Parameters
----------
version: pyproj.enums.ProjVersion, default=pyproj.enums.ProjVersion.PROJ_5
The version of the PROJ string output.
pretty: bool, default=False
If True, it will set the output to be a multiline string.
Returns
-------
str:
The PROJ string.
"""
return _to_proj4(self.context, self.projobj, version=version, pretty=pretty)
@property
def towgs84(self):
"""
Returns
-------
list[float]:
A list of 3 or 7 towgs84 values if they exist.
"""
if self._towgs84 is not None:
return self._towgs84
towgs84_dict = OrderedDict(
(
('X-axis translation', None),
('Y-axis translation', None),
('Z-axis translation', None),
('X-axis rotation', None),
('Y-axis rotation', None),
('Z-axis rotation', None),
('Scale difference', None),
)
)
for param in self.params:
if param.name in towgs84_dict:
towgs84_dict[param.name] = param.value
self._towgs84 = [val for val in towgs84_dict.values() if val is not None]
return self._towgs84
@property
def operations(self):
"""
.. versionadded:: 2.4.0
Returns
-------
tuple[CoordinateOperation]:
The operations in a concatenated operation.
"""
if self._operations is not None:
return self._operations
self._operations = _get_concatenated_operations(self.context, self.projobj)
return self._operations
def __repr__(self):
return (
f"<Coordinate Operation: {self.type_name}>\n"
f"Name: {self.name}\n"
f"Method: {self.method_name}\n"
f"Area of Use:\n{self.area_of_use or '- undefined'}"
)
AuthorityMatchInfo = namedtuple(
"AuthorityMatchInfo",
[
"auth_name",
"code",
"confidence",
],
)
AuthorityMatchInfo.__doc__ = """
.. versionadded:: 3.2.0
CRS Authority Match Information
Parameters
----------
auth_name: str
Authority name.
code: str
Object code.
confidence: int
Confidence that this CRS matches
the authority and code.
"""
cdef dict _CRS_TYPE_MAP = {
PJ_TYPE_UNKNOWN: "Unknown CRS",
PJ_TYPE_CRS: "CRS",
PJ_TYPE_GEODETIC_CRS: "Geodetic CRS",
PJ_TYPE_GEOCENTRIC_CRS: "Geocentric CRS",
PJ_TYPE_GEOGRAPHIC_CRS: "Geographic CRS",
PJ_TYPE_GEOGRAPHIC_2D_CRS: "Geographic 2D CRS",
PJ_TYPE_GEOGRAPHIC_3D_CRS: "Geographic 3D CRS",
PJ_TYPE_VERTICAL_CRS: "Vertical CRS",
PJ_TYPE_PROJECTED_CRS: "Projected CRS",
PJ_TYPE_COMPOUND_CRS: "Compound CRS",
PJ_TYPE_TEMPORAL_CRS: "Temporal CRS",
PJ_TYPE_ENGINEERING_CRS: "Engineering CRS",
PJ_TYPE_BOUND_CRS: "Bound CRS",
PJ_TYPE_OTHER_CRS: "Other CRS",
}
IF (CTE_PROJ_VERSION_MAJOR, CTE_PROJ_VERSION_MINOR) >= (9, 2):
_CRS_TYPE_MAP[PJ_TYPE_DERIVED_PROJECTED_CRS] = "Derived Projected CRS"
cdef class _CRS(Base):
"""
.. versionadded:: 2.0.0
The cython CRS class to be used as the base for the
python CRS class.
"""
def __cinit__(self):
self._ellipsoid = None
self._area_of_use = None
self._prime_meridian = None
self._datum = None
self._sub_crs_list = None
self._source_crs = None
self._target_crs = None
self._geodetic_crs = None
self._coordinate_system = None
self._coordinate_operation = None
self._type_name = None
def __init__(self, const char *proj_string):
self.context = pyproj_context_create()
# initialize projection
self.projobj = proj_create(
self.context,
proj_string,
)
if self.projobj == NULL:
raise CRSError(f"Invalid projection: {proj_string}")
# make sure the input is a CRS
if not proj_is_crs(self.projobj):
raise CRSError(f"Input is not a CRS: {proj_string}")
# set proj information
self.srs = proj_string
self._type = proj_get_type(self.projobj)
self._set_base_info()
_clear_proj_error()
@property
def type_name(self):
"""
Returns
-------
str:
The name of the type of the CRS object.
"""
if self._type_name is not None:
return self._type_name
self._type_name = _CRS_TYPE_MAP[self._type]
if not self.is_derived or self._type == PJ_TYPE_PROJECTED_CRS:
# Projected CRS are derived by definition
# https://github.com/OSGeo/PROJ/issues/3525#issuecomment-1365790999
return self._type_name
# Handle Derived Projected CRS
# https://github.com/OSGeo/PROJ/issues/3525#issuecomment-1366002289
IF (CTE_PROJ_VERSION_MAJOR, CTE_PROJ_VERSION_MINOR) < (9, 2):
if self._type == PJ_TYPE_OTHER_CRS:
self._type_name = "Derived Projected CRS"
return self._type_name
ELSE:
if self._type == PJ_TYPE_DERIVED_PROJECTED_CRS:
return self._type_name
self._type_name = f"Derived {self._type_name}"
return self._type_name
@property
def axis_info(self):
"""
Retrieves all relevant axis information in the CRS.
If it is a Bound CRS, it gets the axis list from the Source CRS.
If it is a Compound CRS, it gets the axis list from the Sub CRS list.
Returns
-------
list[Axis]:
The list of axis information.
"""
axis_info_list = []
if self.coordinate_system:
axis_info_list.extend(self.coordinate_system.axis_list)
elif self.is_bound and self.source_crs:
axis_info_list.extend(self.source_crs.axis_info)
else:
for sub_crs in self.sub_crs_list:
axis_info_list.extend(sub_crs.axis_info)
return axis_info_list
@property
def area_of_use(self):
"""
Returns
-------
AreaOfUse:
The area of use object with associated attributes.
"""
if self._area_of_use is not None:
return self._area_of_use
self._area_of_use = create_area_of_use(self.context, self.projobj)
return self._area_of_use
@property
def ellipsoid(self):
"""
.. versionadded:: 2.2.0
Returns
-------
Ellipsoid:
The ellipsoid object with associated attributes.
"""
if self._ellipsoid is not None:
return None if self._ellipsoid is False else self._ellipsoid
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* ellipsoid_pj = proj_get_ellipsoid(
context,
self.projobj
)
_clear_proj_error()
if ellipsoid_pj == NULL:
pyproj_context_destroy(context)
self._ellipsoid = False
return None
self._ellipsoid = Ellipsoid.create(context, ellipsoid_pj)
return self._ellipsoid
@property
def prime_meridian(self):
"""
.. versionadded:: 2.2.0
Returns
-------
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
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* prime_meridian_pj = proj_get_prime_meridian(
context,
self.projobj,
)
_clear_proj_error()
if prime_meridian_pj == NULL:
pyproj_context_destroy(context)
self._prime_meridian = False
return None
self._prime_meridian = PrimeMeridian.create(context, prime_meridian_pj)
return self._prime_meridian
@property
def datum(self):
"""
.. versionadded:: 2.2.0
Returns
-------
Datum
"""
if self._datum is not None:
return None if self._datum is False else self._datum
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* datum_pj = proj_crs_get_datum(
context,
self.projobj,
)
if datum_pj == NULL:
datum_pj = proj_crs_get_horizontal_datum(
context,
self.projobj,
)
_clear_proj_error()
if datum_pj == NULL:
pyproj_context_destroy(context)
self._datum = False
return None
self._datum = Datum.create(context, datum_pj)
return self._datum
@property
def coordinate_system(self):
"""
.. versionadded:: 2.2.0
Returns
-------
CoordinateSystem
"""
if self._coordinate_system is not None:
return None if self._coordinate_system is False else self._coordinate_system
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* coord_system_pj = proj_crs_get_coordinate_system(
context,
self.projobj
)
_clear_proj_error()
if coord_system_pj == NULL:
pyproj_context_destroy(context)
self._coordinate_system = False
return None
self._coordinate_system = CoordinateSystem.create(
context,
coord_system_pj,
)
return self._coordinate_system
@property
def coordinate_operation(self):
"""
.. versionadded:: 2.2.0
Returns
-------
CoordinateOperation
"""
if self._coordinate_operation is not None:
return (
None
if self._coordinate_operation is False
else self._coordinate_operation
)
if not (
self.is_bound or self.is_derived
):
self._coordinate_operation = False
return None
cdef PJ_CONTEXT* context = pyproj_context_create()
cdef PJ* coord_pj = proj_crs_get_coordoperation(
context,
self.projobj
)
_clear_proj_error()
if coord_pj == NULL:
pyproj_context_destroy(context)
self._coordinate_operation = False
return None
self._coordinate_operation = CoordinateOperation.create(
context,
coord_pj,
)
return self._coordinate_operation
@property
def source_crs(self):
"""
Returns
-------
_CRS:
The base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS.
"""
if self._source_crs is not None:
return None if self._source_crs is False else self._source_crs
cdef PJ * projobj = proj_get_source_crs(self.context, self.projobj)
_clear_proj_error()
if projobj == NULL:
self._source_crs = False
return None
try:
self._source_crs = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj)
return self._source_crs
@property
def target_crs(self):
"""
.. versionadded:: 2.2.0
Returns
-------
_CRS:
The hub CRS of a BoundCRS.
"""
if self._target_crs is not None:
return None if self._target_crs is False else self._target_crs
cdef PJ * projobj = proj_get_target_crs(self.context, self.projobj)
_clear_proj_error()
if projobj == NULL:
self._target_crs = False
return None
try:
self._target_crs = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj)
return self._target_crs
@property
def sub_crs_list(self):
"""
If the CRS is a compound CRS, it will return a list of sub CRS objects.
Returns
-------
list[_CRS]
"""
if self._sub_crs_list is not None:
return self._sub_crs_list
if not self.is_compound:
self._sub_crs_list = []
return self._sub_crs_list
cdef int iii = 0
cdef PJ * projobj = proj_crs_get_sub_crs(
self.context,
self.projobj,
iii,
)
self._sub_crs_list = []
while projobj != NULL:
try:
self._sub_crs_list.append(_CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
)))
finally:
proj_destroy(projobj) # deallocate temp proj
iii += 1
projobj = proj_crs_get_sub_crs(
self.context,
self.projobj,
iii,
)
_clear_proj_error()
return self._sub_crs_list
@property
def geodetic_crs(self):
"""
.. versionadded:: 2.2.0
The geodeticCRS / geographicCRS from the CRS.
Returns
-------
_CRS
"""
if self._geodetic_crs is not None:
return self._geodetic_crs if self. _geodetic_crs is not False else None
cdef PJ * projobj = proj_crs_get_geodetic_crs(self.context, self.projobj)
_clear_proj_error()
if projobj == NULL:
self._geodetic_crs = False
return None
try:
self._geodetic_crs = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj) # deallocate temp proj
return self._geodetic_crs
def to_proj4(self, version=ProjVersion.PROJ_4):
"""
Convert the projection to a PROJ string.
.. warning:: You will likely lose important projection
information when converting to a PROJ string from
another format. See:
https://proj.org/faq.html#what-is-the-best-format-for-describing-coordinate-reference-systems # noqa: E501
Parameters
----------
version: pyproj.enums.ProjVersion, default=pyproj.enums.ProjVersion.PROJ_4
The version of the PROJ string output.
Returns
-------
str
"""
warnings.warn(
"You will likely lose important projection information when "
"converting to a PROJ string from another format. See: "
"https://proj.org/faq.html#what-is-the-best-format-for-describing-"
"coordinate-reference-systems"
)
return _to_proj4(self.context, self.projobj, version=version, pretty=False)
def to_epsg(self, int min_confidence=70):
"""
Return the EPSG code best matching the CRS
or None if it a match is not found.
Example:
>>> from pyproj import CRS
>>> ccs = CRS("EPSG:4328")
>>> ccs.to_epsg()
4328
If the CRS is bound, you can attempt to get an epsg code from
the source CRS:
>>> from pyproj import CRS
>>> ccs = CRS("+proj=geocent +datum=WGS84 +towgs84=0,0,0")
>>> ccs.to_epsg()
>>> ccs.source_crs.to_epsg()
4978
>>> ccs == CRS.from_epsg(4978)
False
Parameters
----------
min_confidence: int, default=70
A value between 0-100 where 100 is the most confident.
:ref:`min_confidence`
Returns
-------
Optional[int]:
The best matching EPSG code matching the confidence level.
"""
auth_info = self.to_authority(
auth_name="EPSG",
min_confidence=min_confidence
)
if auth_info is not None and auth_info[0].upper() == "EPSG":
return int(auth_info[1])
return None
def to_authority(self, str auth_name=None, int min_confidence=70):
"""
.. versionadded:: 2.2.0
Return the authority name and code best matching the CRS
or None if it a match is not found.
Example:
>>> from pyproj import CRS
>>> ccs = CRS("EPSG:4328")
>>> ccs.to_authority()
('EPSG', '4328')
If the CRS is bound, you can get an authority from
the source CRS:
>>> from pyproj import CRS
>>> ccs = CRS("+proj=geocent +datum=WGS84 +towgs84=0,0,0")
>>> ccs.to_authority()
>>> ccs.source_crs.to_authority()
('EPSG', '4978')
>>> ccs == CRS.from_authorty('EPSG', '4978')
False
Parameters
----------
auth_name: str, optional
The name of the authority to filter by.
min_confidence: int, default=70
A value between 0-100 where 100 is the most confident.
:ref:`min_confidence`
Returns
-------
tuple(str, str) or None:
The best matching (<auth_name>, <code>) for the confidence level.
"""
try:
authority = self.list_authority(
auth_name=auth_name, min_confidence=min_confidence,
)[0]
return authority.auth_name, authority.code
except IndexError:
return None
def list_authority(self, str auth_name=None, int min_confidence=70):
"""
.. versionadded:: 3.2.0
Return the authority names and codes best matching the CRS.
Example:
>>> from pyproj import CRS
>>> ccs = CRS("EPSG:4328")
>>> ccs.list_authority()
[AuthorityMatchInfo(auth_name='EPSG', code='4326', confidence=100)]
If the CRS is bound, you can get an authority from
the source CRS:
>>> from pyproj import CRS
>>> ccs = CRS("+proj=geocent +datum=WGS84 +towgs84=0,0,0")
>>> ccs.list_authority()
[]
>>> ccs.source_crs.list_authority()
[AuthorityMatchInfo(auth_name='EPSG', code='4978', confidence=70)]
>>> ccs == CRS.from_authorty('EPSG', '4978')
False
Parameters
----------
auth_name: str, optional
The name of the authority to filter by.
min_confidence: int, default=70
A value between 0-100 where 100 is the most confident.
:ref:`min_confidence`
Returns
-------
list[AuthorityMatchInfo]:
List of authority matches for the CRS.
"""
# get list of possible matching projections
cdef PJ_OBJ_LIST *proj_list = NULL
cdef int *c_out_confidence_list = NULL
cdef int num_proj_objects = -9999
cdef bytes b_auth_name
cdef char *user_auth_name = NULL
cdef int iii = 0
if auth_name is not None:
b_auth_name = cstrencode(auth_name)
user_auth_name = b_auth_name
out_confidence_list = []
try:
proj_list = proj_identify(
self.context,
self.projobj,
user_auth_name,
NULL,
&c_out_confidence_list
)
if proj_list != NULL:
num_proj_objects = proj_list_get_count(proj_list)
if c_out_confidence_list != NULL and num_proj_objects > 0:
out_confidence_list = [
c_out_confidence_list[iii] for iii in range(num_proj_objects)
]
finally:
if c_out_confidence_list != NULL:
proj_int_list_destroy(c_out_confidence_list)
_clear_proj_error()
# retrieve the best matching projection
cdef PJ* proj = NULL
cdef const char* code
cdef const char* out_auth_name
authority_list = []
try:
for iii in range(num_proj_objects):
if out_confidence_list[iii] < min_confidence:
continue
proj = proj_list_get(self.context, proj_list, iii)
code = proj_get_id_code(proj, 0)
out_auth_name = proj_get_id_auth_name(proj, 0)
if out_auth_name != NULL and code != NULL:
authority_list.append(
AuthorityMatchInfo(
out_auth_name,
code,
out_confidence_list[iii]
)
)
# at this point, the auth name is copied and we can release the proj object
proj_destroy(proj)
proj = NULL
finally:
# If there was an error we have to call proj_destroy
# If there was none, calling it on NULL does nothing
proj_destroy(proj)
proj_list_destroy(proj_list)
_clear_proj_error()
return authority_list
def to_3d(self, str name=None):
"""
.. versionadded:: 3.1.0
Convert the current CRS to the 3D version if it makes sense.
New vertical axis attributes:
- ellipsoidal height
- oriented upwards
- metre units
Parameters
----------
name: str, optional
CRS name. If None, it will use the name of the original CRS.
Returns
-------
_CRS
"""
cdef char* c_name = NULL
cdef bytes b_name
if name is not None:
b_name = cstrencode(name)
c_name = b_name
cdef PJ * projobj = proj_crs_promote_to_3D(
self.context, c_name, self.projobj
)
_clear_proj_error()
if projobj == NULL:
return self
try:
crs_3d = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj)
return crs_3d
def to_2d(self, str name=None):
"""
.. versionadded:: 3.6.0
Convert the current CRS to the 2D version if it makes sense.
Parameters
----------
name: str, optional
CRS name. If None, it will use the name of the original CRS.
Returns
-------
_CRS
"""
cdef char* c_name = NULL
cdef bytes b_name
if name is not None:
b_name = cstrencode(name)
c_name = b_name
cdef PJ * projobj = proj_crs_demote_to_2D(
self.context, c_name, self.projobj
)
_clear_proj_error()
if projobj == NULL:
return self
try:
crs_2d = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj)
return crs_2d
def _is_crs_property(
self, str property_name, tuple property_types, int sub_crs_index=0
):
"""
.. versionadded:: 2.2.0
This method will check for a property on the CRS.
It will check if it has the property on the sub CRS
if it is a compound CRS and will check if the source CRS
has the property if it is a bound CRS.
Parameters
----------
property_name: str
The name of the CRS property.
property_types: tuple(PJ_TYPE)
The types to check for for the property.
sub_crs_index: int, default=0
THe index of the CRS in the sub CRS list.
Returns
-------
bool:
True if the CRS has this property.
"""
if self.sub_crs_list:
sub_crs = self.sub_crs_list[sub_crs_index]
if sub_crs.is_bound:
is_property = getattr(sub_crs.source_crs, property_name)
else:
is_property = getattr(sub_crs, property_name)
elif self.is_bound:
is_property = getattr(self.source_crs, property_name)
else:
is_property = self._type in property_types
return is_property
@property
def is_geographic(self):
"""
This checks if the CRS is geographic.
It will check if it has a geographic CRS
in the sub CRS if it is a compound CRS and will check if
the source CRS is geographic if it is a bound CRS.
Returns
-------
bool:
True if the CRS is in geographic (lon/lat) coordinates.
"""
return self._is_crs_property(
"is_geographic",
(
PJ_TYPE_GEOGRAPHIC_CRS,
PJ_TYPE_GEOGRAPHIC_2D_CRS,
PJ_TYPE_GEOGRAPHIC_3D_CRS
)
)
@property
def is_projected(self):
"""
This checks if the CRS is projected.
It will check if it has a projected CRS
in the sub CRS if it is a compound CRS and will check if
the source CRS is projected if it is a bound CRS.
Returns
-------
bool:
True if CRS is projected.
"""
return self._is_crs_property(
"is_projected",
(PJ_TYPE_PROJECTED_CRS,)
)
@property
def is_vertical(self):
"""
.. versionadded:: 2.2.0
This checks if the CRS is vertical.
It will check if it has a vertical CRS
in the sub CRS if it is a compound CRS and will check if
the source CRS is vertical if it is a bound CRS.
Returns
-------
bool:
True if CRS is vertical.
"""
return self._is_crs_property(
"is_vertical",
(PJ_TYPE_VERTICAL_CRS,),
sub_crs_index=1
)
@property
def is_bound(self):
"""
Returns
-------
bool:
True if CRS is bound.
"""
return self._type == PJ_TYPE_BOUND_CRS
@property
def is_compound(self):
"""
.. versionadded:: 3.1.0
Returns
-------
bool:
True if CRS is compound.
"""
return self._type == PJ_TYPE_COMPOUND_CRS
@property
def is_engineering(self):
"""
.. versionadded:: 2.2.0
Returns
-------
bool:
True if CRS is local/engineering.
"""
return self._type == PJ_TYPE_ENGINEERING_CRS
@property
def is_geocentric(self):
"""
This checks if the CRS is geocentric and
takes into account if the CRS is bound.
Returns
-------
bool:
True if CRS is in geocentric (x/y) coordinates.
"""
if self.is_bound:
return self.source_crs.is_geocentric
return self._type == PJ_TYPE_GEOCENTRIC_CRS
@property
def is_derived(self):
"""
.. versionadded:: 3.2.0
Returns
-------
bool:
True if CRS is a Derived CRS.
"""
return proj_is_derived_crs(self.context, self.projobj) == 1
def _equals(self, _CRS other, bint ignore_axis_order):
if ignore_axis_order:
# Only to be used with DerivedCRS/ProjectedCRS/GeographicCRS
return proj_is_equivalent_to_with_ctx(
self.context,
self.projobj,
other.projobj,
PJ_COMP_EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
) == 1
return self._is_equivalent(other)
def equals(self, other, ignore_axis_order=False):
"""
Check if the projection objects are equivalent.
Properties
----------
other: CRS
Check if the other object
ignore_axis_order: bool, default=False
If True, it will compare the CRS class and ignore the axis order.
Returns
-------
bool
"""
if not isinstance(other, _CRS):
return False
return self._equals(other, ignore_axis_order=ignore_axis_order)