yjh0502 / rust-s2

S2 geometry library in Rust
Apache License 2.0
77 stars 20 forks source link

Traversing Neighbors Horizontally & Vertically (not an issue) #43

Open TurtIeSocks opened 3 months ago

TurtIeSocks commented 3 months ago

First of all, thanks for the Rust port of the S2 lib! I still have a lot to learn about S2 in general but I'm running into a wall on this and feel like there must be a better way.

The Goal

Traverse multiple cells in any (edge) direction from a given cell.

Quick Example

  1. My API ingests some Polygon shape.
  2. I create a RegionCoverer to collect all cells in the bbox of that shape.
  3. Then I want to traverse those cells and collect the Lat/Lng point every 9 cells in a grid pattern.
Screenshot 2024-08-01 at 10 02 31 AM

Current Implementation

My initial implementation of this essentially called .edge_neighbors over and over n times to get to where I wanted to go. Since looking at source, I think I've moved onto something a little more sophisticated:

Structs:

#[derive(Debug, Clone, Copy)]
pub enum Dir {
    Up,
    Right,
    Down,
    Left,
}

#[derive(Debug, Clone, Copy)]
pub struct TraversalCell {
    pub cell: CellID,
    pub dir: Dir,
    pub prev_face: u8,
}

Traversal function:

// implementation for `TraversalCell`
    fn traverse(&mut self, count: u64) {
        let face = self.cell.face();
        let level = self.cell.level();
        let size = (cellid::size_ij(level) * count) as i32;
        let (f, cur_i, cur_j, _) = self.cell.face_ij_orientation();

        let (next_i, next_j) = match self.dir {
            Dir::Up => (cur_i, cur_j + size),
            Dir::Right => (cur_i + size, cur_j),
            Dir::Down => (cur_i, cur_j - size),
            Dir::Left => (cur_i - size, cur_j),
        };

        // this function was copied from source since it was originally private
        self.cell = from_face_ij_wrap(f, next_i, next_j).parent(level); 

        if self.cell.face() != self.prev_face {
            self.prev_face = face;
        }
    }

This works...okay. When the face stays the same, it's fine. When the face changes after a traversal, that's when things start to fall apart and where I'm running into a wall here.

The Dir enum is somewhat arbitrary, it was originally N, E, S, W, but since S2 cell neighbors don't always necessarily align with that I've swapped to something more generic.

I've looked at all sorts of various ways of manipulating the direction depending on the current face / current dir / previous face and for the most part have found some solutions that probably lean more in the hacky direction for solving most scenarios when there are 2 faces, but I haven't found something that will account for places where there are 3 faces. The issue that I'm running into with this is that some solutions will work for most scenarios but sometimes they have to be adapted entirely for other scenarios that then no longer work for the previously working ones.

Questions

Apologies, I know that's a lot of info but any help is appreciated, thank you!