CRS & Reprojection Helpers#
CRS-handling helpers in pyramids.base.crs — the single source of
truth for osr.SpatialReference construction, WKT/Proj4 → EPSG
resolution, and coordinate reprojection. The most-commonly-used
ones are also re-exposed as FeatureCollection static methods for
ergonomic continuity (e.g.
FeatureCollection.reproject_coordinates delegates to
pyramids.base.crs.reproject_coordinates).
Functions#
pyramids.base.crs.sr_from_epsg(epsg)
#
Build an :class:osr.SpatialReference from an EPSG code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
epsg
|
int
|
EPSG code; cast to |
required |
Returns:
| Type | Description |
|---|---|
SpatialReference
|
osr.SpatialReference: The constructed SRS. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If GDAL cannot resolve the EPSG code (the
non-zero return path from |
Source code in src/pyramids/base/crs.py
pyramids.base.crs.sr_from_wkt(wkt)
#
Build an :class:osr.SpatialReference from a WKT string.
Thin wrapper around osr.SpatialReference(wkt=wkt) that gives
the WKT path a consistent name alongside :func:sr_from_epsg and
:func:create_sr_from_proj. Use this when you have a WKT (the
most common case in the dataset stack — dataset.crs returns
WKT) and want a typed SRS without re-typing the constructor's
keyword argument every call site.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wkt
|
str
|
Well-Known Text representation of the spatial reference. |
required |
Returns:
| Type | Description |
|---|---|
SpatialReference
|
osr.SpatialReference: The constructed SRS. |
Examples:
- Round-trip an EPSG code through WKT:
Source code in src/pyramids/base/crs.py
pyramids.base.crs.create_sr_from_proj(prj, string_type=None)
#
Create an :class:osr.SpatialReference from a projection string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prj
|
str
|
The projection string (WKT, ESRI WKT, or Proj4). |
required |
string_type
|
str | None
|
One of |
None
|
Returns:
| Type | Description |
|---|---|
SpatialReference
|
osr.SpatialReference: The constructed spatial reference. |
Examples:
- Parse a standard EPSG:4326 WKT string and inspect the result:
- Parse a Proj4 string by passing
string_type="PROJ4": - Parse an EPSG:3857 WKT and confirm the axis order is projected:
Source code in src/pyramids/base/crs.py
pyramids.base.crs.get_epsg_from_prj(prj)
#
Return the EPSG code identified by a projection string.
Resolves the EPSG of the root CRS object in three steps:
:meth:osr.SpatialReference.AutoIdentifyEPSG (tags recognisable
CRSes), then the root AUTHORITY code, then a confident,
unambiguous :meth:osr.SpatialReference.FindMatches PROJ-database
lookup for well-known CRSes whose WKT lacks a root authority (e.g. a
UTM PROJCS from GDAL's AAIGrid driver). The code of a child
unit/datum node is never returned as if it were a CRS — that bug
(issue #403) made GRIB rasters resolve to the degree-unit EPSG:9122
and UTM ASCII grids to the WGS_1984 datum EPSG:6326.
An empty input string is no longer silently mapped to 4326; that
legacy default masked real configuration errors. Callers that
genuinely want a fallback should handle the CRSError themselves,
or use :func:epsg_from_wkt which accepts an explicit default.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prj
|
str
|
Projection string. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The resolved EPSG code. |
Raises:
| Type | Description |
|---|---|
CRSError
|
If |
Examples:
- Resolve EPSG:4326 from its standard WKT representation:
- Resolve EPSG:3857 (Web Mercator) from its WKT representation:
- A well-known CRS whose WKT carries no root authority still resolves, via a confident PROJ-database match (here a "WGS 84 / UTM zone 18N" PROJCS with its root authority stripped):
- A genuinely custom CRS that matches no database entry raises
CRSErrorrather than returning a child unit/datum code (here GDAL's spherical-earth GRIB GEOGCS, whose only authority node is the degree unit EPSG:9122):>>> grib_wkt = ( ... 'GEOGCS["Coordinate System imported from GRIB file",' ... 'DATUM["unnamed",SPHEROID["Sphere",6371229,0]],' ... 'PRIMEM["Greenwich",0],' ... 'UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],' ... 'AXIS["Latitude",NORTH],AXIS["Longitude",EAST]]' ... ) >>> get_epsg_from_prj(grib_wkt) Traceback (most recent call last): ... pyramids.base._errors.CRSError: get_epsg_from_prj could not resolve an EPSG code ... - An empty projection string raises
CRSError(aValueErrorsubclass):
Source code in src/pyramids/base/crs.py
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 | |
pyramids.base.crs.epsg_from_wkt(wkt, default=4326)
#
Resolve an EPSG code from a WKT / Proj string with a fallback.
Wraps :func:get_epsg_from_prj to absorb the
get_epsg_from_prj(wkt) if wkt else default idiom that was
previously open-coded in four places across the dataset stack.
Returns default when wkt is empty (or None), and also when
get_epsg_from_prj cannot resolve an EPSG from a non-empty wkt
(it raises :class:CRSError for a custom CRS whose root carries no
EPSG authority — e.g. a spherical-earth GRIB GEOGCS); otherwise
delegates to :func:get_epsg_from_prj.
A CRS whose authority is not EPSG — Robinson is ESRI:54030 — resolves to no
EPSG code at all (issue #965), so it takes the default here just as an empty
projection does. That is this function's contract, but it means the default can
stand in for a real, named projection rather than only for a missing one. When
that distinction matters, use :func:epsg_of_crs, which reports None, and read
the CRS itself from .crs.
Use this in places where an empty projection should be treated as
a soft "unknown CRS, assume WGS84" rather than a hard error — for
example the Dataset.epsg property on a freshly-built
in-memory raster that has no projection metadata yet. Use
:func:get_epsg_from_prj directly when you want the strict
behaviour where an empty projection raises.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wkt
|
str | None
|
Projection string (WKT, ESRI WKT, or Proj4). An empty
string or |
required |
default
|
int
|
EPSG code to return when |
4326
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
EPSG code resolved from |
int
|
empty or its CRS carries no resolvable EPSG. |
Examples:
- Empty input falls back to the supplied default:
- Non-empty WKT delegates to :func:
get_epsg_from_prj: - An unresolvable custom CRS falls back to
defaultinstead of raising (here GDAL's spherical-earth GRIB GEOGCS):>>> from pyramids.base.crs import epsg_from_wkt >>> grib_wkt = ( ... 'GEOGCS["Coordinate System imported from GRIB file",' ... 'DATUM["unnamed",SPHEROID["Sphere",6371229,0]],' ... 'PRIMEM["Greenwich",0],' ... 'UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],' ... 'AXIS["Latitude",NORTH],AXIS["Longitude",EAST]]' ... ) >>> epsg_from_wkt(grib_wkt) 4326 >>> epsg_from_wkt(grib_wkt, default=3857) 3857
Source code in src/pyramids/base/crs.py
956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 | |
pyramids.base.crs.epsg_of_crs(wkt)
#
Resolve the EPSG code a CRS declares, or None when it declares none.
None means "this CRS has no EPSG code", which covers two situations that
:func:epsg_from_wkt conflates by substituting 4326 for both:
- No CRS at all — an empty (or
None) projection. The dataset is not georeferenced, so there is nothing to report. Fabricating WGS 84 here would claim a georeference the data does not have. - A CRS with no EPSG authority — a real projection the EPSG register does
not name: an orthographic or geostationary projection, a rotated pole, a
spherical-earth GRIB
GEOGCS. Reporting 4326 for these claimed WGS 84 for grids that are not WGS 84 — an orthographic frame is not lat/lon at all, and a spherical datum differs from the WGS 84 ellipsoid by up to ~20 km.
The CRS itself is not lost in the second case: .crs still returns the WKT,
and :func:crs_spec falls back to it, so reprojection and every other
CRS-consuming operation keeps working. Only the code is absent, because
there genuinely is not one.
This mirrors standard CRS-handling behaviour —
to_epsg() returns None rather than guessing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wkt
|
str | None
|
Projection string (WKT, ESRI WKT, or Proj4), possibly empty/ |
required |
Returns:
| Type | Description |
|---|---|
int | None
|
int | None: The EPSG code, or |
int | None
|
names a CRS that carries no EPSG authority. |
Examples:
- An empty projection means "no CRS", not WGS 84:
- A real projection resolves to its EPSG code:
- A CRS the EPSG register does not name has no code to report:
See Also
epsg_from_wkt: The soft variant that substitutes default for both cases.
Source code in src/pyramids/base/crs.py
pyramids.base.crs.crs_spec(epsg, wkt)
#
Best usable CRS specification for a dataset, or None when it has none.
Replaces the dataset.epsg or dataset.crs idiom. That expression looks
total but is not: once epsg propagates None for an ungeoreferenced
raster, it evaluates to the empty CRS string, which every downstream
constructor rejects with an opaque "Invalid projection: ''". Returning
None instead makes the absence explicit, so callers either pass it on
(producing an ungeoreferenced result) or reject it deliberately via
:func:require_crs_spec.
The word usable is load-bearing, and it is why the EPSG code is not blindly
preferred. Most of this function's consumers hand the result to a library that
resolves it with pyproj — geopandas.GeoDataFrame.set_crs is the common one —
and pyproj's bundled PROJ database is routinely older than the one GDAL vendors.
An EPSG code pyramids obtained from GDAL can therefore be one pyproj cannot look
up, and returning it would hand every consumer a specification that raises
"crs not found" (issue #943). When that is the case and a WKT is available, the
WKT is returned instead: it describes the same CRS, and pyproj parses it happily —
only the catalogue lookup is missing, never the projection itself. The code is
still preferred whenever it works, which is the overwhelming majority of the time
and is checked once per code and cached.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
epsg
|
int | None
|
EPSG code, or |
required |
wkt
|
str | None
|
Projection WKT, or an empty string / |
required |
Returns:
| Type | Description |
|---|---|
int | str | None
|
int | str | None: The EPSG code when there is one and it is resolvable |
int | str | None
|
downstream, else the WKT, else the code anyway when there is no WKT to fall |
int | str | None
|
back to, else |
Examples:
- An EPSG code is preferred when present:
- A CRS with no EPSG authority falls back to its WKT:
- A code the downstream CRS library cannot resolve yields the WKT, so the specification stays usable:
- No CRS at all is reported as
None, not as an empty string:
See Also
require_crs_spec: The variant that raises when there is no CRS.
Source code in src/pyramids/base/crs.py
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 | |
pyramids.base.crs.require_crs_spec(epsg, wkt, operation)
#
Like :func:crs_spec, but raise when the dataset has no CRS.
Use at the point of an operation that genuinely cannot proceed without a CRS — reprojection, a coordinate transform, a spatial join against a vector. Mirrors standard CRS-handling behaviour: a missing CRS propagates quietly until something actually needs it, and then fails with a message naming the fix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
epsg
|
int | None
|
EPSG code, or |
required |
wkt
|
str | None
|
Projection WKT, or an empty string / |
required |
operation
|
str
|
Short description of what needs the CRS, used in the error. |
required |
Returns:
| Type | Description |
|---|---|
int | str
|
int | str: The EPSG code when there is one, else the WKT. |
Raises:
| Type | Description |
|---|---|
CRSError
|
Neither an EPSG code nor a WKT is available. |
Examples:
- Resolves exactly as :func:
crs_specwhen a CRS is present: - Refuses, naming the operation, when there is none:
Source code in src/pyramids/base/crs.py
pyramids.base.crs.cf_geographic_wkt(units, axis_units=None)
#
WGS 84 WKT when CF axis units describe a lat/lon grid, else "".
CF-1.x lets a data variable carry no grid_mapping; when its coordinate
axes are in degrees east/north the file is geographic and CF simply leaves
the datum implicit. GDAL reports an empty projection for those, and the whole
ecosystem reads them as WGS 84 — so inferring EPSG:4326 from this evidence is
a convention-backed reading of the metadata, not the blanket "assume WGS 84
for anything unprojected" default that ARC-26 removed. A raster with no CRS
and no such evidence still reports no CRS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
units
|
set[str]
|
Lower-cased unit strings from every coordinate array, including the 2-D auxiliary lat/lon a curvilinear grid uses. |
required |
axis_units
|
set[str] | None
|
Lower-cased unit strings from the true horizontal dimension
axes only. A unit here that belongs to a projected or rotated frame
( |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
WGS 84 WKT when both a longitude and a latitude axis are in degrees, |
str
|
otherwise |
Examples:
- Degrees on both axes identify a geographic grid:
- The CF singular spellings are accepted too:
- One axis alone, or non-degree units, is not evidence:
Source code in src/pyramids/base/crs.py
pyramids.base.crs.reproject_coordinates(x, y, *, from_crs=4326, to_crs=3857, precision=6)
#
Reproject parallel x / y coordinate lists between CRSes.
Argument and return order is (x, y) throughout; accepts any
CRS form :meth:pyproj.Transformer.from_crs understands (EPSG
int, EPSG string, WKT, Proj4, :class:pyproj.CRS).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
list[float]
|
X-coordinates in the source CRS (longitudes when
|
required |
y
|
list[float]
|
Y-coordinates in the source CRS (latitudes when
|
required |
from_crs
|
Any
|
Source CRS. Accepts anything
:meth: |
4326
|
to_crs
|
Any
|
Target CRS, same forms as |
3857
|
precision
|
int | None
|
Decimal places to round each returned coordinate to, using
Python's built-in |
6
|
Returns:
| Type | Description |
|---|---|
tuple[list[float], list[float]]
|
tuple[list[float], list[float]]: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
CRSError
|
If :meth: |
Examples:
- Reproject a WGS84 point into Web Mercator:
Source code in src/pyramids/base/crs.py
1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 | |