Skip to content

Commit 37d5407

Browse files
author
peng.li24
committed
refactor: remove all _f32 suffixes — unified factory naming via overload
pycpp/geometry_py.h: - All factories now use the same name regardless of dtype: point(double,double) / point(float,float) (scalar overload) point(array_t<double>) / point(array_t<float>) (array overload) linestring(array_t<double>) / linestring(array_t<float>) polygon(array_t<double>) / polygon(array_t<float>) - Zero _f32 suffixes — dtype selected by input type in pybind11 overload resolution (array dtype for linestring/polygon, scalar vs array form for point) tests/module.cpp: - All factory bindings use same Python name (e.g. m.def("point", ...) bound 3 times with different signatures) - pybind11 overload resolution automatically picks the right C++ template instantiation based on input dtype tests/conftest.py: - make fixture: f32 factories pass np.float32 arrays to cpp.point/ cpp.linestring/cpp.polygon (same name, dtype selects overload) - CppFactory: same approach with numpy array dtype dispatch
1 parent 7bdec5a commit 37d5407

3 files changed

Lines changed: 77 additions & 101 deletions

File tree

pycpp/geometry_py.h

Lines changed: 41 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Pybind11 wrappers for shapelycpp geometry types.
22
//
3-
// Thin layer: accept Python types (py::list, scalars) → construct C++ geometry
4-
// types → call native methods → wrap results back to Python.
3+
// Thin layer: accept Python types (py::array_t<T>, scalars) → construct C++
4+
// geometry types → call native methods → wrap results back to Python.
55
//
6-
// Inspired by the numpypy/pycpp pattern. Each wrapper matches a shapely Python
7-
// API signature. Cross-type operations are exposed as free functions because
8-
// pybind11 cannot bind template member functions with mixed template args.
6+
// All factories are overloaded by coordinate type — no _f32 suffixes.
7+
// linestring/polygon/linearring use py::array_t<T> so pybind11 distinguishes
8+
// by array dtype. point has both scalar (x,y) and array ([x,y]) forms.
99

1010
#pragma once
1111

@@ -24,27 +24,12 @@
2424
namespace py = pybind11;
2525
using namespace shapely::geometry;
2626

27+
namespace shapely_py {
28+
2729
// ============================================================================
2830
// Internal helpers
2931
// ============================================================================
3032

31-
namespace shapely_py {
32-
33-
/// Convert Python list of (x,y) tuples to a flat coordinate array.
34-
template <typename T>
35-
py::array_t<T> py_coords_to_array(const py::list& py_coords) {
36-
py::ssize_t n = py::len(py_coords);
37-
auto arr = py::array_t<T>({n, static_cast<py::ssize_t>(2)});
38-
auto buf = arr.template request();
39-
T* ptr = static_cast<T*>(buf.ptr);
40-
for (py::ssize_t i = 0; i < n; ++i) {
41-
auto t = py_coords[i].cast<py::tuple>();
42-
ptr[i * 2] = t[0].cast<T>();
43-
ptr[i * 2 + 1] = t[1].cast<T>();
44-
}
45-
return arr;
46-
}
47-
4833
/// Copy native T* data to a Python numpy array.
4934
template <typename T>
5035
py::array_t<T> native_to_array(const T* data, size_t rows, size_t cols) {
@@ -54,86 +39,72 @@ py::array_t<T> native_to_array(const T* data, size_t rows, size_t cols) {
5439
return result;
5540
}
5641

57-
} // namespace shapely_py
58-
5942
// ============================================================================
60-
// Factory functions
43+
// Factory functions — all overloaded, no _f32 suffixes
6144
// ============================================================================
6245

63-
namespace shapely_py {
64-
46+
// -- Point: scalar (x,y) + array ([x,y]) forms --
6547
inline Point<double> point(double x, double y) { return Point<double>(x, y); }
66-
inline Point<float> point_f32(float x, float y) { return Point<float>(x, y); }
48+
inline Point<float> point(float x, float y) { return Point<float>(x, y); }
6749

68-
inline LineString<double> linestring(const py::list& coords) {
69-
auto arr = py_coords_to_array<double>(coords);
50+
inline Point<double> point(const py::array_t<double>& arr) {
7051
auto buf = arr.request();
71-
return LineString<double>(static_cast<const double*>(buf.ptr),
72-
buf.shape[0], buf.shape[1]);
52+
const double* p = static_cast<const double*>(buf.ptr);
53+
return Point<double>(buf.size > 0 ? p[0] : 0, buf.size > 1 ? p[1] : 0);
7354
}
74-
inline LineString<float> linestring_f32(const py::list& coords) {
75-
auto arr = py_coords_to_array<float>(coords);
55+
inline Point<float> point(const py::array_t<float>& arr) {
7656
auto buf = arr.request();
77-
return LineString<float>(static_cast<const float*>(buf.ptr),
78-
buf.shape[0], buf.shape[1]);
57+
const float* p = static_cast<const float*>(buf.ptr);
58+
return Point<float>(buf.size > 0 ? p[0] : 0, buf.size > 1 ? p[1] : 0);
7959
}
8060

81-
inline Polygon<double> polygon(const py::list& coords) {
82-
auto arr = py_coords_to_array<double>(coords);
61+
// -- LineString --
62+
inline LineString<double> linestring(const py::array_t<double>& arr) {
8363
auto buf = arr.request();
84-
return Polygon<double>(static_cast<const double*>(buf.ptr),
85-
buf.shape[0], buf.shape[1]);
64+
return LineString<double>(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
8665
}
87-
inline Polygon<float> polygon_f32(const py::list& coords) {
88-
auto arr = py_coords_to_array<float>(coords);
66+
inline LineString<float> linestring(const py::array_t<float>& arr) {
8967
auto buf = arr.request();
90-
return Polygon<float>(static_cast<const float*>(buf.ptr),
91-
buf.shape[0], buf.shape[1]);
68+
return LineString<float>(static_cast<const float*>(buf.ptr), buf.shape[0], buf.shape[1]);
9269
}
9370

94-
inline LinearRing<double> linearring(const py::list& coords) {
95-
auto arr = py_coords_to_array<double>(coords);
71+
// -- Polygon --
72+
inline Polygon<double> polygon(const py::array_t<double>& arr) {
9673
auto buf = arr.request();
97-
return LinearRing<double>(static_cast<const double*>(buf.ptr),
98-
buf.shape[0], buf.shape[1]);
74+
return Polygon<double>(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
75+
}
76+
inline Polygon<float> polygon(const py::array_t<float>& arr) {
77+
auto buf = arr.request();
78+
return Polygon<float>(static_cast<const float*>(buf.ptr), buf.shape[0], buf.shape[1]);
9979
}
10080

101-
} // namespace shapely_py
81+
// -- LinearRing (double only for now) --
82+
inline LinearRing<double> linearring(const py::array_t<double>& arr) {
83+
auto buf = arr.request();
84+
return LinearRing<double>(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
85+
}
10286

10387
// ============================================================================
104-
// Cross-type distance wrappers
88+
// Cross-type distance
10589
// ============================================================================
106-
// pybind11 cannot bind templated member functions with args of a different
107-
// template parameter (e.g. LineString<double>::distance(Polygon<double>)).
108-
// Expose these via free functions.
109-
110-
namespace shapely_py {
11190

11291
inline double distance_pt_ls(const Point<double>& p, const LineString<double>& l) { return p.distance(l); }
11392
inline double distance_pt_poly(const Point<double>& p, const Polygon<double>& poly) { return p.distance(poly); }
11493
inline double distance_ls_poly(const LineString<double>& l, const Polygon<double>& poly) { return l.distance(poly); }
11594
inline double distance_poly_ls(const Polygon<double>& poly, const LineString<double>& l) { return poly.distance(l); }
11695

117-
} // namespace shapely_py
118-
11996
// ============================================================================
120-
// Cross-type intersects wrappers
97+
// Cross-type intersects
12198
// ============================================================================
12299

123-
namespace shapely_py {
124-
125100
inline bool intersects_ls_poly(const LineString<double>& l, const Polygon<double>& poly) { return l.intersects(poly); }
126101
inline bool intersects_poly_ls(const Polygon<double>& poly, const LineString<double>& l) { return poly.intersects(l); }
127102
inline bool intersects_poly_poly(const Polygon<double>& p1, const Polygon<double>& p2) { return p1.intersects(p2); }
128103

129-
} // namespace shapely_py
130-
131104
// ============================================================================
132-
// Predicate wrappers (all cross-type pairs, float64)
105+
// Predicates — all 9 cross-type pairs
133106
// ============================================================================
134107

135-
namespace shapely_py {
136-
137108
// -- Point ↔ Point --
138109
inline bool pt_contains_pt(const Point<double>& s, const Point<double>& o) { return s.contains(o); }
139110
inline bool pt_within_pt(const Point<double>& s, const Point<double>& o) { return s.within(o); }
@@ -269,23 +240,19 @@ inline bool poly_intersects_poly(const Polygon<double>& s, const Polygon<double>
269240
inline std::string poly_relate_poly(const Polygon<double>& s, const Polygon<double>& o) { return s.relate(o); }
270241
inline double poly_hausdorff_distance_poly(const Polygon<double>& s, const Polygon<double>& o) { return s.hausdorff_distance(o); }
271242

272-
} // namespace shapely_py
273-
274243
// ============================================================================
275244
// Centroid, project, interpolate
276245
// ============================================================================
277246

278-
namespace shapely_py {
279-
280-
inline std::tuple<double, double> centroid_point(const Point<double>& p) { auto r = p.centroid(); return {r.x, r.y}; }
281-
inline std::tuple<double, double> centroid_linestring(const LineString<double>& l) { auto r = l.centroid(); return {r.x, r.y}; }
282-
inline std::tuple<double, double> centroid_polygon(const Polygon<double>& p) { auto r = p.centroid(); return {r.x, r.y}; }
247+
inline std::tuple<double,double> centroid_point(const Point<double>& p) { auto r=p.centroid(); return {r.x,r.y}; }
248+
inline std::tuple<double,double> centroid_linestring(const LineString<double>& l) { auto r=l.centroid(); return {r.x,r.y}; }
249+
inline std::tuple<double,double> centroid_polygon(const Polygon<double>& p) { auto r=p.centroid(); return {r.x,r.y}; }
283250

284251
inline double project_ls_pt(const LineString<double>& l, const Point<double>& p) { return l.project(p); }
285-
inline std::tuple<double, double> interpolate_ls(const LineString<double>& l, double d) { auto r = l.interpolate(d); return {r.x, r.y}; }
252+
inline std::tuple<double,double> interpolate_ls(const LineString<double>& l, double d) { auto r=l.interpolate(d); return {r.x,r.y}; }
286253

287254
inline double intersection_area_poly_poly(const Polygon<double>& p1, const Polygon<double>& p2) {
288-
auto inter = p1.intersection(p2); return inter.area();
255+
return p1.intersection(p2).area();
289256
}
290257

291258
inline py::array_t<double> polygon_exterior(const Polygon<double>& p) {
@@ -298,8 +265,6 @@ inline py::array_t<double> polygon_exterior(const Polygon<double>& p) {
298265
// ============================================================================
299266
// Pybind11 binding helper macros
300267
// ============================================================================
301-
// Use these in your PYBIND11_MODULE to consistently bind geometry classes
302-
// and cross-type predicates. See tests/module.cpp for usage examples.
303268

304269
#define BIND_PREDS(m, SRC, TGT) \
305270
m.def(#SRC "_contains_" #TGT, &shapely_py::SRC ## _contains_ ## TGT); \

tests/conftest.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,24 @@ def dtype(request):
6262

6363
@pytest.fixture(params=[np.float64, np.float32], ids=["float64", "float32"])
6464
def make(cpp, request):
65-
"""Parametrized fixture providing float64/float32 geometry factories and classes."""
65+
"""Parametrized fixture providing float64/float32 geometry factories and classes.
66+
67+
All factories use the SAME Python name (e.g. cpp.point) — dtype is selected
68+
by passing numpy arrays with the appropriate dtype. pybind11 overload
69+
resolution picks the matching C++ template instantiation."""
6670
if request.param == np.float64:
6771
return {
68-
'point': cpp.point, 'linestring': cpp.linestring, 'polygon': cpp.polygon,
72+
'point': lambda x, y: cpp.point(float(x), float(y)),
73+
'linestring': lambda coords: cpp.linestring(np.array(coords, dtype=np.float64)),
74+
'polygon': lambda coords: cpp.polygon(np.array(coords, dtype=np.float64)),
6975
'Point': cpp.Point, 'LineString': cpp.LineString, 'Polygon': cpp.Polygon,
7076
'dtype': np.float64,
7177
}
7278
else:
7379
return {
74-
'point': cpp.point_f32, 'linestring': cpp.linestring_f32, 'polygon': cpp.polygon_f32,
80+
'point': lambda x, y: cpp.point(np.array([x, y], dtype=np.float32)),
81+
'linestring': lambda coords: cpp.linestring(np.array(coords, dtype=np.float32)),
82+
'polygon': lambda coords: cpp.polygon(np.array(coords, dtype=np.float32)),
7583
'Point': cpp.PointF32, 'LineString': cpp.LineStringF32, 'Polygon': cpp.PolygonF32,
7684
'dtype': np.float32,
7785
}
@@ -82,16 +90,19 @@ class CppFactory:
8290
def __init__(self, cpp_mod):
8391
self.m = cpp_mod
8492
def point(self, x, y, dtype='double'):
85-
if dtype == 'float32': return self.m.point_f32(float(x), float(y))
93+
if dtype == 'float32':
94+
return self.m.point(np.array([float(x), float(y)], dtype=np.float32))
8695
return self.m.point(float(x), float(y))
8796
def linestring(self, coords, dtype='double'):
88-
if dtype == 'float32': return self.m.linestring_f32(coords)
89-
return self.m.linestring(coords)
97+
if dtype == 'float32':
98+
return self.m.linestring(np.array(coords, dtype=np.float32))
99+
return self.m.linestring(np.array(coords, dtype=np.float64))
90100
def polygon(self, coords, dtype='double'):
91-
if dtype == 'float32': return self.m.polygon_f32(coords)
92-
return self.m.polygon(coords)
101+
if dtype == 'float32':
102+
return self.m.polygon(np.array(coords, dtype=np.float32))
103+
return self.m.polygon(np.array(coords, dtype=np.float64))
93104
def linearring(self, coords):
94-
return self.m.linearring(coords)
105+
return self.m.linearring(np.array(coords, dtype=np.float64))
95106

96107
@pytest.fixture(scope='session')
97108
def C(cpp):

tests/module.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
// pybind11 test module for shapelycpp — demonstrates pycpp wrapper usage.
22
//
3-
// Includes all shapely geometry types, cross-type predicates, distance,
4-
// centroid, nearest_points, serialization, etc.
5-
//
6-
// Usage (consuming project):
7-
// #include "shapelycpp/pycpp/geometry_py.h"
8-
// #include "shapelycpp/pycpp/ops_py.h"
9-
// PYBIND11_MODULE(my_module, m) { ... bind classes + use BIND_PREDS macros ... }
3+
// All factories are bound under a single Python name per geometry type
4+
// (e.g. "point" handles float64 AND float32 via pybind11 overload resolution).
5+
// No _f32 suffixes anywhere — dtype is selected by the input array/numpy type.
106

117
#include <pybind11/pybind11.h>
128
#include <pybind11/numpy.h>
@@ -22,7 +18,7 @@ namespace py = pybind11;
2218
using namespace shapely::geometry;
2319
using namespace shapely_py;
2420

25-
// Re-export native_to_array for module-local use (templated — supports float/double)
21+
// Re-export native_to_array for module-local use (templated)
2622
namespace {
2723
template <typename T>
2824
py::array_t<T> _native_to_array(const T* data, size_t rows, size_t cols) {
@@ -33,14 +29,18 @@ py::array_t<T> _native_to_array(const T* data, size_t rows, size_t cols) {
3329
PYBIND11_MODULE(shapelycpp, m) {
3430
m.doc() = "shapelycpp test module (powered by pycpp wrappers)";
3531

36-
// -- Factories (from pycpp) --
37-
m.def("point", &point, py::arg("x"), py::arg("y"));
38-
m.def("point_f32", &point_f32, py::arg("x"), py::arg("y"));
39-
m.def("linestring", &linestring, py::arg("coords"));
40-
m.def("linestring_f32", &linestring_f32, py::arg("coords"));
41-
m.def("polygon", &polygon, py::arg("coords"));
42-
m.def("polygon_f32", &polygon_f32, py::arg("coords"));
43-
m.def("linearring", &linearring, py::arg("coords"));
32+
// -- Factories — all under unified names, dtype selected via input type --
33+
// point: scalar (x,y) → f64; array [x,y] with f32 dtype → f32
34+
m.def("point", py::overload_cast<double, double>(&point), py::arg("x"), py::arg("y"));
35+
m.def("point", py::overload_cast<const py::array_t<double>&>(&point), py::arg("coords"));
36+
m.def("point", py::overload_cast<const py::array_t<float>&>(&point), py::arg("coords"));
37+
38+
// linestring / polygon / linearring: array dtype selects f64 or f32
39+
m.def("linestring", py::overload_cast<const py::array_t<double>&>(&linestring), py::arg("coords"));
40+
m.def("linestring", py::overload_cast<const py::array_t<float>&>(&linestring), py::arg("coords"));
41+
m.def("polygon", py::overload_cast<const py::array_t<double>&>(&polygon), py::arg("coords"));
42+
m.def("polygon", py::overload_cast<const py::array_t<float>&>(&polygon), py::arg("coords"));
43+
m.def("linearring", py::overload_cast<const py::array_t<double>&>(&linearring), py::arg("coords"));
4444

4545
// ======================================================================
4646
// Point<double> — full API

0 commit comments

Comments
 (0)