Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new option to proj_create_crs_to_crs_from_pj method to force +over on transformation operations #2914

Merged
merged 9 commits into from
Nov 12, 2021
3 changes: 3 additions & 0 deletions docs/source/development/reference/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ paragraph for more details.
- ALLOW_BALLPARK=YES/NO: can be set to NO to disallow the use of
:term:`Ballpark transformation` in the candidate coordinate operations.

- FORCE_OVER=YES/NO: can be set to YES to force the +over flag on the transformation
returned by this function.

.. doxygenfunction:: proj_normalize_for_visualization
:project: doxygen_api

Expand Down
14 changes: 13 additions & 1 deletion src/4D_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1843,6 +1843,7 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons
const char* authority = nullptr;
double accuracy = -1;
bool allowBallparkTransformations = true;
bool forceOver = false;
for (auto iter = options; iter && iter[0]; ++iter) {
const char *value;
if ((value = getOptionValue(*iter, "AUTHORITY="))) {
Expand All @@ -1859,7 +1860,13 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons
"Invalid value for ALLOW_BALLPARK option.");
return nullptr;
}
} else {
}
else if ((value = getOptionValue(*iter, "FORCE_OVER="))) {
if (ci_equal(value, "yes")) {
forceOver = true;
}
}
else {
std::string msg("Unknown option :");
msg += *iter;
ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, msg.c_str());
Expand Down Expand Up @@ -1913,18 +1920,23 @@ PJ *proj_create_crs_to_crs_from_pj (PJ_CONTEXT *ctx, const PJ *source_crs, cons
return nullptr;
}

ctx->forceOver = forceOver;

PJ* P = proj_list_get(ctx, op_list, 0);
assert(P);

if( P == nullptr || op_count == 1 || (area && area->bbox_set) ||
proj_get_type(source_crs) == PJ_TYPE_GEOCENTRIC_CRS ||
proj_get_type(target_crs) == PJ_TYPE_GEOCENTRIC_CRS ) {
proj_list_destroy(op_list);
ctx->forceOver = false;
return P;
}

auto preparedOpList = pj_create_prepared_operations(ctx, source_crs, target_crs,
op_list);

ctx->forceOver = false;
proj_list_destroy(op_list);

if( preparedOpList.empty() )
Expand Down
3 changes: 3 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,9 @@ pj_init_ctx_with_allow_init_epsg(PJ_CONTEXT *ctx, int argc, char **argv, int all

/* Over-ranging flag */
PIN->over = pj_param(ctx, start, "bover").i;
if (ctx->forceOver) {
PIN->over = ctx->forceOver;
}

/* Vertical datum geoid grids */
PIN->has_geoid_vgrids = pj_param(ctx, start, "tgeoidgrids").i;
Expand Down
1 change: 1 addition & 0 deletions src/proj_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ struct pj_ctx{
void *logger_app_data = nullptr;
struct projCppContext* cpp_context = nullptr; /* internal context for C++ code */
int use_proj4_init_rules = -1; /* -1 = unknown, 0 = no, 1 = yes */
bool forceOver = false;
int epsg_file_exists = -1; /* -1 = unknown, 0 = no, 1 = yes */
std::string ca_bundle_path{};

Expand Down
181 changes: 181 additions & 0 deletions test/unit/gie_self_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -890,4 +890,185 @@ TEST(gie, proj_trans_generic) {
proj_destroy(P);
}

TEST(gie, proj_create_crs_to_crs_from_pj_force_over) {

PJ_CONTEXT* ctx;

ctx = proj_context_create();
ASSERT_TRUE(ctx != nullptr);

auto epsg27700 = proj_create(ctx, "EPSG:27700");
rouault marked this conversation as resolved.
Show resolved Hide resolved
ASSERT_TRUE(epsg27700 != nullptr);

auto epsg4326 = proj_create(ctx, "EPSG:4326");
ASSERT_TRUE(epsg4326 != nullptr);

auto epsg3857 = proj_create(ctx, "EPSG:3857");
ASSERT_TRUE(epsg3857 != nullptr);

{
const char* const options[] = { "FORCE_OVER=YES", nullptr };
auto P = proj_create_crs_to_crs_from_pj(ctx, epsg4326, epsg3857, nullptr, options);
ASSERT_TRUE(P != nullptr);
ASSERT_TRUE(P->over);
PJ_COORD input;
PJ_COORD input_over;

//Test a point along the equator.
//The same point, but in two different representations.
input.xyz.x = 0; // Lat in deg
input.xyz.y = 140; // Long in deg
input.xyz.z = 0;

input_over.xyz.x = 0; // Lat in deg
input_over.xyz.y = -220; // Long in deg
input_over.xyz.z = 0;

auto output = proj_trans(P, PJ_FWD, input);
auto output_over = proj_trans(P, PJ_FWD, input_over);

auto input_inv = proj_trans(P, PJ_INV, output);
auto input_over_inv = proj_trans(P, PJ_INV, output_over);

//Web Mercator x's between 0 and 180 longitude come out positive.
//But when forcing the over flag, the -220 calculation makes it flip.
EXPECT_GT(output.xyz.x, 0);
EXPECT_LT(output_over.xyz.x, 0);

EXPECT_NEAR(output.xyz.x, 15584728.711058298, 1e-8);
EXPECT_NEAR(output_over.xyz.x, -24490287.974520184, 1e-8);

//The distance from 140 to 180 and -220 to -180 should be pretty much the same.
auto dx_o = fabs(output.xyz.x - 20037508.342789244);
auto dx_over = fabs(output_over.xyz.x + 20037508.342789244);
auto dx = fabs(dx_o - dx_over);

EXPECT_NEAR(dx, 0, 1e-8);

//Check the inverse operations get us back close to our original input values.
EXPECT_NEAR(input.xyz.x, input_inv.xyz.x, 1e-8);
EXPECT_NEAR(input.xyz.y, input_inv.xyz.y, 1e-8);
EXPECT_NEAR(input_over.xyz.x, input_over_inv.xyz.x, 1e-8);
EXPECT_NEAR(input_over.xyz.y, input_over_inv.xyz.y, 1e-8);

proj_destroy(P);
}

{
//Try again with force over set to anything but YES to verify it didn't do anything.
const char* const options[] = { "FORCE_OVER=NO", nullptr };
auto P = proj_create_crs_to_crs_from_pj(ctx, epsg4326, epsg3857, nullptr, options);
ASSERT_TRUE(P != nullptr);
ASSERT_FALSE(P->over);
PJ_COORD input;
PJ_COORD input_notOver;

input.xyz.x = 0; // Lat in deg
input.xyz.y = 140; // Long in deg
input.xyz.z = 0;

input_notOver.xyz.x = 0; // Lat in deg
input_notOver.xyz.y = -220; // Long in deg
input_notOver.xyz.z = 0;

auto output = proj_trans(P, PJ_FWD, input);
auto output_notOver = proj_trans(P, PJ_FWD, input_notOver);

EXPECT_GT(output.xyz.x, 0);
EXPECT_GT(output_notOver.xyz.x, 0);

EXPECT_NEAR(output.xyz.x, 15584728.711058298, 1e-8);
EXPECT_NEAR(output_notOver.xyz.x, 15584728.711058298, 1e-8);

proj_destroy(P);
}

{
//Try again with no options to verify it didn't do anything.
auto P = proj_create_crs_to_crs_from_pj(ctx, epsg4326, epsg3857, nullptr, nullptr);
ASSERT_TRUE(P != nullptr);
ASSERT_FALSE(P->over);
PJ_COORD input;
PJ_COORD input_notOver;

input.xyz.x = 0; // Lat in deg
input.xyz.y = 140; // Long in deg
input.xyz.z = 0;

input_notOver.xyz.x = 0; // Lat in deg
input_notOver.xyz.y = -220; // Long in deg
input_notOver.xyz.z = 0;

auto output = proj_trans(P, PJ_FWD, input);
auto output_notOver = proj_trans(P, PJ_FWD, input_notOver);

EXPECT_GT(output.xyz.x, 0);
EXPECT_GT(output_notOver.xyz.x, 0);

EXPECT_NEAR(output.xyz.x, 15584728.711058298, 1e-8);
EXPECT_NEAR(output_notOver.xyz.x, 15584728.711058298, 1e-8);

proj_destroy(P);
}

{
//EPSG:4326 -> EPSG:27700 has more than one coordinate operation candidate.
const char* const options[] = { "FORCE_OVER=YES", nullptr };
auto P = proj_create_crs_to_crs_from_pj(ctx, epsg4326, epsg27700, nullptr, options);
ASSERT_TRUE(P != nullptr);
ASSERT_TRUE(P->over);
PJ_COORD input;
PJ_COORD input_over;

input.xyz.x = 0; // Lat in deg
input.xyz.y = 140; // Long in deg
input.xyz.z = 0;

input_over.xyz.x = 0; // Lat in deg
input_over.xyz.y = -220; // Long in deg
input_over.xyz.z = 0;

auto output = proj_trans(P, PJ_FWD, input);
auto output_over = proj_trans(P, PJ_FWD, input_over);

//Doesn't actually change the result for this tmerc transformation.
EXPECT_NEAR(output.xyz.x, 4980122.749364435, 1e-8);
EXPECT_NEAR(output.xyz.y, 14467212.882603768, 1e-8);
EXPECT_NEAR(output_over.xyz.x, 4980122.749364435, 1e-8);
EXPECT_NEAR(output_over.xyz.y, 14467212.882603768, 1e-8);

proj_destroy(P);
}

{
//Negative test for 27700.
const char* const options[] = { "FORCE_OVER=NO", nullptr };
auto P = proj_create_crs_to_crs_from_pj(ctx, epsg4326, epsg27700, nullptr, options);
ASSERT_TRUE(P != nullptr);
ASSERT_FALSE(P->over);
PJ_COORD input;
PJ_COORD input_over;

input.xyz.x = 0; // Lat in deg
input.xyz.y = 140; // Long in deg
input.xyz.z = 0;

input_over.xyz.x = 0; // Lat in deg
input_over.xyz.y = -220; // Long in deg
input_over.xyz.z = 0;

auto output = proj_trans(P, PJ_FWD, input);
auto output_over = proj_trans(P, PJ_FWD, input_over);

EXPECT_NEAR(output.xyz.x, 4980122.749364435, 1e-8);
EXPECT_NEAR(output.xyz.y, 14467212.882603768, 1e-8);
EXPECT_NEAR(output_over.xyz.x, 4980122.749364435, 1e-8);
EXPECT_NEAR(output_over.xyz.y, 14467212.882603768, 1e-8);

proj_destroy(P);
}

proj_context_destroy(ctx);
}

} // namespace