Skip to content

pybind: Add S1Angle bindings#562

Open
deustis wants to merge 3 commits intogoogle:masterfrom
deustis:deustis/s1angle_bindings
Open

pybind: Add S1Angle bindings#562
deustis wants to merge 3 commits intogoogle:masterfrom
deustis:deustis/s1angle_bindings

Conversation

@deustis
Copy link
Copy Markdown
Contributor

@deustis deustis commented Apr 7, 2026

Add pybind11 bindings for S1Angle.

Also add S1Angle::IsNormalizedAngle() to s1angle.h for pre-validating
E5/E6/E7 conversions.

Supports downstream PRs for S2LatLng (deustis#2) and
S2CellId (deustis#3).

(Part of a series of addressing #524)

@deustis
Copy link
Copy Markdown
Contributor Author

deustis commented Apr 9, 2026

@jmr, ready for you review. Not sure why the CI is failing

Comment thread src/s2/s1angle.h Outdated
Comment thread src/python/s1angle_bindings.cc Outdated
Comment thread src/python/s1angle_bindings.cc Outdated
@deustis deustis requested a review from jmr April 10, 2026 15:33
@deustis
Copy link
Copy Markdown
Contributor Author

deustis commented Apr 15, 2026

@jmr, I believe I've addressed your comments!

Copy link
Copy Markdown
Member

@jmr jmr left a comment

Choose a reason for hiding this comment

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

Sorry, I noticed a few more things.

Comment thread src/python/s1angle_test.py Outdated

def test_repr(self):
a = s2.S1Angle.from_degrees(45.0)
self.assertEqual(repr(a), "S1Angle(45.0000000)")
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.

hmm. add assertEqual(a, S1Angle(45.0000000)) here. I don't think we have a float constructor. Use from_degrees and ideally control the number of trailing 0s.

Copy link
Copy Markdown
Contributor Author

@deustis deustis Apr 21, 2026

Choose a reason for hiding this comment

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

Not quite sure if this addresses your comment, but added test variations that show the input precision doesn't affect the output precision (since that's controlled by the C++ implementation)

Comment thread src/python/s1angle_test.py Outdated

def test_e5(self):
a = s2.S1Angle.from_degrees(45.0)
self.assertEqual(a.e5(), 4500000)
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.

I think all these eN / radians / degrees should be properties.

Copy link
Copy Markdown
Contributor Author

@deustis deustis Apr 21, 2026

Choose a reason for hiding this comment

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

radians, degrees, e5, e6, e7 are now properties. Updated the README property rule to cover simple accessors including trivial unit conversions.

"Raises ValueError if out of range.")
.def("__abs__", &S1Angle::abs,
"Return the absolute value of the angle")
.def("normalized", &S1Angle::Normalized,
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.

might be worthwhile to expose Normalize()

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.

IsNormalized, too

Copy link
Copy Markdown
Contributor Author

@deustis deustis Apr 21, 2026

Choose a reason for hiding this comment

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

Exposed both is_normalized() and normalize()

Comment thread src/python/module.cc
bind_r2rect(m);
bind_s1interval(m);
bind_s2point(m);
bind_s1angle(m);
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.

This says "dependency order", but eithe make it alphabetic if this has no deps, or comment what the dep is.

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.

Reorganized into dependency tranches with per-line comments (e.g. // Deps: s2point).

Comment thread src/python/s1angle_bindings.cc Outdated

void MaybeThrowNotNormalized(const S1Angle& angle) {
if (!angle.IsNormalized()) {
throw py::value_error("Angle " + std::to_string(angle.degrees()) +
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.

We should avoid std::to_string since it uses locale. (I missed this in a previous PR.) Use absl::StrCat here instead.

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 — switched to absl::StrCat in both s1angle_bindings.cc and s1interval_bindings.cc.

"Raises ValueError if out of range.")
.def("__abs__", &S1Angle::abs,
"Return the absolute value of the angle")
.def("normalized", &S1Angle::Normalized,
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.

IsNormalized, too

@jmr
Copy link
Copy Markdown
Member

jmr commented Apr 17, 2026

Supports downstream PRs for S2LatLng (deustis#2) and
S2CellId (deustis#3).

GH now supports stacked PRs. I'm not sure how well they work, or if they would be helpful in this case.

Comment thread src/python/s1angle_test.py Outdated

def test_sin(self):
a = s2.S1Angle.from_degrees(90.0)
self.assertAlmostEqual(s2.sin(a), 1.0)
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.

Is this is going to be a problem when we have S1ChordAngle.sin?

Will s2.sin have type cases to handle both types?

Maybe better to go with a.sin(). Writing s2.sin(a) doesn't seem that natural anyway.

Copy link
Copy Markdown
Contributor Author

@deustis deustis Apr 21, 2026

Choose a reason for hiding this comment

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

Moved sin/cos/tan from module-level functions to instance methods (a.sin(), a.cos(), a.tan()). This avoids the S1ChordAngle collision and reads more naturally.

@deustis
Copy link
Copy Markdown
Contributor Author

deustis commented Apr 21, 2026

Supports downstream PRs for S2LatLng (deustis#2) and
S2CellId (deustis#3).

GH now supports stacked PRs. I'm not sure how well they work, or if they would be helpful in this case.

Thanks... we'll just take these one at a time

- Make radians/degrees/e5/e6/e7 properties instead of methods
- Expose IsNormalized() and Normalize()
- Replace std::to_string with absl::StrCat (locale safety)
- Move sin/cos/tan from module-level functions to instance methods
- Organize module.cc into dependency tranches with comments
- Update README property rule
@deustis deustis force-pushed the deustis/s1angle_bindings branch from 77385b2 to 34c27c7 Compare April 21, 2026 16:27
@deustis
Copy link
Copy Markdown
Contributor Author

deustis commented Apr 21, 2026

@jmr, PTAL!

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