From a9307191902d4cd49b7c8c1b1afbf9eabfe000bf Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 21 Sep 2024 10:29:29 -0700 Subject: [PATCH 1/5] Make all GEOS methods wrap the result in a GI wrapper to preserve CRS and potentially calculate extent. --- .../GeometryOpsLibGEOSExt.jl | 4 ++++ ext/GeometryOpsLibGEOSExt/buffer.jl | 7 ++++--- ext/GeometryOpsLibGEOSExt/segmentize.jl | 9 ++++++++- ext/GeometryOpsLibGEOSExt/simple_overrides.jl | 16 ++++++++-------- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/ext/GeometryOpsLibGEOSExt/GeometryOpsLibGEOSExt.jl b/ext/GeometryOpsLibGEOSExt/GeometryOpsLibGEOSExt.jl index 5a4856f8b..c2ab6b783 100644 --- a/ext/GeometryOpsLibGEOSExt/GeometryOpsLibGEOSExt.jl +++ b/ext/GeometryOpsLibGEOSExt/GeometryOpsLibGEOSExt.jl @@ -15,6 +15,10 @@ for name in filter(!in((:var"#eval", :eval, :var"#include", :include)), names(Ge @eval using GeometryOps: $name end +function _wrap(geom; crs=GI.crs(geom), calc_extent = true) + return GI.geointerface_geomtype(GI.geomtrait(geom))(geom; crs, extent = GI.extent(geom, calc_extent)) +end + include("buffer.jl") include("segmentize.jl") include("simplify.jl") diff --git a/ext/GeometryOpsLibGEOSExt/buffer.jl b/ext/GeometryOpsLibGEOSExt/buffer.jl index f66b86b5f..de3e331b8 100644 --- a/ext/GeometryOpsLibGEOSExt/buffer.jl +++ b/ext/GeometryOpsLibGEOSExt/buffer.jl @@ -18,16 +18,17 @@ to_join_style(style::Symbol) = _GEOS_JOINSTYLE_LOOKUP[style] to_join_style(style::LG.GEOSBufJoinStyles) = style to_join_style(num::Integer) = num -function GO.buffer(alg::GEOS, geometry, distance) +function GO.buffer(alg::GEOS, geometry, distance; calc_extent = true, kwargs...) # The reason we use apply here is so that this also works with featurecollections, # tables, vectors of geometries, etc! - return apply(TraitTarget{GI.AbstractGeometryTrait}(), geometry) do geom - LG.bufferWithStyle( + return apply(TraitTarget{GI.AbstractGeometryTrait}(), geometry; kwargs...) do geom + newgeom = LG.bufferWithStyle( GI.convert(LG, geom), distance; quadsegs = get(alg, :quadsegs, 8), endCapStyle = to_cap_style(get(alg, :endCapStyle, :round)), joinStyle = to_join_style(get(alg, :joinStyle, :round)), mitreLimit = get(alg, :mitreLimit, 5.0), ) + return _wrap(newgeom; crs = GI.crs(geom), calc_extent) end end \ No newline at end of file diff --git a/ext/GeometryOpsLibGEOSExt/segmentize.jl b/ext/GeometryOpsLibGEOSExt/segmentize.jl index 709d1e0b5..b0624b893 100644 --- a/ext/GeometryOpsLibGEOSExt/segmentize.jl +++ b/ext/GeometryOpsLibGEOSExt/segmentize.jl @@ -16,13 +16,20 @@ end _segmentize_geos(geom, max_distance) = _segmentize_geos(GI.convert(LG, geom), max_distance) +function _wrap_and_segmentize_geos(geom, max_distance) + _wrap(_segmentize_geos(geom, max_distance); crs = GI.crs(geom), calc_extent = false) +end + # 2 behaviours: # - enforce: enforce the presence of a kwargs # - fetch: fetch the value of a kwargs, or return a default value @inline function GO.segmentize(alg::GEOS, geom; threaded::Union{Bool, GO.BoolsAsTypes} = _False()) max_distance = enforce(alg, :max_distance, GO.segmentize) return GO.apply( - Base.Fix2(_segmentize_geos, max_distance), + Base.Fix2(_wrap_and_segmentize_geos, max_distance), + # TODO: should this just be a target on GI.AbstractGeometryTrait()? + # But Geos doesn't support eg RectangleTrait + # Maybe we need an abstract trait `GI.AbstractWKBGeomTrait`? GO.TraitTarget(GI.GeometryCollectionTrait(), GI.MultiPolygonTrait(), GI.PolygonTrait(), GI.MultiLineStringTrait(), GI.LineStringTrait(), GI.LinearRingTrait(), GI.MultiPointTrait(), GI.PointTrait()), geom; threaded diff --git a/ext/GeometryOpsLibGEOSExt/simple_overrides.jl b/ext/GeometryOpsLibGEOSExt/simple_overrides.jl index 824534ec7..97353a926 100644 --- a/ext/GeometryOpsLibGEOSExt/simple_overrides.jl +++ b/ext/GeometryOpsLibGEOSExt/simple_overrides.jl @@ -6,20 +6,20 @@ require conversion before calling. =# # ## Polygon set operations # ### Difference -function GO.difference(::GEOS, geom_a, geom_b; target=nothing) - return LG.difference(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) +function GO.difference(::GEOS, geom_a, geom_b; target=nothing, calc_extent = false) + return _wrap(LG.difference(GI.convert(LG, geom_a), GI.convert(LG, geom_b)); crs = GI.crs(geom_a), calc_extent) end # ### Union -function GO.union(::GEOS, geom_a, geom_b; target=nothing) - return LG.union(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) +function GO.union(::GEOS, geom_a, geom_b; target=nothing, calc_extent = false) + return _wrap(LG.union(GI.convert(LG, geom_a), GI.convert(LG, geom_b)); crs = GI.crs(geom_a), calc_extent) end # ### Intersection -function GO.intersection(::GEOS, geom_a, geom_b; target=nothing) - return LG.intersection(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) +function GO.intersection(::GEOS, geom_a, geom_b; target=nothing, calc_extent = false) + return _wrap(LG.intersection(GI.convert(LG, geom_a), GI.convert(LG, geom_b)); crs = GI.crs(geom_a), calc_extent) end # ### Symmetric difference -function GO.symdifference(::GEOS, geom_a, geom_b; target=nothing) - return LG.symmetric_difference(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) +function GO.symdifference(::GEOS, geom_a, geom_b; target=nothing, calc_extent = false) + return _wrap(LG.symmetric_difference(GI.convert(LG, geom_a), GI.convert(LG, geom_b)); crs = GI.crs(geom_a), calc_extent) end # ## DE-9IM boolean methods From c88bbaf3aa7c153779b545c3a8ba894760ca9084 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 21 Sep 2024 10:36:07 -0700 Subject: [PATCH 2/5] Wrap the result of convexhull --- ext/GeometryOpsLibGEOSExt/simple_overrides.jl | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ext/GeometryOpsLibGEOSExt/simple_overrides.jl b/ext/GeometryOpsLibGEOSExt/simple_overrides.jl index 97353a926..815e0dbae 100644 --- a/ext/GeometryOpsLibGEOSExt/simple_overrides.jl +++ b/ext/GeometryOpsLibGEOSExt/simple_overrides.jl @@ -66,15 +66,19 @@ end # ## Convex hull function GO.convex_hull(::GEOS, geoms) - return LG.convexhull( - LG.MultiPoint( - collect( - GO.flatten( - x -> GI.convert(LG, x), - GI.PointTrait, - geoms + return _wrap( + LG.convexhull( + LG.MultiPoint( + collect( + GO.flatten( + x -> GI.convert(LG, x), + GI.PointTrait, + geoms + ) ) ) - ) + ); + crs = GI.crs(geoms), + calc_extent = false ) end \ No newline at end of file From 82fd10716436fee29ef8f44e3c83c0c6d1900535 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sun, 22 Sep 2024 14:10:46 -0700 Subject: [PATCH 3/5] Convert directly to LibGEOS points Co-authored-by: Rafael Schouten --- ext/GeometryOpsLibGEOSExt/simple_overrides.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/GeometryOpsLibGEOSExt/simple_overrides.jl b/ext/GeometryOpsLibGEOSExt/simple_overrides.jl index 815e0dbae..7d82802b1 100644 --- a/ext/GeometryOpsLibGEOSExt/simple_overrides.jl +++ b/ext/GeometryOpsLibGEOSExt/simple_overrides.jl @@ -71,7 +71,7 @@ function GO.convex_hull(::GEOS, geoms) LG.MultiPoint( collect( GO.flatten( - x -> GI.convert(LG, x), + x -> GI.convert(LG.Point, x), GI.PointTrait, geoms ) From 5777c9078dec1a0ba07e2759b234cee199bb1e89 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Mon, 23 Sep 2024 16:00:19 -0700 Subject: [PATCH 4/5] less nesting --- ext/GeometryOpsLibGEOSExt/simple_overrides.jl | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ext/GeometryOpsLibGEOSExt/simple_overrides.jl b/ext/GeometryOpsLibGEOSExt/simple_overrides.jl index 7d82802b1..544a931c2 100644 --- a/ext/GeometryOpsLibGEOSExt/simple_overrides.jl +++ b/ext/GeometryOpsLibGEOSExt/simple_overrides.jl @@ -66,19 +66,20 @@ end # ## Convex hull function GO.convex_hull(::GEOS, geoms) - return _wrap( - LG.convexhull( - LG.MultiPoint( - collect( - GO.flatten( - x -> GI.convert(LG.Point, x), - GI.PointTrait, - geoms - ) + chull = LG.convexhull( + LG.MultiPoint( + collect( + GO.flatten( + x -> GI.convert(LG.Point, x), + GI.PointTrait, + geoms ) ) - ); + ) + ); + return _wrap( + chull; crs = GI.crs(geoms), calc_extent = false ) -end \ No newline at end of file +end From 517f4359c75ea7ad0e9230e1b88342f6f5a5e893 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Mon, 23 Sep 2024 16:01:42 -0700 Subject: [PATCH 5/5] Document `_wrap` and why we use it --- ext/GeometryOpsLibGEOSExt/GeometryOpsLibGEOSExt.jl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ext/GeometryOpsLibGEOSExt/GeometryOpsLibGEOSExt.jl b/ext/GeometryOpsLibGEOSExt/GeometryOpsLibGEOSExt.jl index c2ab6b783..f47b8ee9b 100644 --- a/ext/GeometryOpsLibGEOSExt/GeometryOpsLibGEOSExt.jl +++ b/ext/GeometryOpsLibGEOSExt/GeometryOpsLibGEOSExt.jl @@ -15,6 +15,15 @@ for name in filter(!in((:var"#eval", :eval, :var"#include", :include)), names(Ge @eval using GeometryOps: $name end +""" + _wrap(geom; crs, calc_extent) + +Wraps `geom` in a GI wrapper geometry of its geometry trait. This allows us +to attach CRS and extent info to geometry types which otherwise could not hold +those, like LibGEOS and WKB geometries. + +Returns a GI wrapper geometry, for which `parent(result) == geom`. +""" function _wrap(geom; crs=GI.crs(geom), calc_extent = true) return GI.geointerface_geomtype(GI.geomtrait(geom))(geom; crs, extent = GI.extent(geom, calc_extent)) end @@ -25,4 +34,4 @@ include("simplify.jl") include("simple_overrides.jl") -end \ No newline at end of file +end