s2sphere

repository·master·Indexed 42 days ago

https://github.com/sidewalklabs/s2sphere

A Python implementation of the S2 geometry library for spherical geometry calculations and spatial indexing. It provides tools for working with S2 cells, coordinates (LatLng, Point, Angle), and geometric shapes like Caps and LatLngRects, as well as utility functions for area calculations and coordinate transformations.

Tokens
17.6K
Snippets
46
Records
78
Agent score
53%

What's inside s2sphere

  1. Understand the S2 class and spherical geometry framework

    master

    The S2 class serves as a namespace for constants and static utility functions related to spherical geometry, including area calculations and edge intersection tests.

    Key features include:

    • Cell Decomposition: A framework for decomposing the unit sphere into a hierarchy of 'cells' (quadrilaterals bounded by four geodesics). The hierarchy starts by projecting the six faces of a cube onto the sphere and recursively subdividing cells into four children.
    • Coordinate Systems: Defines how cube faces are projected to ensure sequentially increasing cell IDs follow a continuous space-filling curve.

    Note: The S2 class is not thread-safe for loops and objects that use loops.

  2. Understand S2Loop for spherical polygons

    master

    An S2Loop represents a simple spherical polygon defined by a chain of vertices.

    Constraints and Behavior:

    • Orientation: All loops are defined with Counter-Clockwise (CCW) orientation. The interior is on the left side of the edges. A small clockwise loop is interpreted as a large CCW loop enclosing the rest of the sphere.
    • Validity: Loops must have at least 3 vertices. They cannot have duplicate vertices (adjacent or otherwise) and non-adjacent edges must not intersect.
    • Implementation Note: While these restrictions are not strictly enforced in optimized code, violating them may lead to unexpected results.
  3. Use S2R2Rect for planar geometry approximation

    master

    S2R2Rect is a class that represents a closed axis-aligned rectangle in the (x,y) plane. It acts as a subtype of S2Region, allowing you to use spherical geometry machinery (like S2RegionCoverer) to approximate planar rectangles as a collection of S2CellIds.

    Important Constraints:

    • It is interpreted as a region of (s,t)-space on face 0.
    • Only rectangles that are subsets of the $[0,1] \times [0,1]$ range can be approximated using the S2RegionCoverer interface.
    • It is intended to be copied by value, but it is not a POD (Plain Old Datatype) because it contains virtual functions.
    // Example of creating an S2R2Rect (conceptual usage based on API)
    S2R2Rect rect = S2R2Rect::FromCenterSize(center, size);
    // You can then use it where an S2Region is expected
    // e.g., with an S2RegionCoverer to get S2CellIds
  4. Use S2CellUnion to approximate shapes

    master

    An S2CellUnion is a region consisting of cells of various sizes, typically used to approximate a shape. Because cells follow a fixed hierarchical structure, they are more suitable for preprocessing-based optimizations than polygons.

    Important Lifecycle Note: Most geometric calculations (like Intersects() or Contains()) require the cell union to be normalized. You should call Normalize() after initializing the union to ensure it is in a valid state for computation.

    S2CellUnion union_region;
    union_region.Init(cell_ids);
    union_region.Normalize(); // Required before geometric operations
  5. Understand the S2Region interface

    master

    S2Region is an abstract interface representing a two-dimensional region on the unit sphere. It is designed to allow complex regions to be approximated by simpler ones.

    Core Methods:

    • S2Region* Clone() const: Returns a deep copy. Use down_cast<T*> to narrow to a specific subtype.
    • S2Cap GetCapBound() const: Returns a bounding spherical cap (not guaranteed to be exact).
    • S2LatLngRect GetRectBound() const: Returns a bounding latitude-longitude rectangle (not guaranteed to be tight).
    • bool Contains(S2Cell const& cell) const: Returns true if the region completely contains the cell.
    • bool MayIntersect(S2Cell const& cell) const: Returns false if the region definitely does not intersect the cell.
    • virtual bool VirtualContainsPoint(S2Point const& p) const: Returns true if the point is in the region. For performance, use the non-virtual Contains(S2Point const& p) if calling on a specific known subtype.
    • void Encode(Encoder* const encoder) const / bool Decode(Decoder* const decoder): Used for serialization. DecodeWithinScope can be used for faster decoding by pointing directly into the decoder's buffer (valid only for the lifetime of the decoder).
  6. How S2Polygon works and its restrictions

    master

    An S2Polygon is an S2Region representing a collection of loops acting as "shells" (outer boundaries) and "holes". A point is considered inside the polygon if it is contained by an odd number of loops.

    Polygon Restrictions

    To maintain validity, polygons must adhere to these rules:

    1. No Crossing Loops: A loop's boundary cannot intersect both the interior and exterior of another loop.
    2. No Shared Edges: If a loop contains edge AB, no other loop may contain edge AB or BA.
    3. Area Limit: No single loop may cover more than half the area of the sphere (to prevent ambiguity between a loop and its complement).

    Hierarchy and Traversal

    Loops in an S2Polygon are organized in a nesting hierarchy. When initialized via Init(vector<S2Loop*>* loops), the polygon reorders loops using a preorder traversal. This means a parent loop is immediately followed by its descendants in the hierarchy.

    • Finding Children: To find the immediate children of loop k, iterate from k+1 to GetLastDescendant(k) and select loops where depth == loop(k)->depth() + 1.
    • Parent/Child Navigation:
      • GetParent(int k): Returns the index of the parent loop, or -1 if it is a shell.
      • GetLastDescendant(int k): Returns the index of the last loop contained within loop k.

    Key Methods

    • GetArea(): Returns the total area of the polygon interior (0 to 4*Pi).
    • GetCentroid(): Returns the true centroid multiplied by the polygon area.
    // Initializing a polygon with loops
    std::vector<S2Loop*> loops = { shell_loop, hole_loop };
    S2Polygon poly;
    poly.Init(&loops); // poly now owns the loops and reorders them
    
    // Accessing hierarchy
    int parent_idx = poly.GetParent(1);
    int last_desc = poly.GetLastDescendant(0);
  7. Use S2Point for 3D spherical coordinates

    master

    An S2Point represents a point on the unit sphere as a 3D vector. While points are usually normalized to unit length, some methods do not strictly require this.

    Common operations include:

    • Arithmetic: Overloaded operators allow for convenient arithmetic expressions like (1-x)*p1 + x*p2.
    • Equality Testing: Use S2::ApproxEquals(a, b, max_error) to check if two points are within a specific distance (default 1e-15).
    • Normalization Check: Use S2::IsUnitLength(p) to verify if a point is approximately unit length.
  8. Configure edge splicing in S2PolygonBuilder

    master

    The edge_splice_fraction determines how vertices near an edge are spliced into that edge.

    • Behavior: If positive, vertices closer than this fraction of the vertex merge radius are spliced into the edge. This can cause edges to move if there is a long chain of vertices.
    • Disabling: Set the value to 0 to turn off edge splicing. This saves time and prevents vertices from being spliced into nearby edges.
    • Constraints: The fraction must be less than sqrt(3)/2 to avoid infinite loops in the merge algorithm. The default value is near this maximum.
    • Direction Changes: A higher fraction allows more direction changes. The maximum change per splice is asin(edge_splice_fraction) (default allows up to 60 degrees).
    void set_edge_splice_fraction(double edge_splice_fraction)
  9. Build and install the C++ library and Python bindings

    master

    The Python bindings require a build of the underlying C++ library. Follow these steps to build the C++ core, then the Python bindings, and finally verify the installation.

    Note for OSX users: You must point to Brew's OpenSSL installation and explicitly tell CMake which Python libraries to use before running the build commands.

    OSX specific setup:

    export OPENSSL_ROOT_DIR=$(brew --prefix openssl)
    cmake -DPYTHON_LIBRARY=$(python-config --prefix)/lib/libpython2.7.dylib -DPYTHON_INCLUDE_DIR=$(python-config --prefix)/include/python2.7 .
    # build and install the C++ library
    cd tests/s2-geometry/geometry
    cmake .
    make -j4
    make install
    
    # build the C++ library's Python bindings
    cd python
    cmake .
    make -j4
    make install
    
    # verify Python bindings
    python -v -c 'import s2'
  10. Iterate through S2CellId children and ranges

    master

    You can traverse the hierarchy of cells using iterator-style methods on S2CellId.

    Iterating Children

    To iterate through the immediate children of a cell, use child_begin() and child_end(). Note that you must use the .next() method to advance the iterator to avoid confusion with incrementing the underlying 64-bit integer.

    for(S2CellId c = id.child_begin(); c != id.child_end(); c = c.next()) {
        // Process child c
    }

    Global and Level-based Iteration

    • Begin(int level) / End(int level): Iterates through all cells at a specific level across all 6 faces of the cube.
    • child_begin(int level) / child_end(int level): Iterates through children at a specific level.
    • next() / prev(): Moves to the next/previous cell at the same level along the Hilbert curve. These do not wrap around between faces.
    • next_wrap() / prev_wrap(): Moves to the next/previous cell and does wrap around between faces. Do not use these for standard iteration with child_begin()/child_end().
    • advance(int64 steps) / advance_wrap(int64 steps): Moves a specific number of steps along the curve.
    // Iterating children
    for(S2CellId c = id.child_begin(); c != id.child_end(); c = c.next()) {
        // ...
    }
    
    // Iterating all cells at a specific level
    for(S2CellId c = S2CellId::Begin(10); c != S2CellId::End(10); c = c.next()) {
        // ...
    }
  11. Implement and use S2EdgeIndex for fast edge intersection

    master

    The S2EdgeIndex class allows you to quickly find which edges in a set $S$ might intersect or touch a query edge $(a, b)$.

    Implementation

    To use it, you must subclass S2EdgeIndex and implement three virtual functions:

    1. num_edges()
    2. edge_from()
    3. edge_to()

    Optimization Tip: Instead of using the virtual edge_from() and edge_to() in your query loop, implement a non-virtual GetEdge(int index, S2Point const** from, S2Point const** to) in your subclass to avoid virtual function overhead.

    Usage Pattern

    Use the GetCandidates(a, b) iterator to retrieve potential matches.

    MyS2EdgeIndex edge_index; // Your subclass
    MyS2EdgeIndex::Iterator it(&edge_index);
    S2Point const *from, *to;
    
    // GetCandidates returns edges that might touch or cross (a, b)
    for (it.GetCandidates(a, b); !it.Done(); it.Next()) {
        edge_index.GetEdge(it.Index(), &from, &to);
        // Perform robust crossing check here
    }

    Performance Tuning

    If you know how many times you will call GetCandidates, use PredictAdditionalCalls(n) to help the index decide when to build its internal quad tree, preventing wasted time on brute-force searches.

    MyS2EdgeIndex edge_index;
    MyS2EdgeIndex::Iterator it(&edge_index);
    S2Point const* from;
    S2Point const* to;
    for (it.GetCandidates(a, b); !it.Done(); it.Next()) {
      edge_index.GetEdge(it.Index(), &from, &to);
      // ... RobustCrossing(a,b, from,to) ...
    }