Results and provenance

Every call returns a TransformationResult containing the coordinates and a record of how they were produced. The record is enough to reproduce or audit the result later.

from geodetic_engine.geodesy import transform

result = transform("EPSG:4230", "EPSG:4326", (2.5, 63.5), operation="EPSG:1612")

What the result records

print("coordinates     :", result.coordinates)
print("count           :", result.count)
print("coordinate order:", result.coordinate_order)          # always "xy"
print("source          :", result.source_crs, result.source_axes, result.source_units)
print("target          :", result.target_crs, result.target_axes, result.target_units)
print("epoch           :", result.coordinate_epoch)
print("grids           :", result.grids, "missing:", result.missing_grids)
coordinates     : ((2.4981894757094416, 63.49961374934551),)
count           : 1
coordinate order: xy
source          : CoordinateReferenceSystem('EPSG:4230') ('Lat', 'Lon') ('degree', 'degree')
target          : CoordinateReferenceSystem('EPSG:4326') ('Lat', 'Lon') ('degree', 'degree')
epoch           : None
grids           : () missing: ()

coordinate_order is always "xy", the order of the values: easting or longitude first wherever the target has one. source_axes, target_axes and the unit tuples are in EPSG’s declared order, which for a geographic CRS is latitude first. They are reported side by side so the two cannot be confused. The value order of each axis is result.target_crs.value_axis_abbreviations, which is also what tells a south/west or north/west CRS apart: those keep their declared order.

The applied operation

operation is an AppliedOperation:

op = result.operation
print("requested     :", op.requested)
print("applied       :", op.authority_code, "-", op.name)
print("method        :", op.method_name)
print("accuracy      :", op.accuracy, "m")
print("route         :", op.route)
print("steps         :", op.steps)
print("ballpark      :", op.ballpark)
print("requires epoch:", op.requires_epoch)
print("direction     :", op.execution_direction)
print("bound         :", op.bound_operations)
requested     : EPSG:1612
applied       : EPSG:1612 - ED50 to WGS 84 (23)
method        : Position Vector transformation (geog2D domain)
accuracy      : 1.0 m
route         : transformer_group
steps         : ('ED50 to WGS 84 (23)', 'axis order change (2D)')
ballpark      : False
requires epoch: False
direction     : TransformDirection.FORWARD
bound         : ()

route records how the transformer was obtained (OperationRoute):

Route

Meaning

transformer_group

Your named operation, found among the candidates PROJ offers for the pair

chained

Your named or stated operation, wrapped in same-datum conversions to fit the pair

bound

Taken from a bound CRS’s own definition; bound_operations names it, or both of them when both CRSs are bound and no single code fits

proj_default

Nothing named, no datum change; PROJ’s conversion, recorded

any_operation

Legacy value in old serialised results; never produced now

Replaying the pipeline

pipeline is the exact PROJ pipeline that ran, including axis swaps, unit conversions and inversions. Plain pyproj can replay it without this package:

print(result.pipeline)
proj=pipeline step proj=unitconvert xy_in=deg xy_out=rad step proj=push v_3 step proj=cart ellps=intl step proj=helmert x=-116.641 y=-56.931 z=-110.559 rx=0.893 ry=0.921 rz=-0.917 s=-3.52 convention=position_vector step inv proj=cart ellps=WGS84 step proj=pop v_3 step proj=unitconvert xy_in=rad xy_out=deg
from pyproj import Transformer

Transformer.from_pipeline(result.pipeline).transform(2.5, 63.5)
(2.4981894757094416, 63.49961374934551)

Use the pipeline, not the operation’s WKT, to reproduce a result. to_wkt() gives the WKT2 of the applied operation, but returns None when WKT2 cannot state it faithfully, for example when a step was applied inverted:

print(result.operation.to_wkt()[:300], "...")
COORDINATEOPERATION["ED50 to WGS 84 (23)",SOURCECRS[GEOGCRS["ED50",DATUM["European Datum 1950",ELLIPSOID["International 1924",6378388,297,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["de ...

Which database answered

The same EPSG code can mean different parameters in different proj.db files. Every result stores SHA-256 fingerprints of the databases PROJ was reading:

result.database_fingerprints
(('/usr/local/share/proj/proj.db',
  '3d3d8b279d100544b5737cd08a732335170fff09ee91166b4137919d6d019198'),)

A result produced from a custom database can therefore be traced back to that database, and through its build report to the register entries it was built from (Provenance).

Serialising

to_json() gives a JSON string and to_json_dict() gives the same as a dict. Both include the full WKT of both CRSs, so the record does not depend on a database being present when it is read:

import json

record = result.to_json_dict()
print(sorted(record))
print(json.dumps(record["operation"], indent=2)[:800])
['coordinate_epoch', 'coordinate_order', 'coordinates', 'database_fingerprints', 'grids', 'operation', 'pipeline', 'source_axes', 'source_crs', 'source_crs_wkt', 'source_units', 'target_axes', 'target_crs', 'target_crs_wkt', 'target_units']
{
  "requested": "EPSG:1612",
  "applied": "EPSG:1612",
  "name": "ED50 to WGS 84 (23)",
  "method": "Position Vector transformation (geog2D domain)",
  "accuracy_m": 1.0,
  "route": "transformer_group",
  "steps": [
    "ED50 to WGS 84 (23)",
    "axis order change (2D)"
  ],
  "ballpark": false,
  "requires_epoch": false,
  "execution_direction": "FORWARD",
  "bound_operations": [],
  "axis_order_corrected": false,
  "definition": {
    "type": "Transformation",
    "name": "ED50 to WGS 84 (23)",
    "source_crs": {
      "type": "GeographicCRS",
      "name": "ED50",
      "datum": {
        "type": "GeodeticReferenceFrame",
        "name": "European Datum 1950",
        "ellipsoid": {
          "name": "International 1924",
          "semi_major_axis": 6378388,
          "inverse_flatt