@@ -211,6 +211,123 @@ def test_st_centroid(eng, geom, expected):
211211 eng .assert_query_result (f"SELECT ST_Centroid({ geom_or_null (geom )} )" , expected )
212212
213213
214+ @pytest .mark .parametrize ("eng" , [SedonaDB , PostGIS ])
215+ @pytest .mark .parametrize (
216+ ("geom" , "expected" ),
217+ [
218+ (None , None ),
219+ # POINTS - Always simple (single point has no self-intersections)
220+ ("POINT (1 1)" , True ),
221+ ("POINT EMPTY" , True ), # Empty geometry is simple
222+ # MULTIPOINTS
223+ ("MULTIPOINT (1 1, 2 2, 3 3)" , True ), # Distinct points
224+ ("MULTIPOINT (1 1, 2 2, 1 1)" , False ), # Duplicate points make it non-simple
225+ ("MULTIPOINT EMPTY" , True ), # Empty multipoint
226+ ("MULTIPOINT (1 1, 2 2, 3 3)" , True ),
227+ # LINESTRINGS
228+ ("LINESTRING (0 0, 1 1)" , True ), # Simple straight line
229+ ("LINESTRING (0 0, 1 1, 2 2)" , True ), # Simple line, collinear points
230+ ("LINESTRING (0 0, 1 1, 0 1, 1 0)" , False ), # Self-intersecting (bowtie shape)
231+ ("LINESTRING(1 1,2 2,2 3.5,1 3,1 2,2 1)" , False ), # Complex self-intersection
232+ (
233+ "LINESTRING (0 0, 1 1, 0 0)" ,
234+ False ,
235+ ), # Closed loop with repeated start/end but intersects at interior
236+ ("LINESTRING (0 0, 1 1, 1 0, 0 0)" , True ), # Simple closed ring (triangle)
237+ ("LINESTRING EMPTY" , True ), # Empty linestring
238+ # POLYGONS
239+ ("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))" , True ), # Simple rectangle
240+ (
241+ "POLYGON ((0 0, 1 1, 0 1, 1 0, 0 0))" ,
242+ False ,
243+ ), # Bowtie polygon - self-intersecting
244+ (
245+ "POLYGON((1 2, 3 4, 5 6, 1 2))" ,
246+ False ,
247+ ), # Degenerate polygon - zero-area Triangle
248+ (
249+ "Polygon((0 0, 2 0, 1 1, 2 2, 0 2, 1 1, 0 0))" ,
250+ False ,
251+ ), # Star shape with self-intersection
252+ (
253+ "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))" ,
254+ True ,
255+ ), # Polygon with hole, valid
256+ (
257+ "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (1 1, 0 2, 2 2, 1 1))" ,
258+ True ,
259+ ), # Valid OGC Polygon (is also considered 'Simple' by OGC standard)
260+ # MULTILINESTRINGS
261+ (
262+ "MULTILINESTRING ((0 0, 1 1), (1 1, 2 2))" ,
263+ True ,
264+ ), # Touching at endpoints only
265+ ("MULTILINESTRING ((0 0, 2 2), (0 2, 2 0))" , False ), # Lines cross in middle
266+ ("MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))" , True ), # Disjoint lines
267+ (
268+ "MULTILINESTRING ((0 0, 1 1, 2 2), (2 2, 3 3))" ,
269+ True ,
270+ ), # Connected at endpoint
271+ (
272+ "MULTILINESTRING ((0 0, 2 0, 2 2, 0 2, 0 0), (1 1, 3 1, 3 3, 1 3, 1 1))" ,
273+ False ,
274+ ), # Not simple: The two rings overlap and intersect (2 1), violating the MULTILINESTRING simplicity rule.
275+ ("MULTILINESTRING ((0 0, 2 2), (1 0, 1 2))" , False ), # Lines intersect at (1,1)
276+ ("MULTILINESTRING EMPTY" , True ), # Empty multilinestring
277+ # MULTIPOLYGONS
278+ ("MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)))" , True ), # Single simple polygon
279+ (
280+ "MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0)), ((3 0, 3 2, 5 2, 5 0, 3 0)))" ,
281+ True ,
282+ ), # Two disjoint polygons
283+ (
284+ "MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0)), ((1 1, 1 3, 3 3, 3 1, 1 1)))" ,
285+ True ,
286+ ), # Touching at point
287+ (
288+ "MULTIPOLYGON (((0 0, 0 3, 3 3, 3 0, 0 0)), ((1 1, 1 2, 2 2, 2 1, 1 1)))" ,
289+ True ,
290+ ), # One inside another (donut)
291+ (
292+ "MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0)), ((0 0, 0 1, 1 1, 1 0, 0 0)))" ,
293+ True ,
294+ ), # Simple: The boundaries do not cross
295+ ("MULTIPOLYGON EMPTY" , True ), # Empty multipolygon
296+ # GEOMETRYCOLLECTIONS
297+ (
298+ "GEOMETRYCOLLECTION (POINT (1 1), LINESTRING (0 0, 1 1))" ,
299+ True ,
300+ ), # Simple components
301+ (
302+ "GEOMETRYCOLLECTION (LINESTRING (0 0, 2 2), LINESTRING (0 2, 2 0))" ,
303+ True ,
304+ ),
305+ ("GEOMETRYCOLLECTION EMPTY" , True ), # Empty collection
306+ # EDGE CASES
307+ ("POINT (1 1)" , True ), # Repeated for completeness
308+ (
309+ "LINESTRING (1 1, 1 1)" ,
310+ True ,
311+ ), # Simple: Start and end points are the only intersecting points.
312+ (
313+ "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0), (0.2 0.2, 0.2 0.8, 0.8 0.8, 0.8 0.2, 0.2 0.2))" ,
314+ True ,
315+ ), # Proper hole
316+ (
317+ "POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0), (0.5 0.5, 1.5 0.5, 1.5 1.5, 0.5 1.5, 0.5 0.5))" ,
318+ True ,
319+ ), # Another valid hole
320+ (
321+ "LINESTRING (0 0, 1 0, 1 1, 0 1, 0.5 1, 0.5 0)" ,
322+ False ,
323+ ), # Self-touching at non-endpoint
324+ ],
325+ )
326+ def test_st_issimple (eng , geom , expected ):
327+ eng = eng .create_or_skip ()
328+ eng .assert_query_result (f"SELECT ST_IsSimple({ geom_or_null (geom )} )" , expected )
329+
330+
214331@pytest .mark .parametrize ("eng" , [SedonaDB , PostGIS ])
215332@pytest .mark .parametrize (
216333 ("geom" , "expected" ),
0 commit comments