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
[WIP] 4D API cs2cs style #731
Conversation
Neat - we now have (to the extent test coverage allows) confirmed the orthogonality of this PR to the cs2cs and pj_transform sub-systems. Next step will be to construct test data, to confirm (or probably rather reject) the hypothesis that the code does what it's supposed to. Despite just having to follow the recipe by @kbevers from #700, I doubt that I have managed to get everyting right at first shot. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few comments. Seems like it's going in the right direction.
src/pj_fwd.c
Outdated
coo.lp = lp; | ||
|
||
if (P->left==PJ_IO_UNITS_CARTESIAN) { | ||
/* Do we really support gridshifts on cartesian input? */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really support gridshifts on cartesian input?
Not in "classic PROJ" mode. This can only occur if when using the deformation operation which is not compatible with classic PROJ.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this will first really be an issue to decide on when we introduce global +inv
handling and/or a proj=cartesian
(or proj=geocent
in deprecated PROJ terms).
I'll leave the code in, but we will have to decide what we mean with this. I can imagine situations where it makes sense, but mostly for convenience (but then again, there is nothing in this PR which cannot be handled by properly constructed pipelines - it is by and large a matter of improving backward compatibility by providing some convenience functionality, for interoperability between new code and old parameter file systems)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(but then again, there is nothing in this PR which cannot be handled by properly constructed pipelines - it is by and large a matter of improving backward compatibility by providing some convenience functionality, for interoperability between new code and old parameter file systems)
That's my point. Keep it as simple as possible and don't open the door too much for new ideas based on the classic PROJ.4 idioms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I get your point now, and I fully agree, so I will remove that chunk of code
src/PJ_helmert.c
Outdated
/* Support the classic PROJ towgs84 parameter, but allow later overrides.*/ | ||
/* Note that if towgs84 is specified, the datum_params array is set up */ | ||
/* for us automagically by the pj_datum_set call in pj_init_ctx */ | ||
if (pj_param (P->ctx, P->params, "ttowgs84").i) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pj_param_exists?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it makes sense here, and we could redesign the entire parameter handling in this function to use pj_param_exists
, which I think we should do, but not now. Changed this instance, because it is different in having its work already done by a friend up in pj_datum_set
src/pj_fwd.c
Outdated
coo.xyz.x = P->fr_meter * (coo.xyz.x + P->x0); | ||
coo.xyz.y = P->fr_meter * (coo.xyz.y + P->y0); | ||
coo.xyz.z = P->vfr_meter * (coo.xyz.z + P->z0); | ||
if (P->right==PJ_IO_UNITS_CARTESIAN) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in pj_inv
you define INPUT_UNITS
and OUTPUT_UNITS
. Doing that here as well will make it easier to read the code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and I will do that. I managed to do the mental math necessary in the forward case, without having this translation/reminder, whereas when having to do the same thing backwards and in high heels, in the inverse case, I really needed these persistent pings
src/pj_fwd.c
Outdated
coo.lpz.lam = adjlon(coo.lpz.lam); | ||
|
||
if (P->vgridshift) | ||
coo = proj_trans (P->vgridshift, -1, coo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use PJ_INV
here. And many other places in the code below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't agree. PJ_INV/PJ_FWD
were introduced for your convenience. I actually find it quite a lot more readable to have negative mean backward, and positive mean forward.
And since there is more than enough of layered negations in this code, I really prefer the concise to the verbose here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we will just have to agree to disagree here :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds very agreeable :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That being said, if you find it unreadable, I would not mind switching to PJ_FWD/PJ_INV
once we have established that the material works as intended (last step before merging).
It's just that I find the PJ_FWD/PJ_INV
convention less readable than the mathematical convention of +1/-1, and this stuff is pretty obnoxious already.
src/pj_fwd.c
Outdated
if (P->vgridshift) | ||
coo = proj_trans (P->vgridshift, -1, coo); | ||
if (P->hgridshift) | ||
coo = proj_trans (P->hgridshift, 1, coo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PJ_FWD
here and below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(ibid.)
src/proj_4D_api.c
Outdated
p = pj_param_exists (P->params, "axis"); | ||
/* p->param is a *string array*, not a *pointer to char*, */ | ||
/* so we do not check here and below whether it is non-null */ | ||
if (p && strcmp ("enu", p->param)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point I believe P->axis
can be expected to contain valid data if it is set. So no need for the strcmp
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The strcmp returns true (i.e. non-null) if *(p->param) != "enu", so it's a way to avoid calling axisswap if the data are already in the expected order.
I agree that the data must be valid at this point, so this is not a validation, just a cheap optimization, which may deserve a comment for explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah of course. Makes sense.
These tests are mostly based on the same material as those in | ||
more_builtins.gie, since we are testing the same kinds of things, | ||
but provided through a different interface. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also contain a bunch of tests using the epsg init-file. Test data is easy to prepare with cs2cs. I can come up with some suggestions if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be nice. What is really needed (and will probably cause hiccups) is cases where more than one of the cs2cs style options are used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll merge #705 s soon as all the tests pass. Sync your branch with master and you'll have a bunch of test material in test/gigs/
. I think that should be enough for this purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have sync'ed, but currently drowning in all the "almost OK" tests. I think there is a bug in the interpretation of the GIGS tolerance parameter.
If I understand @micahcochran's comments correctly, they are considered to be max absolute coordinatewise deviations (i.e. in decimal degrees for latitude/longitude pairs), whereas in the .gie
version, they are translated into "same numerical value but interpreted as having a meter unit".
This makes a.o. all NADCON tests fail, because 1e-7 m (0.0001 mm) is much less than 1e-7 degree (1 cm at the Equator), at least if you are more than a few minutes of arc away from the poles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which tests fail? There's a few of them that doesn't work in the current python setup as well. It might be that it is only those tests that fail, in which case you can disregard them, since the problems are not related to the work you are doing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes pj_fwd/pj_inv work the same way as pj_transform: omitting dual identical helmert shifts, so it doesn't really change the test - it rewrites the test in order to act identically to the json case.
I understand why you've done it, I just don't think it is going to work. The end goal of this PR is, amongst other things, to be able to put to epsg
CRS definitions into proj_create_crs_to_crs()
and get correct results when using the resulting PJ
in transformations. While your solution fixes the test, it doesn't fix the transformation in the test when used in the wild (in the 4D API).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that context, please note that I have merged your previous PR over at busstoptaktik/PROJ.4.
Duly noted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The end goal of this PR is, amongst other things, to be able to put to epsg CRS definitions into
proj_create_crs_to_crs()
and get correct results when using the resulting PJ in transformations
proj_create_crs_to_crs()
is a much different situation:
First, we do not necessarily need this, if we can live with the time and slight precision penalty: For typical crs_to_crs uses we do just one half-roundtrip, and do not accumulate helmert imprecision.
Second, we can implement the pj_transform
trick as an optimization in a much simpler way than in the generic pipeline
case: Here we know we have a one input, one output situation, and if both are from epsg definitions we can check whether the towgs84=...
incantations are identical and bypass Helmert by setting the towgs84=0.0.0
thingy at the proj=pipeline
level.
This can be economically implemented by first changing
strcpy(buffer, "+proj=pipeline +step +init=");
to
strcpy(buffer, "+proj=pipeline +towgs84=0,0,0 +step +init=");
Then going on with the setup, but adding some 15 lines of code, checking the case of identical Helmert parameters (instantiate, check), and then finally:
if (helmerts_not_identical)
return P;
proj_destroy (P);
t = strstr (buffer, "towgs84");
strcpy (t, "tonight"); /* or xxxxxxx or whatever fits */
P = proj_create(ctx, buffer);
return P;
Simpler and more localized than solving the general problem in pj_pipeline
So, by and large, see the handheld introduction of towgs84=0,0,0 in the *.gie
files as a temporary exercise, helping pinpoint some cases where we can do somewhat mechanically introduced optimizations in a comming (version 6-ish) early version of a late binding EPSG implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, that works for me. Do you want to make that change to proj_create_crs_to_crs
? It fits nicely within this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but I'm offline until Monday, so not until then.
src/pj_fwd.c
Outdated
@@ -54,18 +54,46 @@ PJ_COORD pj_fwd_prepare (PJ *P, PJ_COORD coo) { | |||
if (lp.phi < -M_HALFPI) | |||
lp.phi = -M_HALFPI; | |||
|
|||
|
|||
coo.lp = lp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May I suggest calling it coord
instead. I think it reads better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
coo
has been conventionally used here, there, and everywhere:
grep coo *.c *.h | grep -v coord | wc -l
gives 74 hits.
coord
is used in methods and type names (PJ_COORD
, proj_coord ()
, etc.) - coo
is used for an otherwise unspecific instance, intentionally modelled after the equivalent unspecificness of the versatile variable foo
of foo, bar, baz
fame.
coord
is used as an instance name a few places in proj_4D_api.c
and gie.c
- I would be less reluctant renaming these instances to coo
, than changing the already widespread coo
idiom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
coo has been conventionally used here, there, and everywhere
I know, but I don't like the convention. It somehow made sense previously when coo
was also used in the now gone PJ_OBS
where a short name was justified. Since that no longer exists I think it is perfectly reasonable to use a more descriptive convention.
I would also object to using non-descriptive names like foo
and bar
in anything other than example code so I am not buying that argument.
Anyway, my point is just that coord
makes more sense to readers unfamiliar with the code-base as a whole. I am not suggesting that the existing occurences of coo
be replaced now, I just wanted to raise a flag here since it has been bothering me for a while. Do what you feel is best - ultimately it is in the nitpicking department!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kbevers: Since you dislike the convention, I think we should consider reconsidering the convention(s).
From classic PROJ/Gerald, we have P
for the generic PJ
object. From 3D-PROJ/Frank, we have ctx
for the generic PJ_CONTEXT
object. So apparently, the smaller the object, the longer the name.
With that in mind, it actually makes quite a lot of sense to switch coo
(same length as ctx
, but smaller) to your preferred coord
(longer and smaller than ctx
).
But if that's to be done I suggest we do it consequently, and reserve a rainy summer day, in time for 5.1.0, for a sweep-over of the entire code base, switching coo
to coord
- and collecting a list of other paper cuts/minor annoyances to be remedied at the same occasion.
I'll buy the beer, and open the issue for discussion - you bring the stamina, and I'll try to follow your typing pace :-)
Regarding test 5208 (and possibly others). The problem is due to prime meridian not being adjusted correctly in the inverse operation, as examplified here:
The forward operation is handled correctly because this
happens in |
------------------------------------------------------------------------------- | ||
tolerance 15 cm # lax tolerance due to widespread bad egm96 file | ||
accept 12.5 55.5 0 | ||
expect 12.5 55.5 -36.0213 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With cs2cs
I get the following output:
λ echo 55.5 12.5 0|bin\cs2cs.exe -f %.2f +proj=latlong +to +proj=latlong +geoidgrids=egm96_15.gtx +axis=neu +ellps=GRS80
12.50 55.50 42.37
C:\dev\build\ninjaproj
λ echo 55.5 12.5 0|bin\cs2cs.exe -f %.2f +proj=latlong +geoidgrids=egm96_15.gtx +axis=neu +ellps=GRS80 +to +proj=latlong
12.50 55.50 36.02
gie returns:
-----
FAILURE in 4D-API_cs2cs-style.gie(94):
expected: 12.5 55.5 -36.0213
got: 12.500000000 55.500000000 42.372592926
deviation: 78393.893 mm, expected: 150.000 mm
which corresponds to the first call to cs2cs
above. I guess this all comes down to what we mean by +axis
. Does +axis
describe the ordering of the input or the output coordinates?
Seems like this is getting close to the finish line. I still get errors when running the 5201.gie test. I guess it is not run on CI yet. The problem is related to +proj=geocent, which at the moment is not taken care of in the cs2cs emulation. I don't think it is too difficult to take care of. |
Parameters such as towgs84, nadgrids and geoidgrids was previously only handled by pj_transform(). This commit add a compatibility layer in proj_create() by calling the pj_cs2cs_emulation_setup() function. This function sets up a handful of predefined transformation objects on the PJ object that is being created. Each of these transformation objects are related to the cs2cs-style parameters we are trying to emulate in the 4D API. That is, if the +towgs84 parameters is used we create P->helmert with the parameters specified in +towgs84. Similarly for +axis, +nadgrids and +geoidgrids. When these transformation objects exists we use them in the prepare and finalize functions in pj_fwd/ pj_inv. If no cs2cs-style parametes are specified we skip those parts of the prepare and finalizing steps. Co-authored-by:Thomas Knudsen <thokn@sdfe.dk> Co-authored-by:Kristian Evers <kristianevers@gmail.com>
The GIGS tests that are known to work are added to the CMake test setup. The GIGS gie files have been auto-translated from the existing json-files and some corrections to tolerances have been necessary since gie uses different norms than GIGS specify. The GIGS tolerances are specified as the infinity norm of angular coordinates, whereas gie uses the actual distances between calculated and expected coordinates (using geodesics). In a few tests +towgs84 is overriden from the EPSG inits to avoid creeping numerical inaccuracy in roundtrips. Co-authored-by: Thomas Knudsen <thokn@sdfe.dk> Co-authored-by: Kristian Evers <kristianevers@gmail.com>
This PR is in response to issue #700. It is WIP and currently untested, but I push the PR in order to check (or actually re-establish) orthogonality to pj_transform and cs2cs.
UPDATE: Now very much tested and very little WIP. Getting ready for merging.