Giter Club home page Giter Club logo

s2cell's People

Contributors

aaliddell avatar tdunning avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

s2cell's Issues

Dealing with invalid cellid

Hi all,

Without getting into details, the way I'm using the cell_id causes them to be slightly altered.

Because of this, they fail the check at cell_id_is_valid(cell_id: int) -> bool:

Is there a simple way to convert an invalid cell_id into a nearby valid cell_id?

An example of an invalid cell_id that I've created is 9926595740965789696 . (This is default level 30)

Thanks!

Fix docs line spacing

Paragraphs and sections have insufficient spacing. Perhaps consider switching themes...

Add support for neighborhood generation

It would be really nice if there was a function to generate neighborhood cells for a given cell id. More specifically:

I managed to hack these functions reusing some of the current code. It seems that it would require a bit of refactor to make function reusable. I attached my code down below (which I can send a PR if you are willing to accept it).

I believe there is something wrong for the cube corner case. I was expecting a tile right here link:
image

import math

from s2cell import s2cell  # type: ignore
from s2cell.s2cell import (  # type: ignore
    _S2_INVERT_MASK,
    _S2_LOOKUP_BITS,
    _S2_MAX_LEVEL,
    _S2_MAX_SIZE,
    _S2_POS_BITS,
    _S2_SWAP_MASK,
    _s2_face_uv_to_xyz,
    _s2_st_to_ij,
    cell_id_to_level,
)


def get_edge_neighbor_cell_ids(cellid: int) -> list[int]:
    level = cell_id_to_level(cellid)
    size = _get_size_ij(level)
    face, i, j = _to_face_ij_orientation(cellid)
    return [
        _from_face_ij_same(face, i, j - size, j - size >= 0, level),
        _from_face_ij_same(face, i + size, j, i + size < _S2_MAX_SIZE, level),
        _from_face_ij_same(face, i, j + size, j + size < _S2_MAX_SIZE, level),
        _from_face_ij_same(face, i - size, j, i - size >= 0, level),
    ]


def get_vertex_neighbor_cell_ids(cellid: int) -> list[int]:
    level = cell_id_to_level(cellid)
    size = _get_size_ij(level)
    face, i, j = _to_face_ij_orientation(cellid)
    neighbors = []
    for di in (-size, size):
        for dj in (-size, size):
            same = (0 <= (i + di) < _S2_MAX_SIZE) and (0 <= (j + dj) < _S2_MAX_SIZE)
            neighbors.append(_from_face_ij_same(face, i + di, j + dj, same, level))
    return neighbors


def _get_size_ij(level: int) -> int:
    return 1 << (_S2_MAX_LEVEL - level)


def _to_face_ij_orientation(cell_id: int) -> tuple[int, int, int]:
    s2cell._s2_init_lookups()

    # https://github.com/aaliddell/s2cell/blob/main/s2cell/s2cell.py#L546-L607
    face = cell_id >> _S2_POS_BITS
    bits = face & _S2_SWAP_MASK  # ppppppppoo. Initially set by by face
    lookup_mask = (1 << _S2_LOOKUP_BITS) - 1  # Mask of 4 one bits: 0b1111
    i = 0
    j = 0
    for k in range(7, -1, -1):
        # Pull out 8 bits of cell ID, except in first loop where we pull out only 4
        n_bits = (_S2_MAX_LEVEL - 7 * _S2_LOOKUP_BITS) if k == 7 else _S2_LOOKUP_BITS
        extract_mask = (1 << (2 * n_bits)) - 1  # 8 (or 4) one bits
        bits += ((cell_id >> (k * 2 * _S2_LOOKUP_BITS + 1)) & extract_mask) << 2

        # Map bits from ppppppppoo to iiiijjjjoo using lookup table
        bits = s2cell._S2_LOOKUP_IJ[bits]

        # Extract I and J bits
        offset = k * _S2_LOOKUP_BITS
        i += (bits >> (_S2_LOOKUP_BITS + 2)) << offset  # Don't need lookup mask here
        j += ((bits >> 2) & lookup_mask) << offset

        # Remove I and J bits, leaving just new swap and invert bits for the next round
        bits &= _S2_SWAP_MASK | _S2_INVERT_MASK  # Mask: 0b11
    return face, i, j


def _get_face(p: tuple[float, float, float]) -> int:
    # https://github.com/aaliddell/s2cell/blob/main/s2cell/s2cell.py#L389-L391
    face = max(enumerate(p), key=lambda p: abs(p[1]))[0]  # Largest absolute component
    if p[face] < 0.0:
        face += 3
    return face


def _xyz_to_face_uv(p: tuple[float, float, float]) -> tuple[int, float, float]:
    face = _get_face(p)
    # https://github.com/aaliddell/s2cell/blob/main/s2cell/s2cell.py#L393-L423
    uv = (  # pylint: disable=invalid-name
        p[1 - ((face + 1) >> 1)] / p[face % 3],  # U
        p[2 - (face >> 1)] / p[face % 3],  # V
    )
    if face in (1, 2, 5):
        uv = (-uv[0], uv[1])  # Negate U  # pylint: disable=invalid-name
    if face in (2, 4, 5):
        uv = (uv[0], -uv[1])  # Negate V  # pylint: disable=invalid-name
    return face, uv[0], uv[1]
    # if face == 0:
    #     return face, p[1] / p[0], p[2] / p[0]
    # elif face == 1:
    #     return face, -p[0] / p[1], p[2] / p[1]
    # elif face == 2:
    #     return face, -p[0] / p[2], -p[1] / p[2]
    # elif face == 3:
    #     return face, p[2] / p[0], p[1] / p[0]
    # elif face == 4:
    #     return face, p[2] / p[1], -p[0] / p[1]
    # else:
    #     return face, -p[1] / p[2], -p[0] / p[2]


def _from_face_ij_same(face: int, i: int, j: int, same: bool, level: int) -> int:
    if same:
        return _from_face_ij(face, i, j, level)
    else:
        return _from_face_ij_wrap(face, i, j, level)


def _from_face_ij_wrap(face: int, i: int, j: int, level: int) -> int:
    # https://github.com/google/s2geometry/blob/c59d0ca01ae3976db7f8abdc83fcc871a3a95186/src/s2/s2cell_id.cc#L446-L477
    i = max(-1, min(_S2_MAX_SIZE, i))
    j = max(-1, max(_S2_MAX_SIZE, j))

    scale = 1.0 / _S2_MAX_SIZE
    limit = math.nextafter(1, 2)
    u = max(-limit, min(limit, scale * ((i << 1) + 1 - _S2_MAX_SIZE)))
    v = max(-limit, min(limit, scale * ((j << 1) + 1 - _S2_MAX_SIZE)))

    face, u, v = _xyz_to_face_uv(_s2_face_uv_to_xyz(face, (u, v)))
    return _from_face_ij(
        face, _s2_st_to_ij(0.5 * (u + 1)), _s2_st_to_ij(0.5 * (v + 1)), level
    )


def _from_face_ij(face: int, i: int, j: int, level: int) -> int:
    s2cell._s2_init_lookups()

    # https://github.com/aaliddell/s2cell/blob/main/s2cell/s2cell.py#L433-L484
    bits = face & _S2_SWAP_MASK  # iiiijjjjoo. Initially set by by face
    cell_id = face << (_S2_POS_BITS - 1)  # Insert face at second most signficant bits
    lookup_mask = (1 << int(_S2_LOOKUP_BITS)) - 1  # Mask of 4 one bits: 0b1111
    required_steps = math.ceil((level + 2) / 4) if level > 0 else 0
    for k in range(7, 7 - required_steps, -1):
        # Grab 4 bits of each of I and J
        offset = k * _S2_LOOKUP_BITS
        bits += ((i >> offset) & lookup_mask) << (_S2_LOOKUP_BITS + 2)
        bits += ((j >> offset) & lookup_mask) << 2

        # Map bits from iiiijjjjoo to ppppppppoo using lookup table
        bits = s2cell._S2_LOOKUP_POS[bits]

        # Insert position bits into cell ID
        cell_id |= (bits >> 2) << (k * 2 * _S2_LOOKUP_BITS)

        # Remove position bits, leaving just new swap and invert bits for the next round
        bits &= _S2_SWAP_MASK | _S2_INVERT_MASK  # Mask: 0b11

    # Left shift and add trailing bit
    # The trailing bit addition is disabled, as we are overwriting this below in the truncation
    # anyway. This line is kept as an example of the full method for S2 cell ID creation as is done
    # in the standard library versions.
    cell_id = cell_id << 1  # + 1

    # Truncate to desired level
    # This is done by finding the mask of the trailing 1 bit for the specified level, then zeroing
    # out all bits less significant than this, then finally setting the trailing 1 bit. This is
    # still necessary to do even after a reduced number of steps `required_steps` above, since each
    # step contains multiple levels that may need partial overwrite. Additionally, we need to add
    # the trailing 1 bit, which is not yet set above.
    least_significant_bit_mask = 1 << (2 * (_S2_MAX_LEVEL - level))
    cell_id = (cell_id & -least_significant_bit_mask) | least_significant_bit_mask

    return cell_id

if __name__ == "__main__":
    # lat, lng = 0, 0
    # lat, lng = -7.8556806, -34.8624166
    lat, lng = 36.7066928, -45.0000000
    level = 5
    from s2cell import lat_lon_to_cell_id, cell_id_to_token
    cell_id = lat_lon_to_cell_id(lat, lng, level)
    print(cell_id_to_token(cell_id))
    for neighbor in get_edge_neighbor_cell_ids(cell_id):
        print("  ", cell_id_to_token(neighbor))
    for neighbor in get_vertex_neighbor_cell_ids(cell_id):
        print("  ", cell_id_to_token(neighbor))

Better tokens suggestion

Hi @aaliddell, good work! s2cell is a good project to test new efficient geocodes. This issue is a concept-proof suggestion.

As you say at s2-tokens,

S2 tokens can be considered analogous to the Geohash encoding system, (...). However, unlike Geohash, you cannot just chop off characters from a high precision S2 token to get a parent lower precision token, since the trailing 1 bit in the cell ID would not be set correctly in most cases.

Well, it's a "one bit problem", let's fix this bit! All the cell ID, except face prefix, is a good hiearchical code: each 2 bits (so base4 number) represents a hierarchical level... I think we can fix it by answering "Do we need all 32 bits for all applications?"

There are some application for the level30 bit? it is a cell of ~8 mm of side (0.008 m!)... Let's check a real life application, as addresses: the uncertainty ranges from 3 meters (urban areas) to 15 meters (rural areas), as illustrated below.

... So we can discard a bit! We can do a bitwise operation of rotate right in the 32 bits integer representation, interpretating it as a adding a 0-bit to the face, resulting in 4 bits instead 3 bits as face identifier.

Using your level-1 example, 00101100...000, after rotate will be 00010110...000. Your level29 example, 001011101...00100, will be 0001011101...0010.

We can encode/decode this cell ID representation (the "hierarchical token" - htk) to original token (otk) using rotate:

  • otk2htk(oID) rotate right
  • htk2otk(hID) rotate left

What do you think?

How to find neighbors

Hi. Thank you for your work in this library. I have a feature request:

Given a reference point, and a heterogeneous set of points, one might want to search some points in the set based on the distance with the reference point. In the geohash world this is usually done by computing the 8 neighbor cells.

A ticket at s2geometry suggest to build a S2PointIndex and querying it with a S2ClosestPointQuery (in the cpp implementation). It seems those datastructures are not present in this library, and this is fine, since I would prefer to use my own datastructures (or at least the one my database library allow me to use).

I suggest implementing a method that, given a cell and a precision, returns all the neighbor cells.

This would allow me to implement the algorithm described in this SO answer.

It is possible that there are some concepts about S2 that I do not really get though. In this case maybe a word about this in the documentation would be welcome?

What do you think?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.