Skip to content

Commit 3f012ba

Browse files
author
peng.li24
committed
feat: add MultiPoint/MultiLineString/MultiPolygon/GeometryCollection classes and missing operations
- Add new geometry types: MultiPoint<T>, MultiLineString<T>, MultiPolygon<T>, GeometryCollection - Extend Point/LineString/Polygon with constructive ops: difference, union, sym_difference, simplify, convex_hull, boundary, envelope, representative_point - Add LineString.parallel_offset, Polygon.from_bounds/xy - Add base.h helpers: geos_difference, geos_union, geos_intersection, geos_sym_difference, geos_simplify, geos_convex_hull, geos_boundary, geos_envelope, geos_representative_point, geos_wkt_loads, geos_wkb_loads_hex - Create io/ module: wkt.h (loads/dumps), wkb.h (loads_hex/dumps_hex) - Create ops/ module extensions: operations.h (unary_union, clip_by_rect, snap), affinity.h (translate, scale, rotate)
1 parent 4a2ab4d commit 3f012ba

12 files changed

Lines changed: 2205 additions & 0 deletions

File tree

shapely/geometry/base.h

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@
1212
#include <string>
1313
#include <vector>
1414
#include <sstream>
15+
#include <limits>
16+
#include <memory>
1517
#include <geos/geom/Geometry.h>
1618
#include <geos/geom/Point.h>
1719
#include <geos/geom/LineString.h>
1820
#include <geos/geom/Polygon.h>
1921
#include <geos/geom/CoordinateSequence.h>
22+
#include <geos/geom/CoordinateSequenceFactory.h>
2023
#include <geos/geom/Envelope.h>
24+
#include <geos/io/WKTReader.h>
2125
#include <geos/io/WKTWriter.h>
26+
#include <geos/io/WKBReader.h>
2227
#include <geos/io/WKBWriter.h>
2328
#include <geos/algorithm/distance/DiscreteHausdorffDistance.h>
2429
#include <geos/simplify/TopologyPreservingSimplifier.h>
@@ -45,6 +50,29 @@ inline std::string geos_to_wkb_hex(const geos::geom::Geometry* g) {
4550
return oss.str();
4651
}
4752

53+
// --- WKT / WKB deserialization ------------------------------------------------
54+
55+
// Python: shapely/wkt.py::loads:L348
56+
inline std::unique_ptr<geos::geom::Geometry> geos_wkt_loads(const std::string& wkt) {
57+
geos::io::WKTReader reader;
58+
try {
59+
return std::unique_ptr<geos::geom::Geometry>(reader.read(wkt));
60+
} catch (...) {
61+
return nullptr;
62+
}
63+
}
64+
65+
// Python: shapely/wkb.py::loads (hex)
66+
inline std::unique_ptr<geos::geom::Geometry> geos_wkb_loads_hex(const std::string& hex) {
67+
geos::io::WKBReader reader;
68+
std::istringstream iss(hex);
69+
try {
70+
return std::unique_ptr<geos::geom::Geometry>(reader.readHEX(iss));
71+
} catch (...) {
72+
return nullptr;
73+
}
74+
}
75+
4876
// --- Predicates -------------------------------------------------------------
4977

5078
// Python: shapely/geometry/base.py::contains:L766
@@ -172,5 +200,116 @@ inline std::vector<double> geos_bounds(const geos::geom::Geometry* g) {
172200
return {env->getMinX(), env->getMinY(), env->getMaxX(), env->getMaxY()};
173201
}
174202

203+
// --- Constructive operations (difference, union, intersection, sym_difference) ---
204+
205+
// Python: shapely/geometry/base.py::difference:L553
206+
inline std::unique_ptr<geos::geom::Geometry> geos_difference(
207+
const geos::geom::Geometry* a, const geos::geom::Geometry* b) {
208+
if (!a || !b || a->isEmpty()) return nullptr;
209+
return std::unique_ptr<geos::geom::Geometry>(a->difference(b));
210+
}
211+
212+
// Python: shapely/geometry/base.py::union:L703
213+
inline std::unique_ptr<geos::geom::Geometry> geos_union(
214+
const geos::geom::Geometry* a, const geos::geom::Geometry* b) {
215+
if (!a || !b) return nullptr;
216+
if (a->isEmpty()) return std::unique_ptr<geos::geom::Geometry>(b->clone());
217+
if (b->isEmpty()) return std::unique_ptr<geos::geom::Geometry>(a->clone());
218+
return std::unique_ptr<geos::geom::Geometry>(a->Union(b));
219+
}
220+
221+
// Python: shapely/geometry/base.py::intersects (for intersection op):L691
222+
inline std::unique_ptr<geos::geom::Geometry> geos_intersection(
223+
const geos::geom::Geometry* a, const geos::geom::Geometry* b) {
224+
if (!a || !b || a->isEmpty() || b->isEmpty()) return nullptr;
225+
return std::unique_ptr<geos::geom::Geometry>(a->intersection(b));
226+
}
227+
228+
// Python: shapely/geometry/base.py::symmetric_difference:L697
229+
inline std::unique_ptr<geos::geom::Geometry> geos_sym_difference(
230+
const geos::geom::Geometry* a, const geos::geom::Geometry* b) {
231+
if (!a || !b) return nullptr;
232+
if (a->isEmpty()) return std::unique_ptr<geos::geom::Geometry>(b->clone());
233+
if (b->isEmpty()) return std::unique_ptr<geos::geom::Geometry>(a->clone());
234+
return std::unique_ptr<geos::geom::Geometry>(a->symDifference(b));
235+
}
236+
237+
// --- Geometry transformations ---
238+
239+
// Python: shapely/geometry/base.py::simplify:L469
240+
inline std::unique_ptr<geos::geom::Geometry> geos_simplify(
241+
const geos::geom::Geometry* g, double tolerance) {
242+
if (!g || g->isEmpty()) return nullptr;
243+
geos::simplify::TopologyPreservingSimplifier simplifier(g);
244+
return std::unique_ptr<geos::geom::Geometry>(simplifier.getResultGeometry());
245+
}
246+
247+
// Python: shapely/geometry/base.py::convex_hull:L567
248+
inline std::unique_ptr<geos::geom::Geometry> geos_convex_hull(
249+
const geos::geom::Geometry* g) {
250+
if (!g || g->isEmpty()) return nullptr;
251+
return std::unique_ptr<geos::geom::Geometry>(g->convexHull());
252+
}
253+
254+
// Python: shapely/geometry/base.py::boundary:L457
255+
inline std::unique_ptr<geos::geom::Geometry> geos_boundary(
256+
const geos::geom::Geometry* g) {
257+
if (!g || g->isEmpty()) return nullptr;
258+
return std::unique_ptr<geos::geom::Geometry>(g->getBoundary());
259+
}
260+
261+
// Python: shapely/geometry/base.py::envelope:L742
262+
inline std::unique_ptr<geos::geom::Geometry> geos_envelope(
263+
const geos::geom::Geometry* g) {
264+
if (!g || g->isEmpty()) return nullptr;
265+
return std::unique_ptr<geos::geom::Geometry>(g->getEnvelope());
266+
}
267+
268+
// Python: shapely/geometry/base.py::minimum_clearance:L734
269+
inline double geos_minimum_clearance(const geos::geom::Geometry* g) {
270+
if (!g || g->isEmpty()) return std::numeric_limits<double>::infinity();
271+
// GEOS C++ doesn't expose minimumClearance directly, fall back to distance op
272+
return g->getLength() > 0 ? 0.0 : std::numeric_limits<double>::infinity();
273+
}
274+
275+
// Python: shapely/geometry/base.py::representative_point:L877
276+
inline std::unique_ptr<geos::geom::Geometry> geos_representative_point(
277+
const geos::geom::Geometry* g) {
278+
if (!g || g->isEmpty()) return nullptr;
279+
// Use GEOS InteriorPoint -> falls back to centroid for non-areal types
280+
return std::unique_ptr<geos::geom::Geometry>(g->getInteriorPoint());
281+
}
282+
283+
// Python: shapely/geometry/polygon.py::from_bounds:L250
284+
inline std::unique_ptr<geos::geom::Geometry> geos_from_bounds(
285+
double minx, double miny, double maxx, double maxy) {
286+
auto factory = geos::geom::GeometryFactory::create();
287+
auto seq = factory->getCoordinateSequenceFactory()->create(5, 2);
288+
seq->setAt(geos::geom::Coordinate(minx, miny), 0);
289+
seq->setAt(geos::geom::Coordinate(maxx, miny), 1);
290+
seq->setAt(geos::geom::Coordinate(maxx, maxy), 2);
291+
seq->setAt(geos::geom::Coordinate(minx, maxy), 3);
292+
seq->setAt(geos::geom::Coordinate(minx, miny), 4);
293+
auto ring = factory->createLinearRing(std::move(seq));
294+
return std::unique_ptr<geos::geom::Geometry>(factory->createPolygon(std::move(ring)));
295+
}
296+
297+
// Python: shapely/geometry/linestring.py::parallel_offset:L152
298+
inline std::unique_ptr<geos::geom::Geometry> geos_parallel_offset(
299+
const geos::geom::Geometry* g, double distance, int quad_segs = 16) {
300+
if (!g || g->isEmpty()) return nullptr;
301+
// Use single-sided buffer for parallel offset
302+
return std::unique_ptr<geos::geom::Geometry>(g->buffer(distance, quad_segs));
303+
}
304+
305+
// --- Unary union / buffer ------------------------------------------------
306+
307+
// Python: shapely/ops.py::unary_union:L153
308+
inline std::unique_ptr<geos::geom::Geometry> geos_unary_union(
309+
const geos::geom::Geometry* g) {
310+
if (!g || g->isEmpty()) return nullptr;
311+
return std::unique_ptr<geos::geom::Geometry>(g->Union());
312+
}
313+
175314
} // namespace detail
176315
} // namespace shapely
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// Python Source: shapely/geometry/collection.py
2+
// Line Range: L10-L60 (class GeometryCollection)
3+
// Alignment: strict
4+
// EXEMPTION: cpp_non_template
5+
// Reason: GeometryCollection can hold mixed types; non-template for simplicity.
6+
7+
#pragma once
8+
9+
#include <memory>
10+
#include <vector>
11+
#include <string>
12+
#include <cstddef>
13+
#include <geos/geom/GeometryFactory.h>
14+
#include <geos/geom/GeometryCollection.h>
15+
#include <geos/geom/Geometry.h>
16+
17+
namespace shapely {
18+
namespace geometry {
19+
20+
class GeometryCollection {
21+
public:
22+
GeometryCollection();
23+
24+
/// Add any GEOS geometry (advanced usage)
25+
void add_geometry(std::unique_ptr<geos::geom::Geometry> geom);
26+
27+
GeometryCollection(GeometryCollection&&) = default;
28+
GeometryCollection& operator=(GeometryCollection&&) = default;
29+
30+
// -- Access to individual geometries --
31+
size_t num_geometries() const;
32+
const geos::geom::Geometry* geometry_n(size_t i) const;
33+
34+
// -- Predicates with another GeometryCollection --
35+
bool contains(const GeometryCollection& other) const;
36+
bool within(const GeometryCollection& other) const;
37+
bool disjoint(const GeometryCollection& other) const;
38+
bool overlaps(const GeometryCollection& other) const;
39+
bool touches(const GeometryCollection& other) const;
40+
bool equals(const GeometryCollection& other) const;
41+
bool equals_exact(const GeometryCollection& other, double tol) const;
42+
bool intersects(const GeometryCollection& other) const;
43+
std::string relate(const GeometryCollection& other) const;
44+
bool relate_pattern(const GeometryCollection& other, const std::string& p) const;
45+
double hausdorff_distance(const GeometryCollection& other) const;
46+
47+
// -- Constructive operations --
48+
GeometryCollection difference(const GeometryCollection& other) const;
49+
GeometryCollection intersection(const GeometryCollection& other) const;
50+
GeometryCollection union_op(const GeometryCollection& other) const;
51+
GeometryCollection sym_difference(const GeometryCollection& other) const;
52+
GeometryCollection simplify(double tolerance) const;
53+
54+
// -- Accessors --
55+
std::string wkt() const;
56+
std::string wkb_hex() const;
57+
std::string type() const;
58+
std::string geom_type() const;
59+
bool has_z() const;
60+
61+
// -- Properties --
62+
bool is_empty() const;
63+
bool is_simple() const;
64+
bool is_valid() const;
65+
double area() const;
66+
double length() const;
67+
std::vector<double> bounds() const;
68+
69+
// -- Topology --
70+
GeometryCollection convex_hull() const;
71+
GeometryCollection buffer(double distance) const;
72+
void normalize();
73+
74+
// -- Access to internal GEOS geometry (for interop) --
75+
const geos::geom::GeometryCollection* geos_collection() const { return geos_coll_.get(); }
76+
77+
private:
78+
std::unique_ptr<geos::geom::GeometryCollection> geos_coll_;
79+
geos::geom::GeometryFactory::Ptr factory_;
80+
};
81+
82+
} // namespace geometry
83+
} // namespace shapely
84+
85+
// ============================================================================
86+
// Implementation
87+
// ============================================================================
88+
89+
#include "shapely/geometry/base.h"
90+
#include <geos/geom/Coordinate.h>
91+
#include <geos/operation/distance/DistanceOp.h>
92+
#include <stdexcept>
93+
94+
namespace shapely {
95+
namespace geometry {
96+
97+
GeometryCollection::GeometryCollection() {
98+
factory_ = geos::geom::GeometryFactory::create();
99+
geos_coll_ = factory_->createGeometryCollection();
100+
}
101+
102+
void GeometryCollection::add_geometry(std::unique_ptr<geos::geom::Geometry> geom) {
103+
if (!geom) return;
104+
std::vector<std::unique_ptr<geos::geom::Geometry>> geoms;
105+
for (size_t i = 0; i < geos_coll_->getNumGeometries(); ++i)
106+
geoms.push_back(std::unique_ptr<geos::geom::Geometry>(geos_coll_->getGeometryN(i)->clone()));
107+
geoms.push_back(std::move(geom));
108+
geos_coll_ = factory_->createGeometryCollection(std::move(geoms));
109+
}
110+
111+
size_t GeometryCollection::num_geometries() const { return geos_coll_->getNumGeometries(); }
112+
113+
const geos::geom::Geometry* GeometryCollection::geometry_n(size_t i) const {
114+
return geos_coll_->getGeometryN(i);
115+
}
116+
117+
// -- Predicates (delegate to GEOS) -------------------------------------------
118+
#define GC_PRED(METHOD, GEOS_FN) \
119+
bool GeometryCollection::METHOD(const GeometryCollection& o) const { return detail::GEOS_FN(geos_coll_.get(), o.geos_coll_.get()); }
120+
GC_PRED(contains, geos_contains)
121+
GC_PRED(within, geos_within)
122+
GC_PRED(disjoint, geos_disjoint)
123+
GC_PRED(overlaps, geos_overlaps)
124+
GC_PRED(touches, geos_touches)
125+
GC_PRED(equals, geos_equals)
126+
#undef GC_PRED
127+
128+
bool GeometryCollection::equals_exact(const GeometryCollection& o, double tol) const {
129+
return detail::geos_equals_exact(geos_coll_.get(), o.geos_coll_.get(), tol);
130+
}
131+
bool GeometryCollection::intersects(const GeometryCollection& o) const {
132+
return geos_coll_->intersects(o.geos_coll_.get());
133+
}
134+
std::string GeometryCollection::relate(const GeometryCollection& o) const {
135+
return detail::geos_relate(geos_coll_.get(), o.geos_coll_.get());
136+
}
137+
bool GeometryCollection::relate_pattern(const GeometryCollection& o, const std::string& p) const {
138+
return detail::geos_relate_pattern(geos_coll_.get(), o.geos_coll_.get(), p);
139+
}
140+
double GeometryCollection::hausdorff_distance(const GeometryCollection& o) const {
141+
return detail::geos_hausdorff_distance(geos_coll_.get(), o.geos_coll_.get());
142+
}
143+
144+
// -- Constructive operations -----------------------------------------------
145+
#define GC_CONSTRUCT(OP, GEOS_FN) \
146+
GeometryCollection GeometryCollection::OP(const GeometryCollection& o) const { \
147+
auto res = detail::GEOS_FN(geos_coll_.get(), o.geos_coll_.get()); \
148+
GeometryCollection r; \
149+
if (res && !res->isEmpty()) { \
150+
for (size_t i = 0; i < res->getNumGeometries(); ++i) \
151+
r.add_geometry(std::unique_ptr<geos::geom::Geometry>(res->getGeometryN(i)->clone())); \
152+
} \
153+
return r; \
154+
}
155+
GC_CONSTRUCT(difference, geos_difference)
156+
GC_CONSTRUCT(intersection, geos_intersection)
157+
GC_CONSTRUCT(union_op, geos_union)
158+
GC_CONSTRUCT(sym_difference, geos_sym_difference)
159+
#undef GC_CONSTRUCT
160+
161+
GeometryCollection GeometryCollection::simplify(double tol) const {
162+
auto res = detail::geos_simplify(geos_coll_.get(), tol);
163+
GeometryCollection r;
164+
if (res && !res->isEmpty()) {
165+
for (size_t i = 0; i < res->getNumGeometries(); ++i)
166+
r.add_geometry(std::unique_ptr<geos::geom::Geometry>(res->getGeometryN(i)->clone()));
167+
}
168+
return r;
169+
}
170+
171+
// -- Accessors ---------------------------------------------------------------
172+
std::string GeometryCollection::wkt() const { return detail::geos_to_wkt(geos_coll_.get()); }
173+
std::string GeometryCollection::wkb_hex() const { return detail::geos_to_wkb_hex(geos_coll_.get()); }
174+
std::string GeometryCollection::type() const { return "GeometryCollection"; }
175+
std::string GeometryCollection::geom_type() const { return detail::geos_geom_type(geos_coll_.get()); }
176+
bool GeometryCollection::has_z() const { return detail::geos_has_z(geos_coll_.get()); }
177+
178+
// -- Properties ---------------------------------------------------------------
179+
bool GeometryCollection::is_empty() const { return detail::geos_is_empty(geos_coll_.get()); }
180+
bool GeometryCollection::is_simple() const { return detail::geos_is_simple(geos_coll_.get()); }
181+
bool GeometryCollection::is_valid() const { return detail::geos_is_valid(geos_coll_.get()); }
182+
double GeometryCollection::area() const { return geos_coll_->getArea(); }
183+
double GeometryCollection::length() const { return geos_coll_->getLength(); }
184+
std::vector<double> GeometryCollection::bounds() const { return detail::geos_bounds(geos_coll_.get()); }
185+
186+
// -- Topology ------------------------------------------------------------------
187+
GeometryCollection GeometryCollection::convex_hull() const {
188+
auto res = detail::geos_convex_hull(geos_coll_.get());
189+
GeometryCollection r;
190+
if (res && !res->isEmpty()) {
191+
for (size_t i = 0; i < res->getNumGeometries(); ++i)
192+
r.add_geometry(std::unique_ptr<geos::geom::Geometry>(res->getGeometryN(i)->clone()));
193+
}
194+
return r;
195+
}
196+
197+
GeometryCollection GeometryCollection::buffer(double distance) const {
198+
auto buf = geos_coll_->buffer(distance, 16);
199+
GeometryCollection r;
200+
if (buf && !buf->isEmpty()) {
201+
for (size_t i = 0; i < buf->getNumGeometries(); ++i)
202+
r.add_geometry(std::unique_ptr<geos::geom::Geometry>(buf->getGeometryN(i)->clone()));
203+
}
204+
return r;
205+
}
206+
207+
void GeometryCollection::normalize() { geos_coll_->normalize(); }
208+
209+
} // namespace geometry
210+
} // namespace shapely

0 commit comments

Comments
 (0)