uber / h3

Hexagonal hierarchical geospatial indexing system
https://h3geo.org
Apache License 2.0
4.92k stars 468 forks source link

How to get the neighbour center geo locations for base cells (resolution 0) ? #659

Closed SkybuckFlying closed 2 years ago

SkybuckFlying commented 2 years ago

I tried the following;

// Step 1. Set center h3 index:
    mCenterH3Index.Reserved := 0;
    mCenterH3Index.Mode := 1;
    mCenterH3Index.Resolution := 0;

    // unuse all H3Index indexes
        // sets all bits of cell indexes to 1, including the base cell to 127. (undocumented, but can't hurt ?)
    mCenterH3Index.UnUseIndexes;

    // set resolution 0 index to 0=center
    mCenterH3Index.Cell[0] := 0;

// clear neighbours just in case:
    for vNeighbourIndex := 1 to 6 do
    begin
        mNeighbourH3Index[vNeighbourIndex].mData := 0;
    end;

//  type is: THexagonNeighbourH3Indexes = array[1..6] of TH3Index;
    if not (originToDirectedEdges( H3Index(mCenterH3Index), @mNeighbourH3Index[1] ) = H3Error(E_SUCCESS)) then
    begin
        result := False;
    end else
    begin
        // debug code.
        for vNeighbourIndex := 1 to 6 do
        begin
            mNeighbourH3Index[vNeighbourIndex].Debug( 'vNeighbour[' + IntToStr(vNeighbourIndex) + ']' );
        end;
    end;
So far input and output is:

Entering THexagonMainNode.AcquireCenterH3Index
mCenterH3Index.Reserved: 0
mCenterH3Index.Mode: 1
mCenterH3Index.ModeDependent: 0
mCenterH3Index.Resolution: 0
mCenterH3Index.Cell[0]: 0
mCenterH3Index.Cell[1]: 7
mCenterH3Index.Cell[2]: 7
mCenterH3Index.Cell[3]: 7
mCenterH3Index.Cell[4]: 7
mCenterH3Index.Cell[5]: 7
mCenterH3Index.Cell[6]: 7
mCenterH3Index.Cell[7]: 7
mCenterH3Index.Cell[8]: 7
mCenterH3Index.Cell[9]: 7
mCenterH3Index.Cell[10]: 7
mCenterH3Index.Cell[11]: 7
mCenterH3Index.Cell[12]: 7
mCenterH3Index.Cell[13]: 7
mCenterH3Index.Cell[14]: 7
mCenterH3Index.Cell[15]: 7
Leaving THexagonMainNode.AcquireCenterH3Index

Entering AcquireNeighbourH3Indexes
mCenterH3Index.Reserved: 0
mCenterH3Index.Mode: 1
mCenterH3Index.ModeDependent: 0
mCenterH3Index.Resolution: 0
mCenterH3Index.Cell[0]: 0
mCenterH3Index.Cell[1]: 7
mCenterH3Index.Cell[2]: 7
mCenterH3Index.Cell[3]: 7
mCenterH3Index.Cell[4]: 7
mCenterH3Index.Cell[5]: 7
mCenterH3Index.Cell[6]: 7
mCenterH3Index.Cell[7]: 7
mCenterH3Index.Cell[8]: 7
mCenterH3Index.Cell[9]: 7
mCenterH3Index.Cell[10]: 7
mCenterH3Index.Cell[11]: 7
mCenterH3Index.Cell[12]: 7
mCenterH3Index.Cell[13]: 7
mCenterH3Index.Cell[14]: 7
mCenterH3Index.Cell[15]: 7
vNeighbour[1].Reserved: 0
vNeighbour[1].Mode: 2
vNeighbour[1].ModeDependent: 1
vNeighbour[1].Resolution: 0
vNeighbour[1].Cell[0]: 0
vNeighbour[1].Cell[1]: 7
vNeighbour[1].Cell[2]: 7
vNeighbour[1].Cell[3]: 7
vNeighbour[1].Cell[4]: 7
vNeighbour[1].Cell[5]: 7
vNeighbour[1].Cell[6]: 7
vNeighbour[1].Cell[7]: 7
vNeighbour[1].Cell[8]: 7
vNeighbour[1].Cell[9]: 7
vNeighbour[1].Cell[10]: 7
vNeighbour[1].Cell[11]: 7
vNeighbour[1].Cell[12]: 7
vNeighbour[1].Cell[13]: 7
vNeighbour[1].Cell[14]: 7
vNeighbour[1].Cell[15]: 7
vNeighbour[2].Reserved: 0
vNeighbour[2].Mode: 2
vNeighbour[2].ModeDependent: 2
vNeighbour[2].Resolution: 0
vNeighbour[2].Cell[0]: 0
vNeighbour[2].Cell[1]: 7
vNeighbour[2].Cell[2]: 7
vNeighbour[2].Cell[3]: 7
vNeighbour[2].Cell[4]: 7
vNeighbour[2].Cell[5]: 7
vNeighbour[2].Cell[6]: 7
vNeighbour[2].Cell[7]: 7
vNeighbour[2].Cell[8]: 7
vNeighbour[2].Cell[9]: 7
vNeighbour[2].Cell[10]: 7
vNeighbour[2].Cell[11]: 7
vNeighbour[2].Cell[12]: 7
vNeighbour[2].Cell[13]: 7
vNeighbour[2].Cell[14]: 7
vNeighbour[2].Cell[15]: 7
vNeighbour[3].Reserved: 0
vNeighbour[3].Mode: 2
vNeighbour[3].ModeDependent: 3
vNeighbour[3].Resolution: 0
vNeighbour[3].Cell[0]: 0
vNeighbour[3].Cell[1]: 7
vNeighbour[3].Cell[2]: 7
vNeighbour[3].Cell[3]: 7
vNeighbour[3].Cell[4]: 7
vNeighbour[3].Cell[5]: 7
vNeighbour[3].Cell[6]: 7
vNeighbour[3].Cell[7]: 7
vNeighbour[3].Cell[8]: 7
vNeighbour[3].Cell[9]: 7
vNeighbour[3].Cell[10]: 7
vNeighbour[3].Cell[11]: 7
vNeighbour[3].Cell[12]: 7
vNeighbour[3].Cell[13]: 7
vNeighbour[3].Cell[14]: 7
vNeighbour[3].Cell[15]: 7
vNeighbour[4].Reserved: 0
vNeighbour[4].Mode: 2
vNeighbour[4].ModeDependent: 4
vNeighbour[4].Resolution: 0
vNeighbour[4].Cell[0]: 0
vNeighbour[4].Cell[1]: 7
vNeighbour[4].Cell[2]: 7
vNeighbour[4].Cell[3]: 7
vNeighbour[4].Cell[4]: 7
vNeighbour[4].Cell[5]: 7
vNeighbour[4].Cell[6]: 7
vNeighbour[4].Cell[7]: 7
vNeighbour[4].Cell[8]: 7
vNeighbour[4].Cell[9]: 7
vNeighbour[4].Cell[10]: 7
vNeighbour[4].Cell[11]: 7
vNeighbour[4].Cell[12]: 7
vNeighbour[4].Cell[13]: 7
vNeighbour[4].Cell[14]: 7
vNeighbour[4].Cell[15]: 7
vNeighbour[5].Reserved: 0
vNeighbour[5].Mode: 2
vNeighbour[5].ModeDependent: 5
vNeighbour[5].Resolution: 0
vNeighbour[5].Cell[0]: 0
vNeighbour[5].Cell[1]: 7
vNeighbour[5].Cell[2]: 7
vNeighbour[5].Cell[3]: 7
vNeighbour[5].Cell[4]: 7
vNeighbour[5].Cell[5]: 7
vNeighbour[5].Cell[6]: 7
vNeighbour[5].Cell[7]: 7
vNeighbour[5].Cell[8]: 7
vNeighbour[5].Cell[9]: 7
vNeighbour[5].Cell[10]: 7
vNeighbour[5].Cell[11]: 7
vNeighbour[5].Cell[12]: 7
vNeighbour[5].Cell[13]: 7
vNeighbour[5].Cell[14]: 7
vNeighbour[5].Cell[15]: 7
vNeighbour[6].Reserved: 0
vNeighbour[6].Mode: 2
vNeighbour[6].ModeDependent: 6
vNeighbour[6].Resolution: 0
vNeighbour[6].Cell[0]: 0
vNeighbour[6].Cell[1]: 7
vNeighbour[6].Cell[2]: 7
vNeighbour[6].Cell[3]: 7
vNeighbour[6].Cell[4]: 7
vNeighbour[6].Cell[5]: 7
vNeighbour[6].Cell[6]: 7
vNeighbour[6].Cell[7]: 7
vNeighbour[6].Cell[8]: 7
vNeighbour[6].Cell[9]: 7
vNeighbour[6].Cell[10]: 7
vNeighbour[6].Cell[11]: 7
vNeighbour[6].Cell[12]: 7
vNeighbour[6].Cell[13]: 7
vNeighbour[6].Cell[14]: 7
vNeighbour[6].Cell[15]: 7
Leaving AcquireNeighbourH3Indexes

First of all my H3 application is producing access violations, not yet sure why.

I want to acquire all the neighbours of a H3 Cell Index. Currently at resolution 0, the base cells.

I am investigating API:

originToDirectedEdges

Once it acquires the H3 Edge Indexes I try and convert them with cellToLatLng:

!! but all the coordinates that come out seem to be the same !?!

So this seems useless ?! I also tried to convert the mode to 1, not helping,

(ofcourse then the cell[0] remains zero as it was copied from H3Index cell 0, I saw that in source code of library).

So how to do this successfully ?

SkybuckFlying commented 2 years ago

Re-opened.

nrabinowitz commented 2 years ago

I think you're looking for gridDisk - you're just trying to get the neighbors of a given cell, right? This shouldn't require using edges - edges are specifically for applications where you need to represent the connection between neighboring cells and store that relationship somewhere. For traversal, you more likely want gridDisk(origin, 1) to find neighbors.

SkybuckFlying commented 2 years ago

HAHA, Funny ! :) I actually do want the connection between neighbours. So how to do it ? Maybe gridDisk can help with that too ? I am too tired to check now... if you can save me some energy/effort then I am all ears lol :)

nrabinowitz commented 2 years ago

I'm confused. If what you want is what's in the title for this issue, neighbour center geo locations, then you don't need connections, you just need the neighbors. In JS:

const neighbors = h3.gridDisk(origin, 1);
const centers = neighbors.map(h3.cellToLatLng);

When you use originToDirectedEdges, you get the directed edges between the origin and the neighbors, and can convert to the neighbor cell using getDirectedEdgeDestination(edge):

const edges = h3.originToDirectedEdges(origin);
const neighbors = edges.map(h3.getDirectedEdgeDestination);
const centers = neighbors.map(h3.cellToLatLng);

But this is much less efficient than gridDisk if you're just trying to get the centers of the neighbors. The reason to use edges is generally because you want to associate data with the connection itself, e.g. a flow rate between cells, or because you want to get geometry for a single edge using directedEdgeToBoundary. If you can explain your use case, it might be helpful in determining whether you want edges at all.

SkybuckFlying commented 2 years ago

I am trying or going to try to implement this routine idea:

An_Optimal_Permutation_Routing_Algorithm_on_Full-D.pdf

Link: https://www.researchgate.net/publication/26645352_An_Optimal_Permutation_Routing_Algorithm_on_Full-Duplex_Hexagonal_Networks

(Routing like this will become a bit of a problem in and around pentagon cells...)

For now I am working on the visualizations to help me out with it.

Two uses cases:

  1. Visualization of connections between hexagon centers, "up and "down" in others words "to" and "from".
  2. This visualization and connection would be for a hexagon world routing protocol. Where packets are routed between the centers of the hexagon cells and where the connections between the hexagon centers form "communication links" (=connections).

Thanks very much for you help, I will give your suggestions/advice a try, if you have any further advice I am all ears ! =D

SkybuckFlying commented 2 years ago

The reason why I did not want to use GridDisk at first is basically "what does distance mean" ? Is there any chance that it might accidently give cells back from the cell further away... but I think now I understand it, if distance means 1 hexagon, then I get it, I will give it a try. There is a little downside to this method, it requires two API calls plus potentially having to allocate something, but that can be done up front, assuming it always returns same ammount, so maybe it does not have to be called:

maxGridDiskSize

SkybuckFlying commented 2 years ago

The problem with maxGridDiskSize is it returns 7. So the problem with gridDisk could be that it also returns the center cell.

I do not want that. I only want the neighbour cells preferably. Now I could try and filter out the center cell from gridDisk,

However there is a further problem. The API mentions, the returned H3Indexes are in random order... So there is no way to know, what is the center cell, except by comparing the center cell index with all 7 returned, so there is a way to know. Though this costs 7 expensive 64 bit comparisions, not to bad yet, but this is not ideal.

SkybuckFlying commented 2 years ago

The documentation is a bit shady:

"H3Error gridDiskDistances(H3Index origin, int k, H3Index out, int distances);"

Output is placed in the provided array in no particular order. Elements of the output array may be left as zero, which can happen when crossing a pentagon.

What array, there are only pointers ? But ok, I assume the H3Index is "the array." But what is int distances ? unexplained.

Also is it just 1 parameter or multiple ? Very strange... Almost unusuable, I am going to have to guess or maybe look at other code examples.

Dispite the weirdness, success for now:

var
    vNeighbourIndexIterator : integer;
    vHexagonalDistance : integer;
    vNeighbourIndex : array[0..6] of H3Index;
    vNeighbourDistance : array[0..6] of integer;

    vCopyIndex : integer;
    vIndexCount : int64;

    // alternative code:
    vHexagonalDistance := 1;
    if maxGridDiskSize(vHexagonalDistance, vIndexCount ) = H3Error(E_SUCCESS) then
    begin

        if gridDiskDistances( H3Index(mCenterH3Index), vHexagonalDistance, @vNeighbourIndex[0], @vNeighbourDistance[0]  ) = H3Error(E_SUCCESS) then
        begin
            vCopyIndex := 1;
            for vNeighbourIndexIterator := 0 to 6 do
            begin

                if vNeighbourIndex[vNeighbourIndexIterator] <> 0 then
                begin

                    // check if it is not the center
                    if H3Index(mCenterH3Index) <> vNeighbourIndex[vNeighbourIndexIterator] then
                    begin
                        mNeighbourH3Index[vCopyIndex].mData := vNeighbourIndex[vNeighbourIndexIterator];
                        vCopyIndex := vCopyIndex + 1;
                    end;
                end;
            end;
        end else
        begin
            result := False;

        end;

    end else
    begin
        result := False;

    end;

Checking the edge method next.

dfellis commented 2 years ago

The problem with maxGridDiskSize is it returns 7. So the problem with gridDisk could be that it also returns the center cell.

I do not want that. I only want the neighbour cells preferably. Now I could try and filter out the center cell from gridDisk,

However there is a further problem. The API mentions, the returned H3Indexes are in random order... So there is no way to know, what is the center cell, except by comparing the center cell index with all 7 returned, so there is a way to know. Though this costs 7 expensive 64 bit comparisions, not to bad yet, but this is not ideal.

So if you want to be exactly sure about the distances, you can either:

  1. Use gridDiskDistances which guarantees the order, but is slower, or for your particular case
  2. Use gridRingUnsafe which is very fast and will only return the cells in the ring of the size you have specified (but is called 'unsafe' because it will error out if there's a pentagon anywhere in the area enclosed by that ring, even if not on the ring itself).

I would also personally recommend to not leave disparaging comments about the documentation as you have? Pointing out where you don't find it clear is one thing, but calling it "shady" and "unusable" is somewhat insulting and also not really true when you are the first person in the multiple years of this documentation being available and successfully used by others to call it "unusable."

SkybuckFlying commented 2 years ago

I didn't notice there were two gridDisk functions:

  1. gridDisk
  2. gridDiskDistances <- I happen to use this one.

However what I did notice is both are documented as "un-ordered".

This is a problem for routing. The neighbours should have a fixed order of appearance, as depicted in the documentation:

https://h3geo.org/docs/core-library/h3Indexing

So gridDisk functions are not following the documentation.

(So now you claim gridDiskDistances does garantuee order ? So which one is it ? Is the documentation correct or are you correct ? Perhaps you looked at source code ? I do need some garantuees for future, perhaps gridDiskDistances happens to garantuee order by accident/chance.

dfellis commented 2 years ago

I didn't notice there were two gridDisk functions:

1. gridDisk

2. gridDiskDistances <- I happen to use this one.

However what I did notice is both are documented as "un-ordered".

This is a problem for routing. The neighbours should have a fixed order of appearance, as depicted in the documentation:

https://h3geo.org/docs/core-library/h3Indexing

So gridDisk functions are not following the documentation.

(So now you claim gridDiskDistances does garantuee order ? So which one is it ? Is the documentation correct or are you correct ? Perhaps you looked at source code ? I do need some garantuees for future, perhaps gridDiskDistances happens to garantuee order by accident/chance.

It guarantees that each ring is separated from each other and any collection of cells are the same distance away from the origin cell. That is the level of ordering I was referring to.

The order of the cells in each grouping with respect to each other is not guaranteed, which was not what I was referring to. The guarantee on ordering is similar to:

arrayOfCells.sort((a, b) => distanceFromOriginCell(a) - distanceFromOriginCell(b))

to use some Javascript pseudocode. Two cells with the same distance will be considered "equivalent" by the sorting algorithm and the order does not matter.

The issue with the ordering within a particular ring, and why it was left undefined, is that different algorithms do different things to try and be efficient with the guarantee on ordering that has been defined (which is only distance ordering for gridDiskDistances, no guaranteed ordering at all for gridDisk, and actually counter-clockwise ordering on gridRingUnsafe, but only when there's no pentagon within the area enclosed by the ring)

On this point:

The neighbours should have a fixed order of appearance, as depicted in the documentation

This could work for a ring size of 1, but would fail the moment you reach a ring size of 2 or greater. It also would still seem "unordered" in non-trivial use-cases because the orientation of the underlying icosahedron affects which hexagon will be first in the list, such that it is not clearly associated in any way to the poles.

Going to this particular explanation of what you are trying to accomplish, however:

An_Optimal_Permutation_Routing_Algorithm_on_Full-D.pdf

You want a generalized traversal and a known orientation from one cell to the next, which is not what the disk/ring functions are for, and bolting that logic on with the directed edge will be tedious and slow.

There a more relevant API for you: https://h3geo.org/docs/api/traversal#celltolocalij

It is called a "Local IJ" coordinate system because it only works on one icosahedron face of the planet at a time, but it produces a simpler axial coordinate system that you can use.

On this one I admit that the documentation is very poor, because it was considered experimental until this release and we have not yet written the complete documentation for it.

@isaacbrodsky let me know if I am getting any of this wrong:

When you specify an origin hexagon, it is not defining the (0,0) point, but instead simply selecting the icosahedron face to use, and the (0,0) coordinate is the center hexagon of that particular icosahedron.

In order to orient the IJ coordinate system with the actual spatial coordinate system you care about, though, you should use three sample points forming a triangle that you convert to hexagon cells first and then acquire the Local IJ coordinates for each, which you can then use to determine the scale, rotation, and skew between your own coordinate system and the IJ coordinate system, then with that known, you can traverse through the Local IJ coordinate system by simply adding or subtracting integer values from the I and J coordinates, and then convert it back into a hexagonal cell, and from there to Lat, Lng or however you wish.

I will end this by noting that you are still being very rude in the face of help we are freely giving, so this will be the last time I message you.

isaacbrodsky commented 2 years ago

@isaacbrodsky let me know if I am getting any of this wrong:

When you specify an origin hexagon, it is not defining the (0,0) point, but instead simply selecting the icosahedron face to use, and the (0,0) coordinate is the center hexagon of that particular icosahedron.

In order to orient the IJ coordinate system with the actual spatial coordinate system you care about, though, you should use three sample points forming a triangle that you convert to hexagon cells first and then acquire the Local IJ coordinates for each, which you can then use to determine the scale, rotation, and skew between your own coordinate system and the IJ coordinate system, then with that known, you can traverse through the Local IJ coordinate system by simply adding or subtracting integer values from the I and J coordinates, and then convert it back into a hexagonal cell, and from there to Lat, Lng or however you wish.

This is right with the correction that it operates on a base cell, not the icosahedron face. So 0,0 refers to the center of the base cell, and the coordinate system is valid only for that base cell and neighboring base cells.

For pentagon base cells it is more limited. The coordinate space encompasses the part of the pentagon base cell on the same icosahedron face as the origin cell, the neighboring base cell on that icosahedron face, and the parts of the pentagon base cell on neighboring icosahedron faces to the origin.

SkybuckFlying commented 2 years ago

I would also personally recommend to not leave disparaging comments about the documentation as you have? Pointing out where you don't find it clear is one thing, but calling it "shady" and "unusable" is somewhat insulting and also not really true when you are the first person in the multiple years of this documentation being available and successfully used by others to call it "unusable."

Perhaps I am the first person in the world to actually use the new version 4 documentation.

I was using the version 3 documentation and I was using the version 3 library. Then recently in the last few days, version 4 appeared. It's a drop box in the top right corner.

Somehow it became difficult to determine if what I was looking at is version 3 or version 4 and that made it unusuable to me, to much time wasted trying to figure out, which version is linked to which version.

Perhaps the documentation does have issues and cross links versions, not sure, just the possibility of it is already confusing.

When looking at a function name from the library API/source code/header I no longer know if it's from version 3 or version 4, so now I have to go find it, in not only a whole bunch of different sections, but also across different versions.

At that point I gave up, and upgraded to version 4.

SkybuckFlying commented 2 years ago

So far I liked nrabinowitz solution best, but it has a problem, I filled a new issue about it too.

As far as I can tell:

const edges = h3.originToDirectedEdges(origin); const neighbors = edges.map(h3.getDirectedEdgeDestination);

The first api call does not sort the edges in clock-wise order... so it seems to be unsorted... this is a problem when trying to visualize the triangles between the centers.

I want to create a triangle from center of hexagon, to center of neighbour hexagon, and then to next neighbour hexagon.

The next neighbour hexagon should be in clock-wise order to get a nice triangle.

Otherwise 180 degree triangles could occur and this looks bad.

isaacbrodsky commented 2 years ago

I think the question raised about finding neighbors has been answered