From 2467828458b716f565f4cb3c3a3f0385ff081ac1 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Tue, 25 Jun 2024 08:26:49 -0400 Subject: [PATCH] Create a `PolygonContents` transform that fixes the contents of polygons --- src/GeometryOps.jl | 1 + src/methods/clipping/union.jl | 18 ++++++++-- .../correction/geometry_correction.jl | 13 +++++++- .../correction/polygon_contents.jl | 33 +++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/transformations/correction/polygon_contents.jl diff --git a/src/GeometryOps.jl b/src/GeometryOps.jl index 1aced0e82..e947df164 100644 --- a/src/GeometryOps.jl +++ b/src/GeometryOps.jl @@ -60,6 +60,7 @@ include("transformations/transform.jl") include("transformations/correction/geometry_correction.jl") include("transformations/correction/closed_ring.jl") include("transformations/correction/intersecting_polygons.jl") +include("transformations/correction/polygon_contents.jl") # Import all names from GeoInterface and Extents, so users can do `GO.extent` or `GO.trait`. for name in names(GeoInterface) diff --git a/src/methods/clipping/union.jl b/src/methods/clipping/union.jl index 8e1d785e4..ada192726 100644 --- a/src/methods/clipping/union.jl +++ b/src/methods/clipping/union.jl @@ -217,7 +217,7 @@ function _union( if !isnothing(fix_multipoly) # Fix multipoly_b to prevent repeated regions in the output multipoly_b = fix_multipoly(multipoly_b) end - polys = [tuples(poly_a, T)] + polys = [fix(tuples(poly_a, T); corrections = (PolygonContents(), ))] for poly_b in GI.getpolygon(multipoly_b) if intersects(polys[1], poly_b) # If polygons intersect and form a new polygon, swap out polygon @@ -229,7 +229,7 @@ function _union( end else # If they don't intersect, poly_b is now a part of the union as its own polygon - push!(polys, tuples(poly_b, T)) + push!(polys, fix(tuples(poly_b, T); corrections = (PolygonContents(), ))) end end return polys @@ -267,6 +267,20 @@ function _union( return polys end +function _union( + target::TraitTarget{GI.MultiPolygonTrait}, ::Type{T}, + ::GI.MultiPolygonTrait, multipoly_a, + ::GI.MultiPolygonTrait, multipoly_b; + fix_multipoly = UnionIntersectingPolygons(), kwargs..., +) where T + return GI.MultiPolygon(_union( # this is the method directly above, and returns a vector of polygons + TraitTarget{GI.PolygonTrait}(), T, + GI.MultiPolygonTrait(), multipoly_a, + GI.MultiPolygonTrait(), multipoly_b; + fix_multipoly, kwargs... + )) +end + # Many type and target combos aren't implemented function _union( ::TraitTarget{Target}, ::Type{T}, diff --git a/src/transformations/correction/geometry_correction.jl b/src/transformations/correction/geometry_correction.jl index 5cccb5ea2..13a14ed43 100644 --- a/src/transformations/correction/geometry_correction.jl +++ b/src/transformations/correction/geometry_correction.jl @@ -44,7 +44,18 @@ application_level(gc::GeometryCorrection) = error("Not implemented yet for $(gc) (gc::GeometryCorrection)(trait::GI.AbstractGeometryTrait, geometry) = error("Not implemented yet for $(gc) and $(trait).") -function fix(geometry; corrections = GeometryCorrection[ClosedRing(),], kwargs...) +""" + fix(x; corrections = GeometryCorrection[], kwargs...) + +Apply the given corrections to `x`, and return the corrected version. + +`x` may be a geometry, vector of geometries, feature collection, or table - +anything which [`apply`](@ref) will accept! + +Some available corrections are: [`ClosedRing`](@ref), [`PolygonContents`](@ref), [`UnionIntersectingPolygons`](@ref), [`DiffIntersectingPolygons`](@ref). + +""" +function fix(geometry; corrections = GeometryCorrection[PolygonContents(), ClosedRing(),], kwargs...) traits = application_level.(corrections) final_geometry = geometry for Trait in (GI.PointTrait, GI.MultiPointTrait, GI.LineStringTrait, GI.LinearRingTrait, GI.MultiLineStringTrait, GI.PolygonTrait, GI.MultiPolygonTrait) diff --git a/src/transformations/correction/polygon_contents.jl b/src/transformations/correction/polygon_contents.jl new file mode 100644 index 000000000..938106e7e --- /dev/null +++ b/src/transformations/correction/polygon_contents.jl @@ -0,0 +1,33 @@ +# # PolygonContents + +export PolygonContents + +#= +Polygons should only contain linear rings. This fix checks +whether the contents of the polygon are linear rings or linestrings, +and converts linestrings to linear rings. + +It does **NOT** check whether the linear rings are valid - it only checks +the types of the polygon's constituent geometries. You can use the [`ClosedRing`](@ref) +geometry fix to check for validity after applying this fix. +=# + +struct PolygonContents <: GeometryCorrection end + +application_level(::PolygonContents) = GI.PolygonTrait + +function (::PolygonContents)(::GI.PolygonTrait, polygon) + exterior = GI.getexterior(polygon) + fixed_exterior = _ls2lr(exterior) + holes = GI.gethole(polygon) + if isempty(holes) + return GI.Polygon([fixed_exterior]) + end + fixed_holes = _ls2lr.(holes) + return GI.Polygon([fixed_exterior, fixed_holes...]) +end + +_ls2lr(x) = _ls2lr(GI.geomtrait(x), x) + +_ls2lr(::GI.LineStringTrait, x) = GI.LinearRing(GI.getpoint(x)) +_ls2lr(::GI.LinearRingTrait, x) = x \ No newline at end of file