Skip to content

Overlaps

julia
export overlaps

What is overlaps?

The overlaps function checks if two geometries overlap. Two geometries can only overlap if they have the same dimension, and if they overlap, but one is not contained, within, or equal to the other.

Note that this means it is impossible for a single point to overlap with a single point and a line only overlaps with another line if only a section of each line is collinear.

To provide an example, consider these two lines:

julia
import GeometryOps as GO
import GeoInterface as GI
using Makie
using CairoMakie

l1 = GI.LineString([(0.0, 0.0), (0.0, 10.0)])
l2 = GI.LineString([(0.0, -10.0), (0.0, 3.0)])
f, a, p = lines(GI.getpoint(l1), color = :blue)
scatter!(GI.getpoint(l1), color = :blue)
lines!(GI.getpoint(l2), color = :orange)
scatter!(GI.getpoint(l2), color = :orange)
f

We can see that the two lines overlap in the plot:

julia
GO.overlaps(l1, l2)  # true
true

Implementation

This is the GeoInterface-compatible implementation.

First, we implement a wrapper method that dispatches to the correct implementation based on the geometry trait. This is also used in the implementation, since it's a lot less work!

Note that that since only elements of the same dimension can overlap, any two geometries with traits that are of different dimensions automatically can return false.

For geometries with the same trait dimension, we must make sure that they share a point, an edge, or area for points, lines, and polygons/multipolygons respectively, without being contained.

julia
"""
    overlaps(geom1, geom2)::Bool

Compare two Geometries of the same dimension and return true if their
intersection set results in a geometry different from both but of the same
dimension. This means one geometry cannot be within or contain the other and
they cannot be equal

# Examples
```jldoctest
import GeometryOps as GO, GeoInterface as GI
poly1 = GI.Polygon([[(0,0), (0,5), (5,5), (5,0), (0,0)]])
poly2 = GI.Polygon([[(1,1), (1,6), (6,6), (6,1), (1,1)]])

GO.overlaps(poly1, poly2)

output

julia
true
```
"""
overlaps(geom1, geom2)::Bool = overlaps(
    GI.trait(geom1),
    geom1,
    GI.trait(geom2),
    geom2,
)

"""
    overlaps(::GI.AbstractTrait, geom1, ::GI.AbstractTrait, geom2)::Bool

For any non-specified pair, all have non-matching dimensions, return false.
"""
overlaps(::GI.AbstractTrait, geom1, ::GI.AbstractTrait, geom2) = false

"""
    overlaps(
        ::GI.MultiPointTrait, points1,
        ::GI.MultiPointTrait, points2,
    )::Bool

If the multipoints overlap, meaning some, but not all, of the points within the
multipoints are shared, return true.
"""
function overlaps(
    ::GI.MultiPointTrait, points1,
    ::GI.MultiPointTrait, points2,
)
    one_diff = false  # assume that all the points are the same
    one_same = false  # assume that all points are different
    for p1 in GI.getpoint(points1)
        match_point = false
        for p2 in GI.getpoint(points2)
            if equals(p1, p2)  # Point is shared
                one_same = true
                match_point = true
                break
            end
        end
        one_diff |= !match_point  # Point isn't shared
        one_same && one_diff && return true
    end
    return false
end

"""
    overlaps(::GI.LineTrait, line1, ::GI.LineTrait, line)::Bool

If the lines overlap, meaning that they are collinear but each have one endpoint
outside of the other line, return true. Else false.
"""
overlaps(::GI.LineTrait, line1, ::GI.LineTrait, line) =
    _overlaps((a1, a2), (b1, b2))

"""
    overlaps(
        ::Union{GI.LineStringTrait, GI.LinearRing}, line1,
        ::Union{GI.LineStringTrait, GI.LinearRing}, line2,
    )::Bool

If the curves overlap, meaning that at least one edge of each curve overlaps,
return true. Else false.
"""
function overlaps(
    ::Union{GI.LineStringTrait, GI.LinearRing}, line1,
    ::Union{GI.LineStringTrait, GI.LinearRing}, line2,
)
    edges_a, edges_b = map(sort! ∘ to_edges, (line1, line2))
    for edge_a in edges_a
        for edge_b in edges_b
            _overlaps(edge_a, edge_b) && return true
        end
    end
    return false
end

"""
    overlaps(
        trait_a::GI.PolygonTrait, poly_a,
        trait_b::GI.PolygonTrait, poly_b,
    )::Bool

If the two polygons intersect with one another, but are not equal, return true.
Else false.
"""
function overlaps(
    trait_a::GI.PolygonTrait, poly_a,
    trait_b::GI.PolygonTrait, poly_b,
)
    edges_a, edges_b = map(sort! ∘ to_edges, (poly_a, poly_b))
    return _line_intersects(edges_a, edges_b) &&
        !equals(trait_a, poly_a, trait_b, poly_b)
end

"""
    overlaps(
        ::GI.PolygonTrait, poly1,
        ::GI.MultiPolygonTrait, polys2,
    )::Bool

Return true if polygon overlaps with at least one of the polygons within the
multipolygon. Else false.
"""
function overlaps(
    ::GI.PolygonTrait, poly1,
    ::GI.MultiPolygonTrait, polys2,
)
    for poly2 in GI.getgeom(polys2)
        overlaps(poly1, poly2) && return true
    end
    return false
end

"""
    overlaps(
        ::GI.MultiPolygonTrait, polys1,
        ::GI.PolygonTrait, poly2,
    )::Bool

Return true if polygon overlaps with at least one of the polygons within the
multipolygon. Else false.
"""
overlaps(trait1::GI.MultiPolygonTrait, polys1, trait2::GI.PolygonTrait, poly2) =
    overlaps(trait2, poly2, trait1, polys1)

"""
    overlaps(
        ::GI.MultiPolygonTrait, polys1,
        ::GI.MultiPolygonTrait, polys2,
    )::Bool

Return true if at least one pair of polygons from multipolygons overlap. Else
false.
"""
function overlaps(
    ::GI.MultiPolygonTrait, polys1,
    ::GI.MultiPolygonTrait, polys2,
)
    for poly1 in GI.getgeom(polys1)
        overlaps(poly1, polys2) && return true
    end
    return false
end

#= If the edges overlap, meaning that they are collinear but each have one endpoint
outside of the other edge, return true. Else false. =#
function _overlaps(
    (a1, a2)::Edge,
    (b1, b2)::Edge,
    exact = _False(),
)

meets in more than one point

julia
    seg_val, _, _ = _intersection_point(Float64, (a1, a2), (b1, b2); exact)

one end point is outside of other segment

julia
    a_fully_within = _point_on_seg(a1, b1, b2) && _point_on_seg(a2, b1, b2)
    b_fully_within = _point_on_seg(b1, a1, a2) && _point_on_seg(b2, a1, a2)
    return seg_val == line_over && (!a_fully_within && !b_fully_within)
end

#= TODO: Once overlaps is swapped over to use the geom relations workflow, can
delete these helpers. =#

Checks if point is on a segment

julia
function _point_on_seg(point, start, stop)

Parse out points

julia
    x, y = GI.x(point), GI.y(point)
    x1, y1 = GI.x(start), GI.y(start)
    x2, y2 = GI.x(stop), GI.y(stop)
    Δxl = x2 - x1
    Δyl = y2 - y1

Determine if point is on segment

julia
    cross = (x - x1) * Δyl - (y - y1) * Δxl
    if cross == 0  # point is on line extending to infinity

is line between endpoints

julia
        if abs(Δxl) >= abs(Δyl)  # is line between endpoints
            return Δxl > 0 ? x1 <= x <= x2 : x2 <= x <= x1
        else
            return Δyl > 0 ? y1 <= y <= y2 : y2 <= y <= y1
        end
    end
    return false
end

#= Returns true if there is at least one intersection between edges within the
two lists of edges. =#
function _line_intersects(
    edges_a::Vector{<:Edge},
    edges_b::Vector{<:Edge};
)

Extents.intersects(to_extent(edges_a), to_extent(edges_b)) || return false

julia
    for edge_a in edges_a
        for edge_b in edges_b
            _line_intersects(edge_a, edge_b) && return true
        end
    end
    return false
end

Returns true if there is at least one intersection between two edges.

julia
function _line_intersects(edge_a::Edge, edge_b::Edge)
    seg_val, _, _ = _intersection_point(Float64, edge_a, edge_b; exact = _False())
    return seg_val != line_out
end

This page was generated using Literate.jl.