Skip to content

pybind: Add S2Cell bindings#631

Open
deustis wants to merge 12 commits into
google:masterfrom
deustis:deustis/s2cell_bindings
Open

pybind: Add S2Cell bindings#631
deustis wants to merge 12 commits into
google:masterfrom
deustis:deustis/s2cell_bindings

Conversation

@deustis
Copy link
Copy Markdown
Contributor

@deustis deustis commented May 26, 2026

Adds pybind11 bindings for S2Cell, covering constructors, factory methods, properties, geometric operations, containment/intersection tests, distance queries, and traversal.

Naming: GetDistance is overloaded in C++ for S2Point, edge (a, b), and S2Cell arguments. Per the binding interface notes, the short name (get_distance) is used for the S2Point overload, and longer names (get_distance_to_edge, get_distance_to_cell) for the others. get_max_distance follows the same pattern.

Omitted::

  • _raw variants:** vertex_raw, edge_raw, and center_raw return unnormalized vectors intended for feeding into exact C++ predicates. Python callers won't do that, so these are dropped in favor of the normalized versions.
  • is_distance_less / is_distance_less_or_equal family is omitted — callers can use get_distance(...) < limit directly.
  • get_cap_bound and get_rect_bound are deferred until S2Cap and S2LatLngRect are bound.
  • Encode / Decode are intentionally omitted per the serialization policy in the pybind README.

(Part of a series addressing #522)

Test plan

bazel test //python/... — all suites pass.

deustis added 6 commits May 26, 2026 15:17
Binds S2Cell's constructors, factory methods, geometric accessors
(vertex, edge, center, area), boundary helpers, containment and
intersection predicates, and subdivide. Adds 43 unit tests.

Distance methods and the S2Cap/S2LatLngRect-returning region methods
are deferred with a TODO; they depend on S1ChordAngle/S2Cap/S2LatLngRect
bindings which are not yet in place.

(Stacked on deustis/s2cell_id_bindings / PR google#593.)
- Drop MaybeThrowNotValidCellId from S2Cell(S2CellId) constructor; all
  S2CellId objects reachable from Python are already validated
- Remove instance average_area(); keep only static average_area_for_level()
- Add S2CellId::kMaxPosition constant and validate pos in from_face_pos_level
  for both S2CellId and S2Cell bindings
- Switch constants to cls.attr() pattern in both S2CellId and S2Cell bindings;
  update README to document this style
- Fix BUILD: add absl/strings dep to s2cell_id_bindings
- Remove misleading encode/decode comment from s2cell_bindings.cc
- Fix magic constant 1<<30 -> 1<<S2CellId.MAX_LEVEL in s2cell_test.py
Consistent with other MaybeThrow* helpers in both binding files.
- Fix s2cell deps comment in module.cc to include s1chord_angle
- Clarify orientation docstring (bitmask, values 0-3)
- Drop _raw variants (vertex_raw, edge_raw, center_raw): Python callers
  don't feed unnormalized vectors into exact C++ predicates
@deustis
Copy link
Copy Markdown
Contributor Author

deustis commented May 26, 2026

@jmr, I've done a few rounds of self/LLM review here. Happy to make any changes as usual.

Comment thread src/python/s2cell_bindings.cc Outdated
.def(py::self <= py::self, "Compare cells by their cell id")
.def(py::self >= py::self, "Compare cells by their cell id")
.def("__hash__", [](const S2Cell& self) {
return absl::Hash<S2CellId>()(self.id());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use absl::HashOf(self). add include and deps

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread src/python/s2cell_bindings.cc Outdated
"Return true if this is a leaf cell (level == kMaxLevel)")

// Geometric operations
.def("get_size_ij", &S2Cell::GetSizeIJ,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_ prefix should be removed in the python bindings. Add this to the readme and look for other places I forgot about this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Also caught the same issue in s2cell_id_bindings.cc and s2latlng_bindings.cc (separate PR: deustis/drop_get_prefix). Added a note to README: "The Get prefix is dropped."

Comment thread src/python/s2cell_bindings.cc Outdated
});

// Edge boundary constants (from S2Cell::Boundary enum).
cls.attr("BOTTOM_EDGE") = static_cast<int>(S2Cell::kBottomEdge);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use py::enum_?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Used py::arithmetic() so existing integer comparisons still work.


namespace {

void MaybeThrowFaceOutOfRange(int face) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment that these are duplicated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"Construct a leaf cell containing the given point.\n\n"
"The point does not need to be normalized.")
.def(py::init([](const S2LatLng& ll) {
return S2Cell(ll);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are all latlngs valid? i don't see the same comment as for cell id

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All S2LatLng objects reachable from Python are guaranteed valid. The bindings validate all constructors and factory methods and throw ValueError for out-of-range inputs. Added a comment matching the one on the S2CellId constructor.

py::arg("point"),
"Construct a leaf cell containing the given point.\n\n"
"The point does not need to be normalized.")
.def(py::init([](const S2LatLng& ll) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look for functions with missing test coverage

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added tests for all seven distance methods: distance (point), boundary_distance, max_distance (point), distance_to_edge, max_distance_to_edge, distance_to_cell, and max_distance_to_cell.

@jmr
Copy link
Copy Markdown
Member

jmr commented May 29, 2026

Tests are failing.

deustis added 3 commits May 29, 2026 15:59
- Drop get_ prefix from all Python method names across S2Cell, S2CellId,
  and S2LatLng bindings; document convention in README
- Use py::enum_<S2Cell::Boundary> with py::arithmetic() instead of raw
  cls.attr() integer assignments
- Use absl::HashOf in __hash__; add absl/hash include and BUILD dep
- Add validity comment on S2LatLng constructor; comment duplicated helpers
- Add tests for all seven distance methods on S2Cell
@deustis deustis requested a review from jmr May 29, 2026 16:15
@deustis
Copy link
Copy Markdown
Contributor Author

deustis commented May 29, 2026

Tests are failing.

Fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants