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 h3ToIj #102

Merged
merged 18 commits into from
Oct 3, 2018
Merged

Add h3ToIj #102

merged 18 commits into from
Oct 3, 2018

Conversation

isaacbrodsky
Copy link
Collaborator

@isaacbrodsky isaacbrodsky commented Jul 26, 2018

h3ToIj wraps the internal function h3ToIjk and produces a two-dimensional IJ coordinate system. It has the same limitations of h3ToIjk regarding cells too far away from the origin or across pentagonal distortion.

Do we want to make any changes to support evolving the CoordIJ system in the future?

Builds on #83 and relates to #99.

h3ToIj wraps the internal function h3ToIjk and produces a two-dimensional
IJ coordinate system. It has the same limitations of h3ToIjk regarding
cells too far away from the origin or across pentagonal distortion.
@coveralls
Copy link

coveralls commented Jul 26, 2018

Coverage Status

Coverage remained the same at 100.0% when pulling 8cd83bb on isaacbrodsky:h3-to-ij into dfcb55e on uber:master.

@nrabinowitz
Copy link
Collaborator

Looks good, just a few comments on documentation. I'm happy making this public if the behavior is well-defined. @dfellis or @sahrk might want to weigh in.

@@ -111,6 +120,48 @@ void h3Distance_kRing_assertions(H3Index h3) {

BEGIN_TESTS(h3ToIjk);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: h3ToIj now? Not actually sure this is shown anywhere.

@@ -111,6 +120,48 @@ void h3Distance_kRing_assertions(H3Index h3) {

BEGIN_TESTS(h3ToIjk);

H3Index bc1 = H3_INIT;
setH3Index(&bc1, 0, 15, 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Can you use clearer var names or add comments explaining what these are?

* @param origin An anchoring index for the ij coordinate system.
* @param index Index to find the coordinates of
* @param out ij coordinates of the index will be placed here on success
* @return 0 on success, or another value on failure.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain in the doc comment why you might get failure values?

Copy link
Collaborator

@dfellis dfellis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All comments are nits. Would prefer they're fixed but not blocking.

@@ -38,8 +39,11 @@ void h3Distance_identity_assertions(H3Index h3) {

t_assert(H3_EXPORT(h3Distance)(h3, h3) == 0, "distance to self is 0");

// Test that coordinates are as expected
CoordIJ ij;
t_assert(H3_EXPORT(h3ToIj)(h3, h3, &ij) == 0, "failed to get ij");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on this test doesn't make sense?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified that comment.

CoordIJK origin = {0};
t_assert(h3ToIjk(h3, h3, &origin) == 0, "got ijk for origin");
CoordIJ origin = {0};
t_assert(H3_EXPORT(h3ToIj)(h3, h3, &origin) == 0, "got ijk for origin");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Should say got ij for origin

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

t_assert(h3ToIjk(h3, offset, &ijk) == 0, "got ijk for destination");
CoordIJ ij = {0};
t_assert(H3_EXPORT(h3ToIj)(h3, offset, &ij) == 0,
"got ijk for destination");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nit here on ijk -> ij

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* Each axis is spaced 120 degrees apart.
*/
typedef struct {
int i; ///< i component
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Do we really need these inline comments?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dfellis they parallel the inline comments on components in CoordIJK, so consistency would argue for keeping them here (or changing them there). Putting them in CoordIJK was a coin-flip in favor of completeness, but I agree they're overkill.

@sahrk
Copy link

sahrk commented Aug 1, 2018

@isaacbrodsky asked awhile ago "Do we want to make any changes to support evolving the CoordIJ system in the future?"

You might want to specifically mention in the documentation that the result of h3ToIj for a specific pair of indexes is not guaranteed to be the same in future versions, unless you're really sure you want that to be the case.

Otherwise LGTM.

@isaacbrodsky
Copy link
Collaborator Author

Sorry I haven't had time to return to this PR.

Re: @sahrk

You might want to specifically mention in the documentation that the result of h3ToIj for a specific pair of indexes is not guaranteed to be the same in future versions, unless you're really sure you want that to be the case.

While I like the flexibility that gives me as a library developer, I'm concerned it might be confusing for library users. This also came up in #83 (comment), but most of the discussion there was about whether to expose h3ToIjk or h3ToIj. I'm interested in what others think of adding that caveat to the API.

@dfellis
Copy link
Collaborator

dfellis commented Aug 21, 2018

Sorry I haven't had time to return to this PR.

I've been on vacation until today so I didn't notice. ;)

Re: @sahrk

You might want to specifically mention in the documentation that the result of h3ToIj for a specific pair of indexes is not guaranteed to be the same in future versions, unless you're really sure you want that to be the case.

While I like the flexibility that gives me as a library developer, I'm concerned it might be confusing for library users. This also came up in #83 (comment), but most of the discussion there was about whether to expose h3ToIjk or h3ToIj. I'm interested in what others think of adding that caveat to the API.

I'm with @sahrk about noting that these values could change between versions of the code. As long as they're internally consistent and operations performed on them will work the same each time (eg h3ToIj(h3Index1, h3Index2,&ijVar); ijVar.i += 2; H3Index h3Index3 = ijToH3(&ijVar, h3Index1) will produce the same h3Index3) then the actual IJ values don't matter.

Copy link
Collaborator

@dfellis dfellis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only just realized that the reverse function, ijToH3 is missing.

@sahrk
Copy link

sahrk commented Aug 21, 2018

ijToH3 gets to the crux of my discomfort, an ij isn’t a global location in and of itself; the user has to hang on to metadata (the origin) to get back to H3. It almost makes me want to create a struct to wrapper each ij with its origin, but that sounds inconvenient/inefficient. It might be better if users were encouraged up front to think of their ij’s as primarily local planar coordinates, with limited extent. Then when they start moving around in ij space and wander into a singularity they will be fore-warned.

@dfellis
Copy link
Collaborator

dfellis commented Aug 21, 2018

Yes, but without something like that, this library would need to implement every kind of planar operation users desire (such as line tracing that's desired for more efficient polyfill operations). Having an "out" into a coordinate system will reduce the bloating of the API.

@sahrk
Copy link

sahrk commented Aug 21, 2018

Agreed @dfellis, I think the utility case is very convincing. I was just encouraging more of a warning label.

@nrabinowitz
Copy link
Collaborator

To throw in my two cents here:

  • If we are fairly certain that the coordinate system is unstable, then I think we should explicitly mark it experimentalH3ToIj or something. As a developer, I would expect that the output of h3ToIj(h3Index1, h3Index2, &ijVar) would be stable across minor version changes, and if we're not providing that guarantee then we should be very clear about it.
  • Supporting currently unreachable coordinate spaces would be a minor version bump as long as working pairs of indexes continued to work in the same way. That would be fine with a comment.

As long as they're internally consistent and operations performed on them will work the same each time (eg h3ToIj(h3Index1, h3Index2,&ijVar); ijVar.i += 2; H3Index h3Index3 = ijToH3(&ijVar, h3Index1) will produce the same h3Index3) then the actual IJ values don't matter.

Is that true, or will people use the IJ values directly in their own code to implement their own algorithms in planar space? If so, I'd want a guarantee that output is stable or a very clear delineation of the unstable interfaces. I don't think a comment is sufficient unless we're willing to stick with current (successful) output until the next major version change.

@isaacbrodsky
Copy link
Collaborator Author

@sahrk

It might be better if users were encouraged up front to think of their ij’s as primarily local planar coordinates, with limited extent.

I definitely want to encourage that. Would changing the name of the function help, perhaps h3ToLocalIj?

I like @nrabinowitz' suggestion of adding an "experimental" prefix, which could then become a deprecated alias when the function is promoted to not have the prefix. (experimentalH3ToLocalIj?)

@sahrk
Copy link

sahrk commented Aug 22, 2018

@isaacbrodsky encoding a warning in the name sounds like a great idea to me. I think that adding local is sufficient to make the point, but would support either.

I realized it might be relevant that I now have the sequence number mapping mentioned in #83 implemented and will be offering it up asap. Assuming you want to use it, it seems like that should have the same kind of caveats as the ij functions, but local would seem odd since it's not really a coordinate system. Which would argue for a prefix like @nrabinowitz's experimental that could be used for both.

Isaac Brodsky added 5 commits August 24, 2018 10:54
The function is renamed to indicate it's experimental, and the
suite of functions is refactored to its own `localij.c` module.
Isaac Brodsky added 4 commits September 14, 2018 10:52
Passing INVALID_BASE_CELL caused a lookup beyond the table, and
thus undefined behavior. The behavior should always be to treat
the base cell as not a pentagon in that case.
@isaacbrodsky
Copy link
Collaborator Author

Now that we have a working build, I think this is ready for a new review.

Copy link
Collaborator

@dfellis dfellis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several comments asking for clarification, but very close to approving this.

- Benchmarks for the kRing method for k's of size 10, 20, 30, and 40.
### Changed
- Internal `h3ToIjk` function renamed to `h3ToLocalIjk`. (#102)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be h3ToLocalIj like elsewhere or no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, as below the IJK functions need to be kept for internal implementation.

* For reversing the rotation introduced in PENTAGON_ROTATIONS when
* the origin is on a pentagon.
*/
const int PENTAGON_ROTATIONS_REVERSE[7][7] = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get PENTAGON_ROTATIONS, PENTAGON_ROTATIONS_REVERSE_NONPOLAR and PENTAGON_ROTATIONS_REVERSE_POLAR, but I don't get this one. Is this one applied in both cases, but then why have the other two? Or why not simply push the compound rotation results into both of the ones below if this is applied and then one of the following two are applied?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PENTAGON_ROTATIONS_REVERSE is not used in the same cases. The comments try to differentiate that this table is used when the origin index is on a pentagon base cell, the two "reverse" tables are used when the origin index is not on a pentagon base cell.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I share David's confusion - maybe expand the comments below to say ...when the index is on a pentagon and the origin is not.

* @param out The index will be placed here on success
* @return 0 on success, or another value on failure.
*/
int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought IJK was going away from this diff? Or are we going H3 -> IJK -> IJ?

CoordIJK ijk;
ijToIjk(ij, &ijk);

return localIjkToH3(origin, &ijk, out);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, you are doing exactly that. Alright, then.

Copy link
Collaborator

@nrabinowitz nrabinowitz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a few nits, but overall this is looking really good! Prestamping, assuming the nits get addressed.

@@ -14,18 +14,20 @@
* limitations under the License.
*/
/** @file
* @brief stdin/stdout filter that converts from H3 indexes to relative IJK
* coordinates. This is experimental.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still true, yes?

t_assert(_ijkMatches(&ijk, &UNIT_VECS[0]) == 1, "not at 0,0,0 (res 0)");
} else if (r == 1) {
t_assert(_ijkMatches(&ijk, &UNIT_VECS[H3_GET_INDEX_DIGIT(h3, 1)]) == 1,
"not at expected coordinates (res 1)");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It looks like these are using negative assertion messages ("test failed for condition") where most of our tests (and some in this file) use positive messages ("test succeeds for condition") or spec messages ("test must succeed when conditition").

"test coordinates result in valid index");
if (_isBaseCellPentagon(H3_EXPORT(h3GetBaseCell)(testH3))) {
// h3IsValid doesn't test for indexes representing part of the
// deleted subsequence.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do this? Should we add an issue for it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say such an index is invalid, similar to if the index had digits set to 7 (invalid child). I think opening an issue to make those indexes invalid would be good.

// Don't iterate all of res 3, to save time
iterateAllIndexesAtResPartial(3, localIjToH3_kRing_assertions, 27);
// These would take too long, even at partial execution
// iterateAllIndexesAtResPartial(4, localIjToH3_kRing_assertions, 20);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include them, or just delete the commented code in this case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced in all cases with the comment that further resolutions aren't tested for time reasons.

We could also consider adding some tests to sample indexes at higher resolutions in the future.

@@ -369,7 +369,7 @@ H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations) {
// Account for differing orientation of the base cells (this edge
// might not follow properties of some other edges.)
if (oldBaseCell != newBaseCell) {
if (newBaseCell == 4 || newBaseCell == 117) {
if (_isBaseCellPolarPentagon(newBaseCell)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

{0, -1, 0, 0, 0, 0, 0}, // 6
};
/**
* Reverse base cell direction -> leading index digit -> roations 60 ccw.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo, here and below: roations

* For reversing the rotation introduced in PENTAGON_ROTATIONS when
* the origin is on a pentagon.
*/
const int PENTAGON_ROTATIONS_REVERSE[7][7] = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I share David's confusion - maybe expand the comments below to say ...when the index is on a pentagon and the origin is not.

*/
int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out) {
if (H3_GET_MODE(origin) != H3_HEXAGON_MODE ||
H3_GET_MODE(h3) != H3_HEXAGON_MODE) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this check - I feel like, similar to the discussion we had around h3IsValid, it would apply to many of our functions, and it's not clear to me why we'd use it here and not in other places.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have the ability to validate some user inputs, is there a reason we would not want to? I think some functions aren't able to cleanly report failures right now, which certainly limits where we can add checks (until 4.0.0 and #104)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, actually. If the function could be conceivably used in a looping hotspot I could see users want to have a faster iteration time if they're confident in the data already being valid.

This feels like one of those functions. H3 indexes could already be validated and stored in DB, then they want fast conversion to IJK for some planar operations and then back again.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have replied directly to the performance concern rather than asking a question. I think in this specific case I am OK with removing the mode check because it doesn't appear this will cause issues, for example there are no array accesses based on the mode of the indexes.

Aside: This code was not introduced in this PR, it was introduced in #83 where we had a similar discussion but didn't come to a conclusion as @nrabinowitz suggested.

I believe there are at least some inputs we should validate, a good example being where there are array accesses based on the inputs, since that could potentially result in segfault, etc.

Isaac Brodsky added 2 commits September 28, 2018 11:31
Normalized test assertion messages to assert a positive outcome.
Tests are updated to assume that unidirectional edges are treated as their origin by this function.

There does not appear to be a case where finding the IJK of the origin itself would fail, so the line checking for that in h3Distance is excluded from coverage.
@isaacbrodsky
Copy link
Collaborator Author

@dfellis and others, please review again.

Copy link
Collaborator

@nrabinowitz nrabinowitz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nit, otherwise LGTM.

} else {
t_assert(0, "wrong resolution");
t_assert(0, "resolution supported by test function (coordinates)");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This (essentially assert.fail) is the one place where negative assertion messages actually make more sense IMO.

int H3_EXPORT(h3Distance)(H3Index origin, H3Index h3) {
CoordIJK originIjk, h3Ijk;
if (h3ToLocalIjk(origin, origin, &originIjk)) {
// Currently there are no tests that would cause getting the coordinates
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this mean practically? Is there no way to create a new test case that would trigger this check to fail?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see there is no input to h3ToLocalIjk where the origin is the same as the index that would fail. The only cases that return failure involve either resolution mismatch, being on different base cells, or deleted subsequence on the pentagon (which should be unreachable when origin=index.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean.

@isaacbrodsky isaacbrodsky merged commit 36a2ceb into uber:master Oct 3, 2018
@isaacbrodsky isaacbrodsky deleted the h3-to-ij branch October 3, 2018 23:58
mrdvt92 pushed a commit to mrdvt92/h3 that referenced this pull request Jun 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants