Quick start for C API usage
This is a short introduction to the PROJ C API. In the following section we create two simple programs that illustrate how to transform points between two different coordinate systems, and how to convert between projected and geodetic (geographic) coordinates for a single coordinate system. Explanations for individual code sniplets and the full programs are provided.
See the following sections for more in-depth descriptions of different parts of the PROJ API or consult the API reference for specifics.
Before the PROJ API can be used it is necessary to include the proj.h
header
file. Here stdio.h
is also included so we can print some text to the screen:
#include <proj.h>
#include <stdio.h>
Let's declare a few variables that'll be used later in the program. Each variable will be discussed below. See the reference for more info on data types.
PJ_CONTEXT *C;
PJ *P;
PJ *norm;
PJ_COORD a, b;
For use in multi-threaded programs the PJ_CONTEXT
threading-context
is used. In this particular example it is not needed, but for the sake of
completeness we demonstrate its use here.
C = proj_context_create();
Next we create the PJ
transformation object P
with the function
proj_create_crs_to_crs()
.
P = proj_create_crs_to_crs(
C, "EPSG:4326", "+proj=utm +zone=32 +datum=WGS84", /* or EPSG:32632 */
NULL);
if (0 == P) {
fprintf(stderr, "Failed to create transformation object.\n");
return 1;
}
Here we have set up a transformation from geographic coordinates to UTM zone
32N. In general, this is a transformation between two different coordinate reference systems
(one of which is here in geographic coordinates). The related function
proj_create()
can be used to set up transformations that are not available through
the PROJ database, for instance for converting geodetic coordinates to a custom definition
of a map projection.
proj_create_crs_to_crs()
takes as its arguments:
the threading context
C
created above,a string that describes the source coordinate reference system (CRS),
a string that describes the target CRS and
an optional description of the area of use.
It is recommended to create one threading context per thread used by the
program. This ensures that all PJ
objects created in the same
context will be sharing resources such as error-numbers and loaded grids.
If you are sure that P
will only be used by a single program thread, you
may pass NULL
for the threading context. This will assign the default
thread context to P
.
The strings for the source and target CRS may be any of:
PROJ strings, e.g.
+proj=longlat +datum=WGS84 +type=crs
,CRS identified by their code, e.g.
EPSG:4326
orurn:ogc:def:crs:EPSG::4326
, ora well-known text (WKT) string, e.g.:
GEOGCRS["WGS 84",
DATUM["World Geodetic System 1984",
ELLIPSOID["WGS 84",6378137,298.257223563,
LENGTHUNIT["metre",1]]],
PRIMEM["Greenwich",0,
ANGLEUNIT["degree",0.0174532925199433]],
CS[ellipsoidal,2],
AXIS["geodetic latitude (Lat)",north,
ORDER[1],
ANGLEUNIT["degree",0.0174532925199433]],
AXIS["geodetic longitude (Lon)",east,
ORDER[2],
ANGLEUNIT["degree",0.0174532925199433]],
USAGE[
SCOPE["unknown"],
AREA["World"],
BBOX[-90,-180,90,180]],
ID["EPSG",4326]]
Warning
The use of PROJ strings to describe a CRS is not recommended. One of the
main weaknesses of PROJ strings is their inability to describe a geodetic
datum, other than the few ones hardcoded in the +datum
parameter.
proj_create_crs_to_crs()
will return a pointer to a PJ
object, or a null pointer in the case of an error. The details of the error
can be retrieved using proj_context_errno()
. See Error handling
for further details.
Now that we have a normalized transformation object in P
, we can use it
with proj_trans()
to transform coordinates from the source CRS to the
target CRS, but first we will discuss the interpretation of coordinates.
By default, a PJ
transformation object accepts coordinates expressed
in the units and axis order of the source CRS, and returns transformed
coordinates in the units and axis order of the target CRS.
For most geographic CRS, the units will be in degrees. In rare cases, such as EPSG:4807 / NTF (Paris), this can be grads. For geographic CRS defined by the EPSG authority, the order of coordinates is latitude first, longitude second. When using a PROJ string, the order is the reverse; longitude first, latitude second.
For projected CRS, the units may vary (metre, us-foot, etc.). For projected CRS
defined by the EPSG authority, and with EAST / NORTH directions, the order
might be easting first, northing second, or the reverse. When using a PROJ
string, the order will be easting first, northing second, except if the
+axis
parameter modifies it.
If you prefer to work with a uniform axis order, regardless of the axis orders
mandated by the source and target CRS, you can use the
proj_normalize_for_visualization()
function.
proj_normalize_for_visualization()
takes a threading context and an
existing PJ
object, and generates from it a new PJ
that
accepts as input and returns as output coordinates using the traditional GIS
order. That is, longitude followed by latitude, optionally followed by
elevation and time for geographic CRS, and easting followed by northing for
most projected CRS.
if (0 == norm) {
fprintf(stderr, "Failed to normalize transformation object.\n");
return 1;
}
proj_destroy(P);
P = norm;
Next we create a PJ_COORD
coordinate object, using the function
proj_coord()
.
The following example creates a coordinate for 55°N 12°E (Copenhagen).
Because we have normalized the transformation object with
proj_normalize_for_visualization()
, the order of coordinates is
longitude followed by latitude, and the units are degrees.
a = proj_coord(12, 55, 0, 0);
Now we are ready to transform the coordinate into UTM zone 32, using the
function proj_trans()
.
b = proj_trans(P, PJ_FWD, a);
printf("easting: %.3f, northing: %.3f\n", b.enu.e, b.enu.n);
proj_trans()
takes as its arguments:
a
PJ
transformation object,a
PJ_DIRECTION
direction, andthe
PJ_COORD
coordinate to transform.
The direction argument can be one of:
PJ_FWD
-- "forward" transformation from source CRS to target CRS.PJ_IDENT
-- "identity", return the source coordinate unchanged.PJ_INV
-- "inverse" transformation from target CRS to source CRS.
It returns the new transformed PJ_COORD
coordinate.
We can perform the transformation in reverse (from UTM zone 32 back to geographic) as follows:
b = proj_trans(P, PJ_INV, b);
printf("longitude: %g, latitude: %g\n", b.lp.lam, b.lp.phi);
Before ending the program, we need to release the memory allocated to our objects:
proj_destroy(P);
proj_context_destroy(C); /* may be omitted in the single threaded case */
A complete compilable version of the example code can be seen below:
1#include <proj.h>
2#include <stdio.h>
3
4int main(void) {
5 PJ_CONTEXT *C;
6 PJ *P;
7 PJ *norm;
8 PJ_COORD a, b;
9
10 /* or you may set C=PJ_DEFAULT_CTX if you are sure you will */
11 /* use PJ objects from only one thread */
12 C = proj_context_create();
13
14 P = proj_create_crs_to_crs(
15 C, "EPSG:4326", "+proj=utm +zone=32 +datum=WGS84", /* or EPSG:32632 */
16 NULL);
17
18 if (0 == P) {
19 fprintf(stderr, "Failed to create transformation object.\n");
20 return 1;
21 }
22
23 /* This will ensure that the order of coordinates for the input CRS */
24 /* will be longitude, latitude, whereas EPSG:4326 mandates latitude, */
25 /* longitude */
26 norm = proj_normalize_for_visualization(C, P);
27 if (0 == norm) {
28 fprintf(stderr, "Failed to normalize transformation object.\n");
29 return 1;
30 }
31 proj_destroy(P);
32 P = norm;
33
34 /* a coordinate union representing Copenhagen: 55d N, 12d E */
35 /* Given that we have used proj_normalize_for_visualization(), the order */
36 /* of coordinates is longitude, latitude, and values are expressed in */
37 /* degrees. */
38 a = proj_coord(12, 55, 0, 0);
39
40 /* transform to UTM zone 32, then back to geographical */
41 b = proj_trans(P, PJ_FWD, a);
42 printf("easting: %.3f, northing: %.3f\n", b.enu.e, b.enu.n);
43
44 b = proj_trans(P, PJ_INV, b);
45 printf("longitude: %g, latitude: %g\n", b.lp.lam, b.lp.phi);
46
47 /* Clean up */
48 proj_destroy(P);
49 proj_context_destroy(C); /* may be omitted in the single threaded case */
50 return 0;
51}
The following example illustrates how to convert between a CRS and geodetic coordinates for that CRS.
1#include <math.h>
2#include <proj.h>
3#include <stdio.h>
4
5int main(void) {
6
7 /* Create the context. */
8 /* You may set C=PJ_DEFAULT_CTX if you are sure you will */
9 /* use PJ objects from only one thread */
10 PJ_CONTEXT *C = proj_context_create();
11
12 /* Create a projection. */
13 PJ *P = proj_create(C, "+proj=utm +zone=32 +datum=WGS84 +type=crs");
14
15 if (0 == P) {
16 fprintf(stderr, "Failed to create transformation object.\n");
17 return 1;
18 }
19
20 /* Get the geodetic CRS for that projection. */
21 PJ *G = proj_crs_get_geodetic_crs(C, P);
22
23 /* Create the transform from geodetic to projected coordinates.*/
24 PJ_AREA *A = NULL;
25 const char *const *options = NULL;
26 PJ *G2P = proj_create_crs_to_crs_from_pj(C, G, P, A, options);
27
28 /* Longitude and latitude of Copenhagen, in degrees. */
29 double lon = 12.0, lat = 55.0;
30
31 /* Prepare the input */
32 PJ_COORD c_in;
33 c_in.lpzt.z = 0.0;
34 c_in.lpzt.t = HUGE_VAL; // important only for time-dependent projections
35 c_in.lp.lam = lon;
36 c_in.lp.phi = lat;
37 printf("Input longitude: %g, latitude: %g (degrees)\n", c_in.lp.lam,
38 c_in.lp.phi);
39
40 /* Compute easting and northing */
41 PJ_COORD c_out = proj_trans(G2P, PJ_FWD, c_in);
42 printf("Output easting: %g, northing: %g (meters)\n", c_out.enu.e,
43 c_out.enu.n);
44
45 /* Apply the inverse transform */
46 PJ_COORD c_inv = proj_trans(G2P, PJ_INV, c_out);
47 printf("Inverse applied. Longitude: %g, latitude: %g (degrees)\n",
48 c_inv.lp.lam, c_inv.lp.phi);
49
50 /* Clean up */
51 proj_destroy(P);
52 proj_destroy(G);
53 proj_destroy(G2P);
54 proj_context_destroy(C); /* may be omitted in the single threaded case */
55 return 0;
56}