Transform Coordinate Data Safely with PROJ cct

Feed cct the wrong projection and you get coordinates that look plausible but are simply wrong. It is PROJ's own four-dimensional coordinate conversion and transformation filter, built for exactly this job. The examples here convert longitude and latitude to UTM, preserve extra fields, pull coordinates out of wider records, and check a result with the inverse operation.

Allow about fifteen minutes. You need the proj-bin package, a shell, and input coordinates whose order, datum and units you actually understand. These examples use PROJ 9.4.0 from proj-bin 9.4.0-1build2. No elevated privileges are needed: the commands only read standard input and write standard output unless you deliberately choose an output file.

1. Check the installed command

Confirm the binary and version first. This is an ordinary read-only check:

$ command -v cct
/usr/bin/cct
$ cct --version
cct: Rel. 9.4.0, March 1st, 2024

cct reads records from a file or standard input. Its default coordinate columns are 1, 2, 3 and 4, read as x, y, z and t. Keep two ideas separate: -c, -z and -t describe the input record, while an operator specification such as +proj=utm describes the coordinate operation itself. It is not +proj=... doing double duty as both.

Checkpoint: You have confirmed the command is PROJ 9.4.0, or recorded whatever version is actually installed on your host.

2. Convert longitude and latitude to UTM

For a first smoke test, convert longitude 12 and latitude 55 to UTM zone 32 on the GRS80 ellipsoid. The input here has only two values, so supply fixed height and time with -z 0 and -t 0:

$ printf '12 55\n' | cct -z 0 -t 0 +proj=utm +zone=32 +ellps=GRS80
  691875.6321   6098907.8250        0.0000        0.0000

The first two output values are the transformed x and y coordinates; the fixed height and time come out too, so you get four values back. Here the UTM result is in metres, but that unit comes from the selected operation, not from cct itself.

Do not copy the zone, ellipsoid or axis order blindly. UTM zone 32 suits one longitude range; your real dataset may need a different zone, datum or coordinate convention entirely. Confirm those details from the dataset's own documentation before transforming production data.

Checkpoint: A successful run prints one four-value record and exits with status 0.

3. Use a file and keep the original data safe

For more than a single record, put the input in a working file and send the converted stream to a separate output file. That way you never overwrite the source while checking the result:

$ printf '12 55\n13 55\n' > coordinates.txt
$ cct -z 0 -t 0 +proj=utm +zone=32 +ellps=GRS80 coordinates.txt > coordinates-utm.txt
$ sed -n '1,2p' coordinates-utm.txt
  691875.6321   6098907.8250        0.0000        0.0000
  756099.6474   6098907.8250        0.0000        0.0000

That redirection is done by the shell. If you would rather cct opened the destination itself, use -o coordinates-utm.txt instead. Either way, treat an output path as a state-changing choice: an existing file can simply be replaced. Check first with test ! -e coordinates-utm.txt, or pick a new path.

Recovery: there is no built-in rollback for an overwritten output file. Recovery means restoring it from backup or regenerating it from the untouched input. Never reuse the same path for input and output until you have confirmed how your shell and this command handle that case.

4. Preserve auxiliary columns

Input records often carry values after the coordinate fields, such as an identifier or source label, and cct forwards those straight to the output. Here the height and observation time are transformed as coordinate values while the trailing label rides along unchanged:

$ printf '12 56 100 2018.0 station-A\n' | cct +proj=merc
 1335833.8895   7326837.7149      100.0000     2018.0000 station-A

That is what makes plain text pipelines useful, but it does not make the input self-describing. Keep a record of the column meaning and the operation specification next to the output: nobody can look at bare numbers later and tell whether they are geographic degrees, projected metres or an ellipsoidal height.

5. Select coordinates from wider records

Use -c x,y,z,t when the four coordinate values are not sitting in columns 1 to 4. The numbers are one-based input column positions. This record has longitude in column 5, latitude in column 2, height in column 1 and time in column 4:

$ printf '0 55 0 0 12\n' | cct -c 5,2,1,4 +proj=utm +ellps=GRS80 +zone=32
  833978.5569         0.0000        0.0000        0.0000 55

The output writes the transformed values back into their original positions. Column 3, unselected, stays as it was, and the trailing value keeps riding along. This example deliberately uses a synthetic record so the placement is easy to see; map the real columns from your own file before running this on anything that matters.

A common mistake is reading -c 5,2,1,4 as reordering the output. It does not: it is an input selection, taking the four values in x, y, z, t order, transforming them, then writing them back into the selected positions.

6. Round output only at the presentation boundary

Use -d when a downstream consumer needs a fixed number of decimal places:

$ printf '12 55\n' | cct -d 2 -z 0 -t 0 +proj=utm +zone=32 +ellps=GRS80
  691875.63     6098907.83          0.00        0.0000

Rounding only changes the printed representation, so keep an unrounded result around for later calculations where precision matters. Two decimal places on the way out is a display choice, not a claim that the source coordinates or the transformation are accurate to that level.

7. Check a transformation with the inverse direction

Add -I to request the inverse operation. It is a good pipeline test, but it will not repair a wrong zone, datum or axis order. This command treats the sample geographic values as input to the inverse UTM operation, so its output is not simply the original pair back again:

$ printf '12 55\n' | cct -I -z 0 -t 0 +proj=utm +zone=32 +ellps=GRS80
       4.5113636234    0.0004960657        0.0000        0.0000

For a meaningful round trip, transform a record, then feed the resulting four coordinate values into a second invocation with -I. Compare the final values against the original, allowing for the operation's precision and any rounding you introduced along the way. Keep the intermediate stream unrounded while you check it.

Tip: if a datum transformation needs a grid that is not installed locally, PROJ may need extra resources. The manpage documents optional remote grids activated with PROJ_NETWORK=ON. Turning that on changes the reproducibility and trust boundary of a batch job, so confirm whether the required grids are already local before relying on it, and never enable network access casually for sensitive or offline processing.

8. Diagnose a bad result without guessing

Use -v for non-essential diagnostic information, repeated as -vv or -vvv for more detail. Diagnostics go to standard error while transformed records stay on standard output, so the two streams never mix:

$ printf '12 55\n' | cct -v -z 0 -t 0 +proj=utm +zone=32 +ellps=GRS80 > result.txt
$ printf 'status=%s\n' "$?"
status=0

When a result looks wrong, check in this order:

A successful exit status only says the command completed; it does not certify that your coordinate metadata was correct.

Checkpoint: You can reproduce the result from a saved input file and the exact operator specification, with no reliance on an interactive prompt or an undocumented default.

Done means