Errors and refusals

A refusal here is never a bug to work around. Each one stops a coordinate that might be wrong, and each exception type names a different reason. Catch GeodesyError for all of them, or a specific subclass to handle one case.

The hierarchy

        flowchart LR
    GE[GeodeticEngineError] --> GD[GeodesyError]
    GE --> PR[PersistableReferenceError]
    GE --> GR[GeorepositoryError]
    GE --> PB[ProjDbBuildError]
    GD --> U[UnresolvableCRSError]
    GD --> A[AmbiguousOperationError]
    GD --> N[OperationNotAvailableError]
    GD --> B[BallparkTransformationError]
    GD --> G[MissingGridError]
    GD --> E[MissingCoordinateEpochError]
    GD --> T[TransformationFailedError]
    T --> R[CoordinateOutOfRangeError]
    GD --> UE[UnembeddableOperationError]
    UE --> NC[NotCollapsibleError]
    

Everything the package raises on purpose derives from GeodeticEngineError. The exceptions are TypeError and ValueError for malformed arguments, such as points with the wrong number of values.

When each is raised, with an example

UnresolvableCRSError

The CRS input cannot be turned into a CRS. The message says why. For a CRS a custom database build left out, it gives the reason recorded at build time rather than “not found”.

from geodetic_engine.geodesy import Transformation

Transformation("EPSG:not-a-crs", "EPSG:4326")
UnresolvableCRSError: could not resolve 'EPSG:not-a-crs' as a CRS: Invalid projection: EPSG:not-a-crs: (Internal Proj Error: proj_create: crs not found: EPSG:not-a-crs)

AmbiguousOperationError

A datum change with no operation named. Name one; Choosing an operation shows how to choose. allow_any_operation=True no longer bypasses this. The keyword is accepted only so older calls do not break.

Transformation("EPSG:4230", "EPSG:4326")
AmbiguousOperationError: EPSG:4230 to EPSG:4326 involves a datum change; every datum operation must be named explicitly. allow_any_operation no longer permits automatic selection or ballpark results

OperationNotAvailableError

The named operation cannot be applied between these CRSs, or does not exist. This package raises rather than let PROJ substitute another operation:

# EPSG:1133 is ED50 -> WGS 84; it has nothing to do with WGS 84 -> World Mercator.
Transformation("EPSG:4326", "EPSG:3395", operation="EPSG:1133")
OperationNotAvailableError: applying EPSG:1133 between 'ED50 (with axis order normalized for visualization)' and 'WGS 84 / World Mercator' would require an additional, unrequested datum change; name the full operation instead
Transformation("EPSG:4326", "EPSG:3395", operation="EPSG:99999999")
OperationNotAvailableError: EPSG:99999999 could not be built as a coordinate operation, and is not among the operations PROJ offers for EPSG:4326 to EPSG:3395: Invalid projection urn:ogc:def:coordinateOperation:EPSG::99999999.: (Internal Proj Error: proj_create: coordinate operation not found: EPSG:99999999)

BallparkTransformationError

The only path PROJ can build is a ballpark approximation. With stock EPSG data this is usually pre-empted: a pair with no real transformation is also a datum change with nothing to name, so AmbiguousOperationError comes first. Either way, no approximate coordinate is returned:

# Jamaica 1875 -> GDA94: PROJ knows no transformation, only a ballpark.
Transformation("EPSG:4241", "EPSG:4283")
AmbiguousOperationError: EPSG:4241 to EPSG:4283 involves a datum change; every datum operation must be named explicitly. allow_any_operation no longer permits automatic selection or ballpark results

BallparkTransformationError is a last check. It is raised if the pipeline PROJ built turns out to be a ballpark after every other check passed. Stock EPSG data rarely gets this far.

MissingGridError

The operation reads a grid file that is not installed. PROJ alone would fall back to another operation without saying so. This package names the grid and stops. To show it, PROJ is temporarily pointed at a directory holding proj.db but no grids, simulating a machine without proj-data:

import os
import shutil
import tempfile
from pathlib import Path

import pyproj

installed = pyproj.datadir.get_data_dir()
bare = Path(tempfile.mkdtemp()) / "proj-without-grids"
bare.mkdir()
shutil.copy(Path(installed) / "proj.db", bare / "proj.db")

pyproj.datadir.set_data_dir(str(bare))
try:
    Transformation("EPSG:4979", "EPSG:3855", operation="EPSG:3858")
finally:
    pyproj.datadir.set_data_dir(installed)
MissingGridError: transforming EPSG:4979 to EPSG:3855 needs 1 grid file(s) that are not installed: Und_min2.5x2.5_egm2008_isw=82_WGS84_TideFree

Install the named grid (projsync --file us_nga_egm08_25.tif) and the same call succeeds. available_operations() shows each candidate’s grids with available flags, so you can check before transforming.

MissingCoordinateEpochError

The operation reads a coordinate epoch and none was given, or the one given is not finite:

tfm = Transformation("EPSG:4896", "EPSG:4938", operation="EPSG:6277")
tfm.transform([(-2593197.524, 5656917.6189, -1394397.8828)])
MissingCoordinateEpochError: a dynamic CRS or time-dependent operation requires a coordinate epoch; supply a finite observation year explicitly

This is raised by transform(), not by the constructor: whether an epoch is needed is known up front (tfm.requires_epoch), but the epoch is given per batch.

CoordinateOutOfRangeError

A latitude outside the range its axis unit allows, checked before PROJ is called. The usual cause is passing projected metres to a geographic CRS. The limit comes from the axis unit, so for a CRS in grads it is ±100, not ±90.

tfm = Transformation("EPSG:4230", "EPSG:4326", operation="EPSG:1612")
tfm.transform(590000, 6700000)  # UTM metres handed to a geographic CRS
CoordinateOutOfRangeError: point 0 has geodetic latitude 6700000.0 degree, outside the valid range [-90, 90] for EPSG:4230; values are given in ('Lon', 'Lat') order and in ('degree', 'degree') -- projected coordinates in metres need a projected CRS

It is a subclass of TransformationFailedError, so code catching that still works.

TransformationFailedError

PROJ ran and could not produce a finite value for every point and every target axis. The message includes PROJ’s own error.

UnembeddableOperationError and NotCollapsibleError

Raised when an operation must be written as the single, unit-less ABRIDGEDTRANSFORMATION a bound CRS carries and cannot be. You see these when building bound CRSs: through Persistable references (OSDU), a custom database build, or directly through Helmert utilities:

from pyproj.crs import CoordinateOperation

from geodetic_engine.geodesy.utils import collapse_concatenated

collapse_concatenated(CoordinateOperation.from_epsg(1612))  # a single step: nothing to collapse
NotCollapsibleError: EPSG:1612 is not a concatenated operation with at least two steps, so there is nothing to collapse

Errors from other modules

Module

Base class

Raised for

persistablereference

PersistableReferenceError

Malformed payloads, unsupported methods, unresolvable grids (Persistable references (OSDU))

georepository

GeorepositoryError

Configuration, authentication, HTTP, truncated pagination (Georepository client)

projdb, osdudb

ProjDbBuildError

Anything that stops a database build (Custom database from Georepository)