-
Notifications
You must be signed in to change notification settings - Fork 503
Add hexLine function to return the line of indexes between a given start and end #165
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
Conversation
if ((isResClassIII(res) && | ||
FAILED_DIRECTIONS_III[originLeadingDigit][dir]) || | ||
(!isResClassIII(res) && | ||
FAILED_DIRECTIONS_II[originLeadingDigit][dir])) { |
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 part fixes #163. The change may exclude valid cases, but I think it's okay to be more conservative here. Would like feedback from @isaacbrodsky if available on how the Class III check was expected to work, but I don't think that has to block this change.
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.
Unfortunately I'm not available to investigate this problem right now. I expect this to be safe (edit: meaning won't produce more invalid coordinates) to fail more directions as a temporary solution. I will definitely take a look at #163 when I'm able to.
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.
Definitely want to defer to @isaacbrodsky on this piece, but my gut feeling of applying direction blocks for both hexagon orientations is not the right solution. Either these two should be merged (because there is no difference in reality), or the algorithm behind the FAILED_DIRECTIONS_*
lookup tables needs to be debugged more closely to see why it missed a path.
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.
Since @isaacbrodsky can't look into it at the moment, could we add a comment to revisit this problem in the future? (The only downside is that it might prevent usage of this new function in situations it would be valid in, but since there is no general-purpose fallback here even for cases where this would have failed, that doesn't really change the perceptible end-user behavior.)
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.
Makes sense, I will add a TODO comment here explaining what was removed.
src/apps/testapps/testH3Line.c
Outdated
} | ||
} | ||
|
||
SUITE(h3Distance) { |
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.
Nit: this should be h3Line
.
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.
Whoops, good catch. Will fix.
* @return 0 on success, or another value on failure. | ||
*/ | ||
int H3_EXPORT(h3Line)(H3Index start, H3Index end, H3Index* out) { | ||
int distance = H3_EXPORT(h3Distance)(start, end); |
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.
Unfortunate that this effectively has to be called twice (once for the mem allocation and once for the safeguard here).
Perhaps there can be two functions, here, one that takes the chunk of memory and it's size and one with this signature that takes just the chunk of memory, assumes that it's big enough, and calls h3Distance
to determine the usable size (or fail out early).
This would allow h3Distance
to conceivably be called only once by users wanting to squeeze as much performance out of these calls as they can (such as the potential polyfill
algorithm to reduce memory consumption and maybe run quicker, too).
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.
Well, h3Distance
is constant time and pretty fast, so I'm not sure this is really an issue. polyfill
, kRing
, etc all have the same problem. But if we care, then the simplest solution is to require the caller to pass in the size of the memory allocation:
H3_EXPORT(h3Line)(H3Index start, H3Index end, H3Index* out, int numIndexes);
All callers ought to call h3LineSize
in any case, and can just pass the result in. The only cost is a slightly uglier API that's inconsistent with kRing
and others.
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.
Well, if h3Distance
doesn't add a significant amount of time, then we don't have to worry about it.
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.
Only way to answer is with benchmarks, so I added one:
Current:
-- h3LineNear: 72.083000 microseconds per iteration (10000 iterations)
-- h3LineFar: 3534.151000 microseconds per iteration (1000 iterations)
Pass in distance:
-- h3LineNear: 71.187000 microseconds per iteration (10000 iterations)
-- h3LineFar: 3539.273000 microseconds per iteration (1000 iterations)
So you gain a few microseconds - not enough to make the API more complex.
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.
For scale here, the near line is 40 hex long, the far line is 1824 hex long.
CoordIJK endIjk = {0}; | ||
|
||
// Convert H3 addresses to IJK coords | ||
h3ToLocalIjk(start, start, &startIjk); |
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 comment on our experimentalH3ToLocalIj
transforms: It's unfortunate that the coordinates are not offset by the origin coordinates in the calculation, then you would only have to call h3ToLocalIjk(start, end, &endIjk)
because h3ToLocalIjk(start, start, &startIjk)
would've been defined as (0, 0, 0)
. A bit less computation and the relative nature of the coordinate system would be more obvious to users.
@isaacbrodsky thoughts?
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.
FWIW, I don't think it's any less computation, since you'd still be calculating h3ToLocalIjk(start, start, &startIjk)
under the hood in order to get the coordinates for offset. In fact, it's probably worse, because calling h3ToLocalIjk
repeatedly will perform the calculation of the offset every time - the only time you save work is when the origin
is the same as the index you want to get coords for.
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.
h3ToLocalIjk
includes several branches and loops. I'd assume that three subtractions would be minimal in comparison to the rest of the function, and it would let you call that function once instead of twice and produce coordinates that are simpler to reason about.
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 you'd need to get the coordinates to subtract, which is effectively an h3ToLocalIjk
call on your origin, right? Right now, the origin is just used to get the base cell, and then the IJK is calculated for the target; if you offset by the origin, you'd need to calculate the IJK for the origin, the IJK for the target, and then translate by the origin. It's the additional IJK calculation that adds the perf impact, not the translation.
I could be wrong here, but pretty sure that in a case like h3Line
, where you are finding the coordinates for indexes 1, 2, 3, ...N
, you'd have N - 1
calls instead of the N
we have now, but you'd actually be doing (N - 1) * 2
IJK calculations, instead of the N
we do now.
src/h3lib/lib/localij.c
Outdated
|
||
CoordIJK currentIjk = {startIjk.i, startIjk.j, startIjk.k}; | ||
for (int n = 0; n <= distance; n++) { | ||
cubeRound((float)startIjk.i + iStep * n, (float)startIjk.j + jStep * n, |
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.
Is there a reason to use float
over double
here? Less memory usage, but there's precision loss that could affect long lines at very high resolutions and make the line not completely contiguous, and there's no evidence double precision is any slower on modern hardware. Besides, with just a few variables in play being updated over and over again, I can't imagine double
s pushing the loop memory out of CPU cache.
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.
No real reason other than thinking we didn't need the precision. Will update.
Add hexLine function to return the line of indexes between a given start and end
hexLine
function and exposes it in the API.ijkToCube
andcubeToIjk
helpers to translate from IJK to cube coordinate spaceThis uses the algorithm described on the Red Blob Games site, converting IJK to cube coordinates in order to use the
cubeRound
algo.TODO:
Fixes #163
Closes #162