From b370c5f2f24aa29f8dd9db393cdd4142b8445054 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 11 Mar 2025 15:47:29 -0500 Subject: [PATCH 1/4] Wrap `concavehull` --- src/geo_interface.jl | 2 ++ src/geos_functions.jl | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/geo_interface.jl b/src/geo_interface.jl index 32214e1..c9d84ac 100644 --- a/src/geo_interface.jl +++ b/src/geo_interface.jl @@ -332,6 +332,8 @@ for f in ( :envelope, :minimumRotatedRectangle, :convexhull, + :concavehull, + :concavehull_of_polygons, :boundary, :unaryUnion, :pointOnSurface, diff --git a/src/geos_functions.jl b/src/geos_functions.jl index ce8233f..deb2a0a 100644 --- a/src/geos_functions.jl +++ b/src/geos_functions.jl @@ -677,6 +677,44 @@ function convexhull(obj::Geometry, context::GEOSContext = get_context(obj)) geomFromGEOS(result, context) end +""" + concavehull(obj::Geometry; [ratio | length_ratio], allow_holes = true) + +Compute the concave hull of a geometry. + +One of `ratio` or `length_ratio` must be provided. +If `ratio` is provided, this calls `GEOSConcaveHull`; +if `length_ratio` is provided, this calls `GEOSConcaveHullByLength`. + +If `allow_holes` is true, the concave hull may have holes. + +Returns a polygon that represents the concave hull from the chi-shape +defined by `ratio` or `length_ratio`. +""" +function concavehull(obj::Geometry; ratio::Union{Real, Nothing} = nothing, length_ratio::Union{Real, Nothing} = nothing, allow_holes::Bool = true, context::GEOSContext = get_context(obj)) + result = if isnothing(ratio) && isnothing(length_ratio) + throw(ArgumentError("Either `ratio` or `length_ratio` must be provided to `LibGEOS.concavehull`, none were provided.")) + elseif !isnothing(ratio) && !isnothing(length_ratio) + throw(ArgumentError("Only one of `ratio` or `length_ratio` may be provided to `LibGEOS.concavehull`; got both.")) + elseif !isnothing(ratio) && isnothing(length_ratio) + GEOSConcaveHull_r(context, obj, ratio, allow_holes) + elseif isnothing(ratio) && !isnothing(length_ratio) + GEOSConcaveHullByLength_r(context, obj, ratio, allow_holes) + end + if result == C_NULL + error("LibGEOS: Error in GEOSConcaveHull") + end + geomFromGEOS(result, context) +end + +function concavehull_of_polygons(obj::Geometry, ratio::Real, tight::Bool, allow_holes::Bool, context::GEOSContext = get_context(obj)) + result = GEOSConcaveHullOfPolygons_r(context, obj, ratio, tight, allow_holes) + if result == C_NULL + error("LibGEOS: Error in GEOSConcaveHullOfPolygons") + end + geomFromGEOS(result, context) +end + function difference( obj1::Geometry, obj2::Geometry, From 1cb652bf9bc371b1f2e7534d31fd63dead7e8b90 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 11 Mar 2025 15:47:40 -0500 Subject: [PATCH 2/4] Add concavehull to tests --- test/test_geo_interface.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_geo_interface.jl b/test/test_geo_interface.jl index d13ec02..48cce74 100644 --- a/test/test_geo_interface.jl +++ b/test/test_geo_interface.jl @@ -318,6 +318,7 @@ const LG = LibGEOS LG.delaunayTriangulationEdges, LG.delaunayTriangulation, LG.constrainedDelaunayTriangulation, + (x -> LG.concavehull(x; ratio = 0.5)), # fix the kwarg here # these have different signatures # LG.simplify, LG.topologyPreserveSimplify, ) From 4e288b85332715be45538cf6538cbc504c19ca59 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 11 Mar 2025 15:47:53 -0500 Subject: [PATCH 3/4] Add context to each test, so we know which functions failed! --- test/test_geo_interface.jl | 52 ++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/test/test_geo_interface.jl b/test/test_geo_interface.jl index 48cce74..01c68d1 100644 --- a/test/test_geo_interface.jl +++ b/test/test_geo_interface.jl @@ -359,12 +359,16 @@ const LG = LibGEOS @test geom isa MultiPoint @test GeoInterface.coordinates(geom) == coords for f in one_arg_functions - @test f(LibGEOS.MultiPoint(coords)) == f(GeoInterface.MultiPoint(coords)) + @testset let current_function = f + @test f(LibGEOS.MultiPoint(coords)) == f(GeoInterface.MultiPoint(coords)) + end end coords2 = [[0.0, 10], [0.5, 10], [20.0, 20], [10.0, 10], [0.0, 10]] for f in two_arg_functions - @test f(LibGEOS.LineString(coords), LibGEOS.LineString(coords)) == - f(GeoInterface.LineString(coords), GeoInterface.LineString(coords)) + @testset let current_function = f + @test f(LibGEOS.LineString(coords), LibGEOS.LineString(coords)) == + f(GeoInterface.LineString(coords), GeoInterface.LineString(coords)) + end end coords = [[0.0, 0], [0.0, 10], [10.0, 10], [10.0, 0], [0.0, 0]] @@ -372,12 +376,16 @@ const LG = LibGEOS @test geom isa LineString @test GeoInterface.coordinates(geom) == coords for f in one_arg_functions - @test f(LibGEOS.LineString(coords)) == f(GeoInterface.LineString(coords)) + @testset let current_function = f + @test f(LibGEOS.LineString(coords)) == f(GeoInterface.LineString(coords)) + end end coords2 = [[0.0, 10], [0.5, 10], [20.0, 20], [10.0, 10], [0.0, 10]] for f in two_arg_functions - @test f(LibGEOS.LineString(coords), LibGEOS.LineString(coords)) == - f(GeoInterface.LineString(coords), GeoInterface.LineString(coords)) + @testset let current_function = f + @test f(LibGEOS.LineString(coords), LibGEOS.LineString(coords)) == + f(GeoInterface.LineString(coords), GeoInterface.LineString(coords)) + end end coords = [[[0.0, 0], [0.0, 10], [10.0, 10], [10.0, 0], [0.0, 0]]] @@ -385,13 +393,17 @@ const LG = LibGEOS @test geom isa MultiLineString @test GeoInterface.coordinates(geom) == coords for f in one_arg_functions - @test f(LibGEOS.MultiLineString(coords)) == - f(GeoInterface.MultiLineString(coords)) + @testset let current_function = f + @test f(LibGEOS.MultiLineString(coords)) == + f(GeoInterface.MultiLineString(coords)) + end end coords2 = [[[0.0, 10], [0.5, 10], [20.0, 20], [10.0, 10], [0.0, 10]]] for f in two_arg_functions - @test f(LibGEOS.MultiLineString(coords), LibGEOS.MultiLineString(coords2)) == - f(GeoInterface.MultiLineString(coords), LibGEOS.MultiLineString(coords2)) + @testset let current_function = f + @test f(LibGEOS.MultiLineString(coords), LibGEOS.MultiLineString(coords2)) == + f(GeoInterface.MultiLineString(coords), LibGEOS.MultiLineString(coords2)) + end end coords = [[[0.0, 0], [0.0, 10], [10.0, 10], [10.0, 0], [0.0, 0]]] @@ -402,12 +414,16 @@ const LG = LibGEOS @test GeoInterface.nhole(geom) == 0 @test GeoInterface.coordinates(geom) == coords for f in one_arg_functions - @test f(LibGEOS.Polygon(coords)) == f(GeoInterface.Polygon(coords)) + @testset let current_function = f + @test f(LibGEOS.Polygon(coords)) == f(GeoInterface.Polygon(coords)) + end end coords2 = [[[0.0, 10], [0.5, 10], [20.0, 20], [10.0, 10], [0.0, 10]]] for f in two_arg_functions - @test f(LibGEOS.Polygon(coords), LibGEOS.Polygon(coords2)) == - f(GeoInterface.Polygon(coords), LibGEOS.Polygon(coords2)) + @testset let current_function = f + @test f(LibGEOS.Polygon(coords), LibGEOS.Polygon(coords2)) == + f(GeoInterface.Polygon(coords), LibGEOS.Polygon(coords2)) + end end pgeom = LibGEOS.prepareGeom(geom) @@ -419,12 +435,16 @@ const LG = LibGEOS @test geom isa MultiPolygon @test GeoInterface.coordinates(geom) == coords for f in one_arg_functions - @test f(LibGEOS.MultiPolygon(coords)) == f(GeoInterface.MultiPolygon(coords)) + @testset let current_function = f + @test f(LibGEOS.MultiPolygon(coords)) == f(GeoInterface.MultiPolygon(coords)) + end end coords2 = [[[[0.0, 10], [0.5, 10], [20.0, 20], [10.0, 10], [0.0, 10]]]] for f in two_arg_functions - @test f(LibGEOS.MultiPolygon(coords), LibGEOS.MultiPolygon(coords2)) == - f(GeoInterface.MultiPolygon(coords), LibGEOS.MultiPolygon(coords2)) + @testset let current_function = f + @test f(LibGEOS.MultiPolygon(coords), LibGEOS.MultiPolygon(coords2)) == + f(GeoInterface.MultiPolygon(coords), LibGEOS.MultiPolygon(coords2)) + end end end From ddf358c6a8616ace98895d34ec1990dfe7643c17 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 11 Mar 2025 15:57:32 -0500 Subject: [PATCH 4/4] refactor to have one required arg and then kwarg that changes method called --- src/geos_functions.jl | 27 ++++++++++++++++++--------- test/test_geo_interface.jl | 2 +- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/geos_functions.jl b/src/geos_functions.jl index deb2a0a..d1e2db1 100644 --- a/src/geos_functions.jl +++ b/src/geos_functions.jl @@ -678,18 +678,18 @@ function convexhull(obj::Geometry, context::GEOSContext = get_context(obj)) end """ - concavehull(obj::Geometry; [ratio | length_ratio], allow_holes = true) + concavehull(geom, ratio; bylength = false, allow_holes = true) -Compute the concave hull of a geometry. +Compute the concave hull of a geometry, according to the chi-shape generated from +`geom` with the given `ratio`. -One of `ratio` or `length_ratio` must be provided. -If `ratio` is provided, this calls `GEOSConcaveHull`; -if `length_ratio` is provided, this calls `GEOSConcaveHullByLength`. +If `bylength` is true, the chi-shape is generated from the length of the geometry, +calling `GEOSConcaveHullByLength`; otherwise it is generated from the area, calling +`GEOSConcaveHull`. -If `allow_holes` is true, the concave hull may have holes. +If `allow_holes` is true, the concave hull may have holes. If false, then holes are disallowed. -Returns a polygon that represents the concave hull from the chi-shape -defined by `ratio` or `length_ratio`. +See also [`convexhull`](@ref), [`concavehull_of_polygons`](@ref). """ function concavehull(obj::Geometry; ratio::Union{Real, Nothing} = nothing, length_ratio::Union{Real, Nothing} = nothing, allow_holes::Bool = true, context::GEOSContext = get_context(obj)) result = if isnothing(ratio) && isnothing(length_ratio) @@ -707,7 +707,16 @@ function concavehull(obj::Geometry; ratio::Union{Real, Nothing} = nothing, lengt geomFromGEOS(result, context) end -function concavehull_of_polygons(obj::Geometry, ratio::Real, tight::Bool, allow_holes::Bool, context::GEOSContext = get_context(obj)) +""" + concavehull_of_polygons(obj::Geometry, ratio; tight = false, allow_holes = true) + +Compute the concave hull of a geometry, according to the chi-shape generated from +`obj` with the given `ratio`. + +If `tight` is true, the chi-shape is generated from the tightest fit of the geometry, +otherwise it is generated from the area. +""" +function concavehull_of_polygons(obj::Geometry, ratio::Real; tight::Bool = false, allow_holes::Bool = true, context::GEOSContext = get_context(obj)) result = GEOSConcaveHullOfPolygons_r(context, obj, ratio, tight, allow_holes) if result == C_NULL error("LibGEOS: Error in GEOSConcaveHullOfPolygons") diff --git a/test/test_geo_interface.jl b/test/test_geo_interface.jl index 01c68d1..d7aa110 100644 --- a/test/test_geo_interface.jl +++ b/test/test_geo_interface.jl @@ -318,7 +318,7 @@ const LG = LibGEOS LG.delaunayTriangulationEdges, LG.delaunayTriangulation, LG.constrainedDelaunayTriangulation, - (x -> LG.concavehull(x; ratio = 0.5)), # fix the kwarg here + Base.Fix2(LG.concavehull, 0.3), # these have different signatures # LG.simplify, LG.topologyPreserveSimplify, )