Skip to content

Union Polygon Clipping

julia
export union

"""
    union(geom_a, geom_b, [::Type{T}]; target::Type)

Return the union between two geometries as a list of geometries. Return an empty list if
none are found. The type of the list will be constrained as much as possible given the input
geometries. Furthermore, the user can provide a `taget` type as a keyword argument and a
list of target geometries found in the difference will be returned. The user can also
provide a float type 'T' that they would like the points of returned geometries to be.

Calculates the union between two polygons.
# Example

```jldoctest
import GeoInterface as GI, GeometryOps as GO

p1 = GI.Polygon([[(0.0, 0.0), (5.0, 5.0), (10.0, 0.0), (5.0, -5.0), (0.0, 0.0)]])
p2 = GI.Polygon([[(3.0, 0.0), (8.0, 5.0), (13.0, 0.0), (8.0, -5.0), (3.0, 0.0)]])
union_poly = GO.union(p1, p2; target = GI.PolygonTrait())
GI.coordinates.(union_poly)

output

julia
1-element Vector{Vector{Vector{Vector{Float64}}}}:
 [[[6.5, 3.5], [5.0, 5.0], [0.0, 0.0], [5.0, -5.0], [6.5, -3.5], [8.0, -5.0], [13.0, 0.0], [8.0, 5.0], [6.5, 3.5]]]
```
"""
function union(
    geom_a, geom_b, ::Type{T}=Float64; target=nothing,
) where {T<:AbstractFloat}
    _union(TraitTarget(target), T, GI.trait(geom_a), geom_a, GI.trait(geom_b), geom_b)
end

#= This 'union' implementation returns the union of two polygons. The algorithm to determine
the union was adapted from "Efficient clipping of efficient polygons," by Greiner and
Hormann (1998). DOI: https://doi.org/10.1145/274363.274364 =#
function _union(
    ::TraitTarget{GI.PolygonTrait}, ::Type{T},
    ::GI.PolygonTrait, poly_a,
    ::GI.PolygonTrait, poly_b,
) where T

First, I get the exteriors of the two polygons

julia
    ext_a = GI.getexterior(poly_a)
    ext_b = GI.getexterior(poly_b)

Then, I get the union of the exteriors

julia
    a_list, b_list, a_idx_list = _build_ab_list(T, ext_a, ext_b)
    polys = _trace_polynodes(T, a_list, b_list, a_idx_list, (x, y) -> x ? (-1) : 1)
    n_pieces = length(polys)

Check if one polygon totally within other and if so, return the larger polygon.

julia
    if n_pieces == 0 # no crossing points, determine if either poly is inside the other
        a_in_b, b_in_a = _find_non_cross_orientation(a_list, b_list, ext_a, ext_b)
        if a_in_b
            push!(polys, GI.Polygon([tuples(ext_b)]))
        elseif b_in_a
            push!(polys,  GI.Polygon([tuples(ext_a)]))
        else
            share_edge_warn(a_list, "Edge case: polygons share edge but can't be combined.") # will get taken care of with "glued edges"
            push!(polys, tuples(poly_a))
            push!(polys, tuples(poly_b))
            return polys
        end
    elseif n_pieces > 1  # extra polygons are holes (n_pieces == 1 is the desired state)
        sort!(polys, by = area, rev = true)  # sort so first element is the exterior
    end

the first element is the exterior, the rest are holes

julia
    new_holes = @views (GI.getexterior(p) for p in polys[2:end])
    polys = n_pieces > 1 ? polys[1:1] : polys

Add holes back in for there are any

julia
    if GI.nhole(poly_a) != 0 || GI.nhole(poly_b) != 0 || n_pieces > 1
        hole_iterator = Iterators.flatten((GI.gethole(poly_a), GI.gethole(poly_b), new_holes))
        _add_holes_to_polys!(T, polys, hole_iterator)
    end
    return polys
end

Many type and target combos aren't implemented

julia
function _union(
    ::TraitTarget{Target}, ::Type{T},
    trait_a::GI.AbstractTrait, geom_a,
    trait_b::GI.AbstractTrait, geom_b,
) where {Target,T}
    throw(ArgumentError("Union between $trait_a and $trait_b with target $Target isn't implemented yet."))
    return nothing
end

This page was generated using Literate.jl.