zerocukor287
zerocukor287

Reputation: 1050

C++ libproj, swap coordinates

I have a C++ program running on linux for years. It uses some coordinate transformation, we use the libproj (version 6.3.1) for this purpose.

Interestingly, one client gave the input parameter epsg:2180 then everything has blown up.

It seems like that for a few epsg transformations the output is swapped. Here are the commands I used to prove it:

First, get the projection string:

$ projinfo epsg:2180

PROJ.4 string:
+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs

If I use it with a sample coodinate (LatLon format) like below, I got the result as Easting first. (Basically the command is cs2cs epsg:4326 +to <projection string here> -f %.4f)

echo "55.12578267 16.77291767" | cs2cs epsg:4326 +to +proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs -f %.4f

Result that everyone expects:

358037.8085 809219.1725 0.0000

However, if I use the simple command:

echo "55.12578267 16.77291767" | cs2cs epsg:4326 epsg:2180 -f %.4f

Then the result is flipped (customer not happy):

809219.1725 358037.8085 0.0000

Problem doesn't stop here, in C++, I use the same library, with the same problem. The question is, how can I make sure that the Easting will be the first parameter?

I've already tried to use proj_normalize_for_visualization, as the documentation suggested that it would do a swap on the coordinates, but in reality it doesn't swap the result.

#include <cstdlib>
#include <cstdio>
#include <proj.h>
int main(int argc, char** argv)
{
    PJ_CONTEXT* projContext_m = proj_context_create();
    
    // hardcode epsg (in reality it is user defined)
    int epsg_p = 2180;
    
    char _buff[100];
    snprintf(_buff, sizeof (_buff), "EPSG:%d", epsg_p);
    PJ* projEPSG_m = proj_create_crs_to_crs(projContext_m,
                                            "EPSG:4326",
                                            _buff, NULL);

    if (projEPSG_m != nullptr)
    {   
        proj_normalize_for_visualization(projContext_m, projEPSG_m);
    }
    
    // fake input (in reality it is user defined)
    PJ_COORD input_p;
    input_p.xy.x = 55.12578267;
    input_p.xy.y = 16.77291767;
    
    PJ_COORD result = proj_trans(projEPSG_m, PJ_FWD, input_p);    // input_p is the coordinate we need to transform

    // we assume `result.x` is the Easting
    printf("Easting: %f, Northing %f", result.xy.x, result.xy.y);
    
    return 0;
}

Output:

Easting: 809219.172518, Northing 358037.808510

Expected:

Easting: 358037.808510, Northing 809219.172518

What should I do to enforce result.x is always an Easting?

Upvotes: 2

Views: 104

Answers (2)

Vladislav Zavjalov
Vladislav Zavjalov

Reputation: 26

I had the same problem and I solved it with proj_normalize_for_visualization().

  auto p1 = proj_create(ctx, name.c_str()); // create crs
  if (!p1) throw Err() << "Can't create libproj crs: " << name;

  // always north up
  auto p2 = proj_normalize_for_visualization(ctx, p1);
  if (p2) { proj_destroy(p1); p1 = p2; }

  // promote to 3d
  auto p3 = proj_crs_promote_to_3D(ctx, nullptr, p1);
  if (p3) { proj_destroy(p1); p1 = p3; }

Upvotes: 1

zerocukor287
zerocukor287

Reputation: 1050

TLDR;
PROJ4 format is not perfect, get JSON projection string with proj_as_projjson and check the key ["target_crs"]["coordinate_system"]["axis"][0]["direction"].

const char* projInfoJson = proj_as_projjson(projContext_m, projEPSG_m, NULL);

json::parse(projInfoJson)["target_crs"]["coordinate_system"]["axis"][0]["direction"] == "north";

After a few day debugging and documentation reading, it became clear that the PROJ4 format doesn't provide all the parameters.

That is the reason why cs2cs produced swapped results when using with only EPSGs, or with a projection string.

Also, a few EPSGs (including 2180) for historic/tradition reasons outputs the results in other than 'Easting first' manner.
Have a look at epsg's website and check the 'Coordinate System' part (emphasis mine):

Cartesian 2D CS. Axes: northing, easting (x,y). Orientations: north, east. UoM: m.

Unfortunately, there is no way to tell this information from a PROJ4 type projection string. There are other formats that provide more information.

So the solution is, get the JSON projection string, extract the axis info, and use a swap when the stars align.
Here is the code that does this:

#include <cstdlib>
#include <cstdio>
#include <proj.h>
int main(int argc, char** argv)
{
    PJ_CONTEXT* projContext_m = proj_context_create();
    
    // hardcode epsg (in reality it is user defined)
    int epsg_p = 2180;
    
    char _buff[100];
    snprintf(_buff, sizeof (_buff), "EPSG:%d", epsg_p);
    PJ* projEPSG_m = proj_create_crs_to_crs(projContext_m,
                                            "EPSG:4326",
                                            _buff, NULL);
// ----- here comes the change
    bool swapNeeded = false;
    if (projEPSG_m != nullptr)
    {   
        const char* projInfoJson = proj_as_projjson(projContext_m, projEPSG_m, NULL);
        nlohmann::json projInfo = nlohmann::json::parse(projInfoJson);
        swapNeeded = projInfo["target_crs"]["coordinate_system"]["axis"][0]["direction"] == "north";
    }

    // fake input (in reality it is user defined)
    PJ_COORD input_p;
    input_p.xy.x = 55.12578267;
    input_p.xy.y = 16.77291767;
    
    PJ_COORD result = proj_trans(projEPSG_m, PJ_FWD, input_p);    // input_p is the coordinate we need to transform
    if (swapNeeded) {
        std::swap(result.xy.x, result.xy.y);
    }

    // we assume `result.x` is the Easting
    printf("Easting: %f, Northing %f", result.xy.x, result.xy.y);
    
    return 0;
}

I used the nlohmann::json library for JSON parsing, but one might use other library to read those few characters.

Upvotes: 1

Related Questions