# PROJ puts projections, datum grids and seven CLI tools in one C++ package

> PROJ is a C++ library and set of command line utilities that move coordinates between coordinate reference systems, from a flat map projection to a datum shift. Its data files live outside the binary, and the project's own container image puts them somewhere the install documentation does not name.

**OSGeo/PROJ** — PROJ - Cartographic Projections and Coordinate Transformations Library

- Repository: https://github.com/OSGeo/PROJ
- Website: https://proj.org
- Stars: 2,030 · Forks: 924
- Language: C++
- License: NOASSERTION
- Published: 2026-10-13 · Updated: 2026-10-13 · Language: en
- Canonical page: https://hysenlabs.com/projects/osgeo-proj

## One library, two different coordinate jobs

PROJ converts coordinates from one coordinate reference system to another, and the README splits that into two different problems. Cartographic projection flattens geodetic coordinates onto a map plane, while geodetic transformation moves a point between reference systems, where the numbers depend on published parameters and on grid files that ship separately. One package carries both jobs, so code that projects a point for a printed map and code that carries the same point between three dimensional reference systems link against the same library and the same build.

The top-level listing separates the library code in `src/` from its headers, its manual pages, its data set and its test suite. The licence field reads Other, a custom licence GitHub cannot classify, and points readers to a LICENSE file that the listing does not contain, where the entry actually present is `COPYING`.

## Seven shipped utilities with overlapping jobs

The package installs 7 command line programs, and they overlap enough that picking the wrong one costs an afternoon.

`proj` handles cartographic projection of geodetic coordinates, the plain map case.

`cs2cs` transforms from one CRS to another, which is the job the project is named for.

`cct` covers generic coordinate conversions and transformations, for work that matches neither of the first two.

`geod` leaves coordinates behind entirely and does geodesic great circle computations instead.

`projinfo` answers questions about geodetic objects and candidate coordinate operations without converting anything.

`gie`, the Geospatial Integrity Investigation Environment, is the verification tool of the set.

`projsync` is the odd one out: it synchronizes the datum and transformation support data rather than computing with it.

Detail on each program is deferred to the link to https://proj.org/apps, which leaves the list above as the full inventory.

## Grids ship separately from the code tarball

The software arrives as a compressed tar file named `proj-x.y.z.tar.gz`, where x is the major number, y the minor and z the patch.

Datum conversion grids and PROJ parameter files arrive in a different package, `proj-data-x.y.zip`, versioned with a major and a minor number only. That difference is worth pausing on before any upgrade, because the data package carries fewer version components than the code package, so two file names that look matched will not tell you whether a grid set belongs to a given build.

Resource files belong in `PROJ_LIB`, which defaults to /usr/local/share/proj on UNIX-based systems and can be pointed elsewhere, while on Windows you have to define it yourself because no default is set for you. The alternative to a local unpack is https://cdn.proj.org/, where a network-enabled build fetches any resource file it does not find locally.

## No install command anywhere, only two links

For installation, the checkout gives no shell command at all. It points at the Installation page of the official documentation and adds that `docs/source/install.rst` may be more up to date for builds on the master branch.

Between them the two links tell you where the instructions live, not what to type, and the default branch is master, so a plain clone of it floats ahead of any published version. Those pages also point at https://proj.org/download.html for the tarball and the grid package, and the contents of the data package are described in the PROJ-data GitHub repository.

For citation the repository keeps a `CITATION` file and carries a Zenodo DOI badge, 10.5281/zenodo.5884394.

If you want runnable commands today, the `Dockerfile` at the repository root is the only place in the checkout holding a complete build sequence.

How current that work is: the last push was on 2026-10-03, and the recent tags are 9.9.0 from 2026-09-15, 9.8.1 from 2026-04-10 and 9.8.0 from 2026-03-02, that last one carrying a warning related to ETRS89.

## The image installs data outside the documented default path

The Dockerfile at the repository root is a two stage build, and both stages start from `ubuntu:24.04`. The builder installs build-essential, cmake, wget, unzip, zlib1g-dev, libsqlite3-dev, sqlite3, libcurl4-gnutls-dev and libtiff-dev, then configures with an install prefix of /usr and testing switched off:

```dockerfile
FROM ubuntu:24.04 AS builder

ARG DESTDIR="/build"
ARG PROJ_GITHUB_SHA
```

```bash
RUN cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -S /PROJ -B _build
RUN cmake --build _build -j $(nproc)
RUN cmake --install _build
```

The runner stage installs the runtime libraries (libsqlite3-0, libtiff6, libcurl4, libcurl3-gnutls, wget and ca-certificates) and copies four directories out of the builder:

```dockerfile
FROM ubuntu:24.04 AS runner
WORKDIR /usr/share/proj
RUN wget --no-verbose --mirror https://cdn.proj.org/ || true
```

```dockerfile
COPY --from=builder  /build/usr/share/proj/ /usr/share/proj/
COPY --from=builder  /build/usr/include/ /usr/include/
COPY --from=builder  /build/usr/bin/ /usr/bin/
COPY --from=builder  /build/usr/lib/ /usr/lib/
```

Now the contradiction. `PROJ_LIB` defaults to /usr/local/share/proj on UNIX-based systems, this image never sets it anywhere, and the copied data lands in /usr/share/proj.

The two only agree because the CMake prefix and the built-in default were picked to match each other, so rebuilding the same two stages against another prefix leaves nothing in the file to guide you. Windows runs into the same requirement by design, since PROJ_LIB has to be defined there however the library was built.

## A swallowed mirror failure and a disabled test suite

The mirror line ends in `|| true`, and those two characters are the most consequential thing in the file. A failed or partial download from cdn.proj.org leaves an image with an empty or thin data directory and no error at build time, and nothing later in the sequence counts the files that arrived.

The cleanup step that follows runs against whatever did land:

```dockerfile
RUN cd cdn.proj.org && rm -fv *.js *.css *.html favicon* && mv * .. && cd .. && rmdir cdn.proj.org
```

Testing is off by construction, since the builder passes `-DBUILD_TESTING=OFF`, and the runner copies only share, include, bin and lib out of the builder, so the repository's test/ directory stays behind in the build stage and never reaches the final image.

Verification inside that image rests on the `gie` binary with nothing beside it.

Continuous integration carries the same patchwork shape, since the top-level listing holds both .travis.yml and `.cirrus.yml.disabled`, one active by name and one active only as a disabled filename.

## Three routes for the data, three routes for the code

Data supply has three options and each one fails differently. Unpack the grid package into `PROJ_LIB` and every lookup stays local and offline.

Point a network-enabled build at https://cdn.proj.org/ and missing files arrive on demand, at the cost of a network round trip per missing resource and a host that can no longer resolve coordinates once the network is gone. Run `projsync` and you synchronize the datum and transformation support data against what is installed.

Reaching the library shows the same spread: the command line programs cover most work, while the checkout also ships C, C++ and observer API material under `examples/`.

`examples/EPSG_4326_to_32631.cpp` is the C++ example, named after the two coordinate reference system codes it converts between.

For C there is `examples/crs_to_geodetic.c`, and a second demo in the same directory drives the observer API.

Two data files come with them, `examples/s45b.pol` and examples/val_def.demo, both fed to the utilities as input.

Anyone after a flat map and nothing more should stay with `proj` and leave the datum side alone. Anyone whose accuracy claim rests on a transformation needs those grid files on disk before the numbers mean anything.

## Conclusion

PROJ earns its place for anyone who has to move coordinates between reference systems and can carry a data dependency next to the library. It is the wrong size of tool for a one-off map that a plotting helper already draws. Verify three things before trusting numbers from it: that PROJ_LIB points at a populated grid directory, that the proj-data package version matches the code you compiled, and whether the build can reach cdn.proj.org at runtime, because a network-enabled PROJ with no local grids keeps looking for files that never arrive.

## FAQ

### What does PROJ actually do, and who needs it?

It transforms coordinates from one coordinate reference system to another, which covers both cartographic projections and geodetic transformations between reference systems. It suits anyone mapping, geocoding or moving coordinates between systems who wants the same maths from a command line and from C or C++ code.

### Where does PROJ look for its datum grid files on Linux?

In `PROJ_LIB`, which defaults to /usr/local/share/proj on UNIX-based systems and can be pointed at another directory. On Windows you have to define it yourself.

### Which PROJ command converts between two coordinate reference systems?

cs2cs transforms from one CRS to another, proj handles cartographic projection of geodetic coordinates, and cct covers generic coordinate conversions and transformations. projinfo answers queries about coordinate operations without converting anything.

### Can PROJ fetch its data files instead of installing them locally?

A network-enabled build automatically fetches resource files that are not present locally from https://cdn.proj.org/. The `projsync` utility synchronizes the datum and transformation support data separately.

### How do I build PROJ from a checkout?

The repository's own Dockerfile holds the sequence: CMake configure with -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF, then cmake --build, then cmake --install. For other setups the project points at its Installation page rather than giving commands.

### What licence is PROJ distributed under?

The licence is recorded as Other, a custom licence GitHub cannot classify, with a pointer to a LICENSE file. The top-level listing of the repository shows `COPYING` rather than a LICENSE entry.

## Sources

- [Issues](https://github.com/OSGeo/PROJ/issues)
- [OSGeo/PROJ on GitHub](https://github.com/OSGeo/PROJ)
- [Project website](https://proj.org)
- [README](https://github.com/OSGeo/PROJ/blob/master/README.md)
- [Releases](https://github.com/OSGeo/PROJ/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/osgeo-proj
