Skip to content

Commit 9cab895

Browse files
author
peng.li24
committed
feat: add MultiPoint/MultiLineString/MultiPolygon pycpp wrappers with cross-type distance/intersects
1 parent 4d391bf commit 9cab895

7 files changed

Lines changed: 211 additions & 3 deletions

File tree

pycpp/geometry_py.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
#include "../shapely/geometry/point.h"
1717
#include "../shapely/geometry/linestring.h"
1818
#include "../shapely/geometry/polygon.h"
19+
#include "../shapely/geometry/multipoint.h"
20+
#include "../shapely/geometry/multilinestring.h"
21+
#include "../shapely/geometry/multipolygon.h"
22+
#include "../shapely/geometry/geometrycollection.h"
1923

2024
#include <cstring>
2125
#include <tuple>
@@ -84,6 +88,52 @@ inline LinearRing<double> linearring(const py::array_t<double>& arr) {
8488
return LinearRing<double>(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
8589
}
8690

91+
// -- MultiPoint: single array of shape (n_pts, 2) --
92+
inline MultiPoint<double> multipoint(const py::array_t<double>& arr) {
93+
auto buf = arr.request();
94+
return MultiPoint<double>(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
95+
}
96+
inline MultiPoint<float> multipoint(const py::array_t<float>& arr) {
97+
auto buf = arr.request();
98+
return MultiPoint<float>(static_cast<const float*>(buf.ptr), buf.shape[0], buf.shape[1]);
99+
}
100+
101+
// -- MultiLineString: vector of arrays, each (n_rows, 2); dtype distinguishes overload --
102+
inline MultiLineString<double> multilinestring(const std::vector<py::array_t<double>>& arrays) {
103+
MultiLineString<double> mls;
104+
for (auto& arr : arrays) {
105+
auto buf = arr.request();
106+
mls.add_line(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
107+
}
108+
return mls;
109+
}
110+
inline MultiLineString<float> multilinestring(const std::vector<py::array_t<float>>& arrays) {
111+
MultiLineString<float> mls;
112+
for (auto& arr : arrays) {
113+
auto buf = arr.request();
114+
mls.add_line(static_cast<const float*>(buf.ptr), buf.shape[0], buf.shape[1]);
115+
}
116+
return mls;
117+
}
118+
119+
// -- MultiPolygon: vector of arrays, each (n_rows, 2); dtype distinguishes overload --
120+
inline MultiPolygon<double> multipolygon(const std::vector<py::array_t<double>>& arrays) {
121+
MultiPolygon<double> mp;
122+
for (auto& arr : arrays) {
123+
auto buf = arr.request();
124+
mp.add_polygon(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
125+
}
126+
return mp;
127+
}
128+
inline MultiPolygon<float> multipolygon(const std::vector<py::array_t<float>>& arrays) {
129+
MultiPolygon<float> mp;
130+
for (auto& arr : arrays) {
131+
auto buf = arr.request();
132+
mp.add_polygon(static_cast<const float*>(buf.ptr), buf.shape[0], buf.shape[1]);
133+
}
134+
return mp;
135+
}
136+
87137
// ============================================================================
88138
// Cross-type distance
89139
// ============================================================================

shapely/geometry/linestring.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ class LineString {
155155
template <typename U> friend class Polygon;
156156
template <typename U> friend class Point;
157157
template <typename U> friend class LinearRing;
158+
template <typename U> friend class MultiPoint;
159+
template <typename U> friend class MultiLineString;
160+
template <typename U> friend class MultiPolygon;
158161
std::vector<T> coords_;
159162
size_t rows_ = 0, cols_ = 0;
160163
std::unique_ptr<geos::geom::LineString> geos_linestring_;

shapely/geometry/multipoint.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,8 @@ MultiPoint<T>::MultiPoint(const T* coords, size_t n_pts, size_t dims)
160160
factory_ = geos::geom::GeometryFactory::create();
161161
std::vector<std::unique_ptr<geos::geom::Geometry>> pts;
162162
for (size_t i = 0; i < n_pts; ++i) {
163-
auto pt = factory_->createPoint(geos::geom::Coordinate(
164-
static_cast<double>(coords_[i*dims]), static_cast<double>(coords_[i*dims+1])));
165-
pts.push_back(std::move(pt));
163+
pts.emplace_back(factory_->createPoint(geos::geom::Coordinate(
164+
static_cast<double>(coords_[i*dims]), static_cast<double>(coords_[i*dims+1]))));
166165
}
167166
geos_multipoint_ = factory_->createMultiPoint(std::move(pts));
168167
}

shapely/geometry/point.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ class Point {
143143
template <typename U> friend class LineString;
144144
template <typename U> friend class Polygon;
145145
template <typename U> friend class Point;
146+
template <typename U> friend class MultiPoint;
147+
template <typename U> friend class MultiLineString;
148+
template <typename U> friend class MultiPolygon;
146149
std::unique_ptr<geos::geom::Point> geos_point_;
147150
geos::geom::GeometryFactory::Ptr factory_;
148151
};

shapely/geometry/polygon.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ class LinearRing {
159159
template <typename U> friend class Polygon;
160160
template <typename U> friend class LineString;
161161
template <typename U> friend class Point;
162+
template <typename U> friend class MultiPoint;
163+
template <typename U> friend class MultiLineString;
164+
template <typename U> friend class MultiPolygon;
162165
std::vector<T> coords_;
163166
size_t rows_ = 0, cols_ = 0;
164167
std::unique_ptr<geos::geom::LinearRing> geos_ring_;
@@ -300,6 +303,9 @@ class Polygon {
300303
template <typename U> friend class LineString;
301304
template <typename U> friend class Point;
302305
template <typename U> friend class LinearRing;
306+
template <typename U> friend class MultiPoint;
307+
template <typename U> friend class MultiLineString;
308+
template <typename U> friend class MultiPolygon;
303309
std::vector<T> coords_;
304310
size_t rows_ = 0, cols_ = 0;
305311
std::unique_ptr<geos::geom::Polygon> geos_polygon_;

tests/conftest.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,23 @@ def make(cpp, request):
7272
'point': lambda x, y: cpp.point(float(x), float(y)),
7373
'linestring': lambda coords: cpp.linestring(np.array(coords, dtype=np.float64)),
7474
'polygon': lambda coords: cpp.polygon(np.array(coords, dtype=np.float64)),
75+
'multipoint': lambda coords: cpp.multipoint(np.array(coords, dtype=np.float64)),
76+
'multilinestring': lambda lines: cpp.multilinestring([np.array(l, dtype=np.float64) for l in lines]),
77+
'multipolygon': lambda polys: cpp.multipolygon([np.array(p, dtype=np.float64) for p in polys]),
7578
'Point': cpp.Point, 'LineString': cpp.LineString, 'Polygon': cpp.Polygon,
79+
'MultiPoint': cpp.MultiPoint, 'MultiLineString': cpp.MultiLineString, 'MultiPolygon': cpp.MultiPolygon,
7680
'dtype': np.float64,
7781
}
7882
else:
7983
return {
8084
'point': lambda x, y: cpp.point(np.array([x, y], dtype=np.float32)),
8185
'linestring': lambda coords: cpp.linestring(np.array(coords, dtype=np.float32)),
8286
'polygon': lambda coords: cpp.polygon(np.array(coords, dtype=np.float32)),
87+
'multipoint': lambda coords: cpp.multipoint(np.array(coords, dtype=np.float32)),
88+
'multilinestring': lambda lines: cpp.multilinestring([np.array(l, dtype=np.float32) for l in lines]),
89+
'multipolygon': lambda polys: cpp.multipolygon([np.array(p, dtype=np.float32) for p in polys]),
8390
'Point': cpp.PointF32, 'LineString': cpp.LineStringF32, 'Polygon': cpp.PolygonF32,
91+
'MultiPoint': cpp.MultiPoint, 'MultiLineString': cpp.MultiLineString, 'MultiPolygon': cpp.MultiPolygon,
8492
'dtype': np.float32,
8593
}
8694

@@ -103,6 +111,18 @@ def polygon(self, coords, dtype='double'):
103111
return self.m.polygon(np.array(coords, dtype=np.float64))
104112
def linearring(self, coords):
105113
return self.m.linearring(np.array(coords, dtype=np.float64))
114+
def multipoint(self, coords, dtype='double'):
115+
if dtype == 'float32':
116+
return self.m.multipoint(np.array(coords, dtype=np.float32))
117+
return self.m.multipoint(np.array(coords, dtype=np.float64))
118+
def multilinestring(self, lines, dtype='double'):
119+
if dtype == 'float32':
120+
return self.m.multilinestring([np.array(l, dtype=np.float32) for l in lines])
121+
return self.m.multilinestring([np.array(l, dtype=np.float64) for l in lines])
122+
def multipolygon(self, polys, dtype='double'):
123+
if dtype == 'float32':
124+
return self.m.multipolygon([np.array(p, dtype=np.float32) for p in polys])
125+
return self.m.multipolygon([np.array(p, dtype=np.float64) for p in polys])
106126

107127
@pytest.fixture(scope='session')
108128
def C(cpp):

tests/module.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,133 @@ PYBIND11_MODULE(shapelycpp, m) {
165165
.def("is_ccw", &LinearRing<double>::is_ccw)
166166
BIND_ACCESSORS(LinearRing<double>);
167167

168+
// -- Multi* factories (multipoint overloads by array dtype; multilinestring/multipolygon by vector<array_t<T>>) --
169+
m.def("multipoint", py::overload_cast<const py::array_t<double>&>(&multipoint), py::arg("coords"));
170+
m.def("multipoint", py::overload_cast<const py::array_t<float>&>(&multipoint), py::arg("coords"));
171+
m.def("multilinestring", py::overload_cast<const std::vector<py::array_t<double>>&>(&multilinestring), py::arg("lines"));
172+
m.def("multilinestring", py::overload_cast<const std::vector<py::array_t<float>>&>(&multilinestring), py::arg("lines"));
173+
m.def("multipolygon", py::overload_cast<const std::vector<py::array_t<double>>&>(&multipolygon), py::arg("polygons"));
174+
m.def("multipolygon", py::overload_cast<const std::vector<py::array_t<float>>&>(&multipolygon), py::arg("polygons"));
175+
176+
// ======================================================================
177+
// MultiPoint<double> — full API
178+
// ======================================================================
179+
py::class_<MultiPoint<double>>(m, "MultiPoint")
180+
.def(py::init([](const py::array_t<double>& arr) {
181+
auto buf = arr.request();
182+
return new MultiPoint<double>(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
183+
}), py::arg("coords"))
184+
.def("num_geometries", &MultiPoint<double>::num_geometries)
185+
.def("geometry_n", &MultiPoint<double>::geometry_n)
186+
.def("distance", [](const MultiPoint<double>& self, const Point<double>& o) {
187+
return self.distance(o);
188+
})
189+
.def("distance", [](const MultiPoint<double>& self, const MultiPoint<double>& o) {
190+
return self.distance(o);
191+
})
192+
.def("intersects", [](const MultiPoint<double>& self, const Point<double>& o) {
193+
return self.intersects(o);
194+
})
195+
.def("intersects", [](const MultiPoint<double>& self, const MultiPoint<double>& o) {
196+
return self.intersects(o);
197+
})
198+
BIND_ACCESSORS(MultiPoint<double>);
199+
200+
// ======================================================================
201+
// MultiLineString<double> — full API
202+
// ======================================================================
203+
py::class_<MultiLineString<double>>(m, "MultiLineString")
204+
.def(py::init([](const std::vector<py::array_t<double>>& arrays) {
205+
auto* mls = new MultiLineString<double>();
206+
for (auto& arr : arrays) {
207+
auto buf = arr.request();
208+
mls->add_line(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
209+
}
210+
return mls;
211+
}), py::arg("lines"))
212+
.def("num_geometries", &MultiLineString<double>::num_geometries)
213+
.def("geometry_n", &MultiLineString<double>::geometry_n)
214+
.def("add_line", [](MultiLineString<double>& self, const py::array_t<double>& arr) {
215+
auto buf = arr.request();
216+
self.add_line(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
217+
})
218+
.def("distance", [](const MultiLineString<double>& self, const Point<double>& o) {
219+
return self.distance(o);
220+
})
221+
.def("distance", [](const MultiLineString<double>& self, const LineString<double>& o) {
222+
return self.distance(o);
223+
})
224+
.def("distance", [](const MultiLineString<double>& self, const MultiLineString<double>& o) {
225+
return self.distance(o);
226+
})
227+
.def("intersects", [](const MultiLineString<double>& self, const Point<double>& o) {
228+
return self.intersects(o);
229+
})
230+
.def("intersects", [](const MultiLineString<double>& self, const LineString<double>& o) {
231+
return self.intersects(o);
232+
})
233+
.def("intersects", [](const MultiLineString<double>& self, const MultiLineString<double>& o) {
234+
return self.intersects(o);
235+
})
236+
BIND_ACCESSORS(MultiLineString<double>);
237+
238+
// ======================================================================
239+
// MultiPolygon<double> — full API
240+
// ======================================================================
241+
py::class_<MultiPolygon<double>>(m, "MultiPolygon")
242+
.def(py::init([](const std::vector<py::array_t<double>>& arrays) {
243+
auto* mp = new MultiPolygon<double>();
244+
for (auto& arr : arrays) {
245+
auto buf = arr.request();
246+
mp->add_polygon(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
247+
}
248+
return mp;
249+
}), py::arg("polygons"))
250+
.def("num_geometries", &MultiPolygon<double>::num_geometries)
251+
.def("geometry_n", &MultiPolygon<double>::geometry_n)
252+
.def("add_polygon", [](MultiPolygon<double>& self, const py::array_t<double>& arr) {
253+
auto buf = arr.request();
254+
self.add_polygon(static_cast<const double*>(buf.ptr), buf.shape[0], buf.shape[1]);
255+
})
256+
.def("distance", [](const MultiPolygon<double>& self, const Point<double>& o) {
257+
return self.distance(o);
258+
})
259+
.def("distance", [](const MultiPolygon<double>& self, const LineString<double>& o) {
260+
return self.distance(o);
261+
})
262+
.def("distance", [](const MultiPolygon<double>& self, const Polygon<double>& o) {
263+
return self.distance(o);
264+
})
265+
.def("distance", [](const MultiPolygon<double>& self, const MultiPolygon<double>& o) {
266+
return self.distance(o);
267+
})
268+
.def("intersects", [](const MultiPolygon<double>& self, const Point<double>& o) {
269+
return self.intersects(o);
270+
})
271+
.def("intersects", [](const MultiPolygon<double>& self, const LineString<double>& o) {
272+
return self.intersects(o);
273+
})
274+
.def("intersects", [](const MultiPolygon<double>& self, const Polygon<double>& o) {
275+
return self.intersects(o);
276+
})
277+
.def("intersects", [](const MultiPolygon<double>& self, const MultiPolygon<double>& o) {
278+
return self.intersects(o);
279+
})
280+
BIND_ACCESSORS(MultiPolygon<double>);
281+
282+
// ======================================================================
283+
// GeometryCollection — basic API
284+
// ======================================================================
285+
py::class_<GeometryCollection>(m, "GeometryCollection")
286+
.def(py::init<>())
287+
.def("num_geometries", &GeometryCollection::num_geometries)
288+
.def("is_empty", &GeometryCollection::is_empty)
289+
.def("area", &GeometryCollection::area)
290+
.def("length", &GeometryCollection::length)
291+
.def("wkt", &GeometryCollection::wkt)
292+
.def("buffer", &GeometryCollection::buffer)
293+
.def("normalize", &GeometryCollection::normalize);
294+
168295
// ======================================================================
169296
// Cross-type predicates (via pycpp macros)
170297
// ======================================================================

0 commit comments

Comments
 (0)