diff --git a/src/methods/geom_relations/crosses.jl b/src/methods/geom_relations/crosses.jl index fc5f7a843..40468d6d0 100644 --- a/src/methods/geom_relations/crosses.jl +++ b/src/methods/geom_relations/crosses.jl @@ -1,137 +1,215 @@ -# # Crossing checks +# # Crosses -""" - crosses(geom1, geom2)::Bool +export crosses -Return `true` if the intersection results in a geometry whose dimension is one less than -the maximum dimension of the two source geometries and the intersection set is interior to -both source geometries. +#= +## What is crosses? -TODO: broken +The crosses function checks if one geometry is crosses another geometry. +A geometry can only cross another geometry if they are either two lines, or if +the two geometries have different dimensionalities. If checking two lines, they +must meet in one point. If checking two geometries of different dimensions, the +interiors must meet in at least one point and at least one of the geometries +must have a point outside of the other geometry. -## Examples -```julia -import GeoInterface as GI, GeometryOps as GO +Note that points can't cross any geometries, despite different dimension, due to +their inability to be both interior and exterior to any other shape. -line1 = GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)]) -line2 = GI.LineString([(-2, 2), (4, 2)]) +To provide an example, consider these two lines: +```@example crosses +using GeometryOps +using GeometryOps.GeometryBasics +using Makie +using CairoMakie -GO.crosses(line1, line2) -# output -true +l1 = Line([Point(0.0, 0.0), Point(1.0, 0.0)]) +l2 = Line([Point(0.5, 1.0), Point(0.5, -1.0)]) + +f, a, p = lines(l1) +lines!(l2) ``` +We can see that these two lines cross at their midpoints. +```@example crosses +crosses(l1, l2) # 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. + +Each of these calls a method in the geom_geom_processors file. The methods in +this file determine if the given geometries meet a set of criteria. For the +`crosses` function and arguments g1 and g2, this criteria is as follows: + - points of g1 are allowed to be in the interior of g2 (only through + crossing and NOT overlap for lines) + - points of g1 are allowed to be on the boundary of g2 + - points of g1 are allowed to be in the exterior of g2 + - at least one point of g1 are required to be in the interior of g2 + - no points of g1 are required to be on the boundary of g2 + - at least one point of g1 are required to be in the exterior of g2 + +The code for the specific implementations is in the geom_geom_processors file. +=# + +const CROSSES_CURVE_ALLOWS = (over_allow = false, cross_allow = true, on_allow = true, out_allow = true) +const CROSSES_POLYGON_ALLOWS = (in_allow = true, on_allow = true, out_allow = true) +const CROSSES_REQUIRES = (in_require = true, on_require = false, out_require = true) + """ -crosses(g1, g2)::Bool = crosses(trait(g1), g1, trait(g2), g2)::Bool -crosses(t1::FeatureTrait, g1, t2, g2)::Bool = crosses(GI.geometry(g1), g2) -crosses(t1, g1, t2::FeatureTrait, g2)::Bool = crosses(g1, geometry(g2)) -crosses(::MultiPointTrait, g1, ::LineStringTrait, g2)::Bool = multipoint_crosses_line(g1, g2) -crosses(::MultiPointTrait, g1, ::PolygonTrait, g2)::Bool = multipoint_crosses_poly(g1, g2) -crosses(::LineStringTrait, g1, ::MultiPointTrait, g2)::Bool = multipoint_crosses_lines(g2, g1) -crosses(::LineStringTrait, g1, ::PolygonTrait, g2)::Bool = line_crosses_poly(g1, g2) -crosses(::LineStringTrait, g1, ::LineStringTrait, g2)::Bool = line_crosses_line(g1, g2) -crosses(::PolygonTrait, g1, ::MultiPointTrait, g2)::Bool = multipoint_crosses_poly(g2, g1) -crosses(::PolygonTrait, g1, ::LineStringTrait, g2)::Bool = line_crosses_poly(g2, g1) - -function multipoint_crosses_line(geom1, geom2) - int_point = false - ext_point = false - i = 1 - np2 = GI.npoint(geom2) - - while i < GI.npoint(geom1) && !int_point && !ext_point - for j in 1:GI.npoint(geom2) - 1 - exclude_boundary = (j === 1 || j === np2 - 2) ? :none : :both - if _point_on_segment(GI.getpoint(geom1, i), (GI.getpoint(geom2, j), GI.getpoint(geom2, j + 1)); exclude_boundary) - int_point = true - else - ext_point = true - end - end - i += 1 - end + crosses(geom1, geom2)::Bool - return int_point && ext_point -end +Return `true` if the first geometry crosses the second geometry. If they are +both lines, they must meet in one point. Otherwise, they must be of different +dimensions, the interiors must intersect, and the interior of the first geometry +must intersect the exterior of the secondary geometry. -function line_crosses_line(line1, line2) - np2 = GI.npoint(line2) - if GeometryOps.intersects(line1, line2) - for i in 1:GI.npoint(line1) - 1 - for j in 1:GI.npoint(line2) - 1 - exclude_boundary = (j === 1 || j === np2 - 2) ? :none : :both - pa = GI.getpoint(line1, i) - pb = GI.getpoint(line1, i + 1) - p = GI.getpoint(line2, j) - _point_on_segment(p, (pa, pb); exclude_boundary) && return true - end - end - end - return false -end +## Examples +```jldoctest setup=:(using GeometryOps, GeometryBasics) +import GeometryOps as GO, GeoInterface as GI -function line_crosses_poly(line, poly) - for l in flatten(AbstractCurveTrait, poly) - intersects(line, l) && return true - end - return false -end +l1 = GI.Line([(0.0, 0.0), (1.0, 0.0)]) +l2 = GI.Line([(0.5, 1.0), (0.5, -1.0)]) -function multipoint_crosses_poly(mp, poly) - int_point = false - ext_point = false - - for p in GI.getpoint(mp) - if _point_polygon_process( - p, poly; - in_allow = true, on_allow = true, out_allow = false, - ) - int_point = true - else - ext_point = true - end - int_point && ext_point && return true +GO.crosses(l1, l2) +# output +true +``` +""" +crosses(g1, g2) = _crosses(trait(g1), g1, trait(g2), g2) + +# # Convert features to geometries +_crosses(::GI.FeatureTrait, g1, ::Any, g2) = crosses(GI.geometry(g1), g2) +_crosses(::Any, g1, t2::GI.FeatureTrait, g2) = crosses(g1, GI.geometry(g2)) + + +# # Non-specified geometries + +# Points and geometries with the same dimensions D where D ≂̸ 1 default to false +_crosses( + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1, + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g2, +) = false + + +# # Lines cross geometries + +#= Linestring crosses another linestring if the intersection of the two lines +is exlusivly points (only cross intersections) =# +_crosses( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, +) = _line_curve_process( + g1, g2; + CROSSES_CURVE_ALLOWS..., + CROSSES_REQUIRES..., + closed_line = false, + closed_curve = false, +) + +#= Linestring crosses a linearring if the intersection of the line and ring is +exlusivly points (only cross intersections) =# +_crosses( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_process( + g1, g2; + CROSSES_CURVE_ALLOWS..., + CROSSES_REQUIRES..., + closed_line = false, + closed_curve = true, +) + +#= Linestring crosses a polygon if at least some of the line interior is in the +polygon interior and some of the line interior is exterior to the polygon. =# +_crosses( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::GI.PolygonTrait, g2, +) = _line_polygon_process( + g1, g2; + CROSSES_POLYGON_ALLOWS..., + CROSSES_REQUIRES..., + closed_line = false, +) + + +# # Rings cross geometries + +#= Linearring crosses a linestring if the intersection of the line and ring is +exlusivly points (only cross intersections) =# +_crosses( + trait1::GI.LinearRingTrait, g1, + trait2::Union{GI.LineTrait, GI.LineStringTrait}, g2, +) = _crosses(trait2, g2, trait1, g1) + +#= Linearring crosses another ring if the intersection of the two rings is +exlusivly points (only cross intersections) =# +_crosses( + ::GI.LinearRingTrait, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_process( + g1, g2; + CROSSES_CURVE_ALLOWS..., + CROSSES_REQUIRES..., + closed_line = true, + closed_curve = true, +) + +#= Linearring crosses a polygon if at least some of the ring interior is in the +polygon interior and some of the ring interior is exterior to the polygon. =# +_crosses( + ::GI.LinearRingTrait, g1, + ::GI.PolygonTrait, g2, +) = _line_polygon_process( + g1, g2; + CROSSES_POLYGON_ALLOWS..., + CROSSES_REQUIRES..., + closed_line = true, +) + + +# # Polygons cross geometries + +#= Polygon crosses a curve if at least some of the curve interior is in the +polygon interior and some of the curve interior is exterior to the polygon.=# +_crosses( + trait1::GI.PolygonTrait, g1, + trait2::GI.AbstractCurveTrait, g2 +) = _crosses(trait2, g2, trait1, g1) + + +# # Geometries cross multi-geometry/geometry collections + +#= Geometry crosses a multi-geometry or a collection if the geometry crosses +one of the elements of the collection. =# +function _crosses( + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1, + ::Union{ + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g2, +) + for sub_g2 in GI.getgeom(g2) + crosses(g1, sub_g2) && return true end return false end -#= TODO: Once crosses is swapped over to use the geom relations workflow, can -delete these helpers. =# - -function _point_on_segment(point, (start, stop); exclude_boundary::Symbol=:none)::Bool - x, y = GI.x(point), GI.y(point) - x1, y1 = GI.x(start), GI.y(start) - x2, y2 = GI.x(stop), GI.y(stop) - - dxc = x - x1 - dyc = y - y1 - dx1 = x2 - x1 - dy1 = y2 - y1 - - # TODO use better predicate for crossing here - cross = dxc * dy1 - dyc * dx1 - cross != 0 && return false - - # Will constprop optimise these away? - if exclude_boundary === :none - if abs(dx1) >= abs(dy1) - return dx1 > 0 ? x1 <= x && x <= x2 : x2 <= x && x <= x1 - end - return dy1 > 0 ? y1 <= y && y <= y2 : y2 <= y && y <= y1 - elseif exclude_boundary === :start - if abs(dx1) >= abs(dy1) - return dx1 > 0 ? x1 < x && x <= x2 : x2 <= x && x < x1 - end - return dy1 > 0 ? y1 < y && y <= y2 : y2 <= y && y < y1 - elseif exclude_boundary === :end - if abs(dx1) >= abs(dy1) - return dx1 > 0 ? x1 <= x && x < x2 : x2 < x && x <= x1 - end - return dy1 > 0 ? y1 <= y && y < y2 : y2 < y && y <= y1 - elseif exclude_boundary === :both - if abs(dx1) >= abs(dy1) - return dx1 > 0 ? x1 < x && x < x2 : x2 < x && x < x1 - end - return dy1 > 0 ? y1 < y && y < y2 : y2 < y && y < y1 +# # Multi-geometry/geometry collections cross geometries + +#= Multi-geometry or a geometry collection crosses a geometry one elements of +the collection crosses the geometry. =# +function _crosses( + ::Union{ + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g1, + ::GI.AbstractGeometryTrait, g2, +) + for sub_g1 in GI.getgeom(g1) + crosses(sub_g1, g2) && return true end return false -end +end \ No newline at end of file diff --git a/src/methods/geom_relations/overlaps.jl b/src/methods/geom_relations/overlaps.jl index 32b9b6583..62fc28cbe 100644 --- a/src/methods/geom_relations/overlaps.jl +++ b/src/methods/geom_relations/overlaps.jl @@ -5,13 +5,14 @@ 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. +The overlaps function checks if two geometries overlap. Two geometries overlap +if they have the same dimension, and if they overlap then their interiors +interact, but they both also need interior points exterior to the other +geometry. 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 colinear. +each line is colinear (crosses don't count for interior points interacting). To provide an example, consider these two lines: ```@example overlaps @@ -37,25 +38,34 @@ overlap(l1, l2) 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 autmoatically 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 -respectivly, without being contained. +implementation based on the geometry trait. + +Each of these calls a method in the geom_geom_processors file. The methods in +this file determine if the given geometries meet a set of criteria. For the +`overlaps` function and arguments g1 and g2, this criteria is as follows: + - points of g1 are allowed to be in the interior of g2 + - points of g1 are allowed to be on the boundary of g2 + - points of g1 are allowed to be in the exterior of g2 + - at least one point of g1 is required to be in the interior of g2 + - at least one point of g2 is required to be in the interior of g1 + - no points of g1 is required to be on the boundary of g2 + - at least one point of g1 is required to be in the exterior of g2 + - at least one point of g2 is required to be in the exterior of g1 + +The code for the specific implementations is in the geom_geom_processors file. =# +const OVERLAPS_CURVE_ALLOWS = (over_allow = true, cross_allow = false, on_allow = true, out_allow = true) +const OVERLAPS_POLYGON_ALLOWS = (in_allow = true, on_allow = true, out_allow = true) +const OVERLAPS_REQUIRES = (in_require = true, on_require = false, out_require = true) + """ 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 +Compare two Geometries of the same dimension and return true if their interiors +interact, but they both also have interior points exterior to the other +geometry. Lines crossing doesn't count for interiors interacting as overlaps +of curves must be of dimension one. ## Examples ```jldoctest @@ -68,38 +78,124 @@ GO.overlaps(poly1, poly2) true ``` """ -overlaps(geom1, geom2)::Bool = overlaps( - GI.trait(geom1), - geom1, - GI.trait(geom2), - geom2, +overlaps(g1, g2)::Bool = _overlaps(GI.trait(g1), g1, GI.trait(g2), g2) + + +# # Convert features to geometries +_overlaps(::GI.FeatureTrait, g1, ::Any, g2) = overlaps(GI.geometry(g1), g2) +_overlaps(::Any, g1, t2::GI.FeatureTrait, g2) = overlaps(g1, GI.geometry(g2)) + + +# # Non-specified geometries + +# Geometries of different dimensions and points cannot overlap and return false +_overlaps( + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1, + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g2, +) = false + + +# # Lines cross curves + +#= Linestring overlaps with another linestring when they share co-linear +segments (interiors interacting), but both have interior points exterior to the +other line. =# +_overlaps( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, +) = _line_curve_process( + g1, g2; + OVERLAPS_CURVE_ALLOWS..., + OVERLAPS_REQUIRES..., + closed_line = false, + closed_curve = false, +) && _line_curve_process( + g2, g1; + OVERLAPS_CURVE_ALLOWS..., + OVERLAPS_REQUIRES..., + closed_line = false, + closed_curve = false, + ) + +#= Linestring overlaps with a linearring when they share co-linear segments +(interiors interacting), but both have interior points exterior to the other. =# +_overlaps( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_process( + g1, g2; + OVERLAPS_CURVE_ALLOWS..., + OVERLAPS_REQUIRES..., + closed_line = false, + closed_curve = true, +) && _line_curve_process( + g2, g1; + OVERLAPS_CURVE_ALLOWS..., + OVERLAPS_REQUIRES..., + closed_line = true, + closed_curve = false, + ) + + +# # Rings cross curves + +#= Linearring overlaps with a linestring when they share co-linear segments +(interiors interacting), but both have interior points exterior to the other. =# +_overlaps( + trait1::GI.LinearRingTrait, g1, + trait2::Union{GI.LineTrait, GI.LineStringTrait}, g2, +) = _overlaps(trait2, g2, trait1, g1) + +#= Linearring overlaps with another linearring when they share co-linear +segments (interiors interacting), but both have interior points exterior to the +other line. =# +_overlaps( + ::GI.LinearRingTrait, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_process( + g1, g2; + OVERLAPS_CURVE_ALLOWS..., + OVERLAPS_REQUIRES..., + closed_line = true, + closed_curve = true, +) && _line_curve_process( + g2, g1; + OVERLAPS_CURVE_ALLOWS..., + OVERLAPS_REQUIRES..., + closed_line = true, + closed_curve = true, + ) + + +# # Polygons cross polygons + +#= Polygon overlaps with another polygon when their interiors intersect, but +both have interior points exterior to the other polygon. =# +_overlaps( + ::GI.PolygonTrait, g1, + ::GI.PolygonTrait, g2, +) = _polygon_polygon_process( + g1, g2; + OVERLAPS_POLYGON_ALLOWS..., + OVERLAPS_REQUIRES..., +) && _polygon_polygon_process( + g2, g1; + OVERLAPS_POLYGON_ALLOWS..., + OVERLAPS_REQUIRES..., ) -""" - 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 +# # Geometries disjoint multi-geometry/geometry collections -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, +# Multipoints overlap with other multipoints if only some sub-points are shared +function _overlaps( + ::GI.MultiPointTrait, g1, + ::GI.MultiPointTrait, g2, ) 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) + for p1 in GI.getpoint(g1) match_point = false - for p2 in GI.getpoint(points2) + for p2 in GI.getpoint(g2) if equals(p1, p2) # Point is shared one_same = true match_point = true @@ -112,159 +208,34 @@ function overlaps( return false end -""" - overlaps(::GI.LineTrait, line1, ::GI.LineTrait, line)::Bool - -If the lines overlap, meaning that they are colinear 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 _edge_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, +#= Geometry overlaps a multi-geometry or a collection if the geometry overlaps +at least one of the elements of the collection. =# +function _overlaps( + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1, + ::Union{ + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g2, ) - for poly2 in GI.getgeom(polys2) - overlaps(poly1, poly2) && return true + for sub_g2 in GI.getgeom(g2) + overlaps(g1, sub_g2) && 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 +# # Multi-geometry/geometry collections cross geometries -#= If the edges overlap, meaning that they are colinear but each have one endpoint -outside of the other edge, return true. Else false. =# +#= Multi-geometry or a geometry collection overlaps a geometry if at least one +elements of the collection overlaps the geometry. =# function _overlaps( - (a1, a2)::Edge, - (b1, b2)::Edge + ::Union{ + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g1, + ::GI.AbstractGeometryTrait, g2, ) - # meets in more than one point - on_top = ExactPredicates.meet(a1, a2, b1, b2) == 0 - # one end point is outside of other segment - 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 on_top && (!a_fully_within && !b_fully_within) -end - -#= TODO: Once overlaps is swapped over to use the geom relations workflow, can -delete these helpers. =# - -# Checks it vectors of edges intersect -function _edge_intersects( - edges_a::Vector{Edge}, - edges_b::Vector{Edge} -) - # Extents.intersects(to_extent(edges_a), to_extent(edges_b)) || return false - for edge_a in edges_a - for edge_b in edges_b - _edge_intersects(edge_a, edge_b) && return true - end - end - return false -end - -# Checks if two edges intersect -function _edge_intersects(edge_a::Edge, edge_b::Edge) - meet_type = ExactPredicates.meet(edge_a..., edge_b...) - return meet_type == 0 || meet_type == 1 -end - -# Checks if point is on a segment -function _point_on_seg(point, start, stop) - # Parse out points - 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 - cross = (x - x1) * Δyl - (y - y1) * Δxl - if cross == 0 # point is on line extending to infinity - # is line between endpoints - 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 + for sub_g1 in GI.getgeom(g1) + overlaps(sub_g1, g2) && return true end return false -end +end \ No newline at end of file diff --git a/test/methods/geom_relations.jl b/test/methods/geom_relations.jl index 50b0dfffd..543dd0d64 100644 --- a/test/methods/geom_relations.jl +++ b/test/methods/geom_relations.jl @@ -170,125 +170,9 @@ end @testset "Contains" begin test_geom_relation(GO.contains, LG.contains, "contains"; swap_points = true) end @testset "Covered By" begin test_geom_relation(GO.coveredby, LG.coveredby, "coveredby") end @testset "Covers" begin test_geom_relation(GO.covers, LG.covers, "covers"; swap_points = true) end +@testset "Crosses By" begin test_geom_relation(GO.crosses, LG.crosses, "crosses") end @testset "Disjoint" begin test_geom_relation(GO.disjoint, LG.disjoint, "disjoint")end @testset "Intersect" begin test_geom_relation(GO.intersects, LG.intersects, "intersects") end +@testset "Overlaps" begin test_geom_relation(GO.overlaps, LG.overlaps, "overlaps") end @testset "Touches" begin test_geom_relation(GO.touches, LG.touches, "touches") end -@testset "Within" begin test_geom_relation(GO.within, LG.within, "within") end - - -@testset "Overlaps" begin - @testset "Points/MultiPoints" begin - p1 = LG.Point([0.0, 0.0]) - p2 = LG.Point([0.0, 1.0]) - # Two points can't overlap - @test GO.overlaps(p1, p1) == LG.overlaps(p1, p2) - - mp1 = LG.MultiPoint([[0.0, 1.0], [4.0, 4.0]]) - mp2 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0]]) - mp3 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0], [3.0, 3.0]]) - # No shared points, doesn't overlap - @test GO.overlaps(p1, mp1) == LG.overlaps(p1, mp1) - # One shared point, does overlap - @test GO.overlaps(p2, mp1) == LG.overlaps(p2, mp1) - # All shared points, doesn't overlap - @test GO.overlaps(mp1, mp1) == LG.overlaps(mp1, mp1) - # Not all shared points, overlaps - @test GO.overlaps(mp1, mp2) == LG.overlaps(mp1, mp2) - # One set of points entirely inside other set, doesn't overlap - @test GO.overlaps(mp2, mp3) == LG.overlaps(mp2, mp3) - # Not all points shared, overlaps - @test GO.overlaps(mp1, mp3) == LG.overlaps(mp1, mp3) - - mp1 = LG.MultiPoint([ - [-36.05712890625, 26.480407161007275], - [-35.7220458984375, 27.137368359795584], - [-35.13427734375, 26.83387451505858], - [-35.4638671875, 27.254629577800063], - [-35.5462646484375, 26.86328062676624], - [-35.3924560546875, 26.504988828743404], - ]) - mp2 = GI.MultiPoint([ - [-35.4638671875, 27.254629577800063], - [-35.5462646484375, 26.86328062676624], - [-35.3924560546875, 26.504988828743404], - [-35.2001953125, 26.12091815959972], - [-34.9969482421875, 26.455820238459893], - ]) - # Some shared points, overlaps - @test GO.overlaps(mp1, mp2) == LG.overlaps(mp1, mp2) - @test GO.overlaps(mp1, mp2) == GO.overlaps(mp2, mp1) - end - - @testset "Lines/Rings" begin - l1 = LG.LineString([[0.0, 0.0], [0.0, 10.0]]) - l2 = LG.LineString([[0.0, -10.0], [0.0, 20.0]]) - l3 = LG.LineString([[0.0, -10.0], [0.0, 3.0]]) - l4 = LG.LineString([[5.0, -5.0], [5.0, 5.0]]) - # Line can't overlap with itself - @test GO.overlaps(l1, l1) == LG.overlaps(l1, l1) - # Line completely within other line doesn't overlap - @test GO.overlaps(l1, l2) == GO.overlaps(l2, l1) == LG.overlaps(l1, l2) - # Overlapping lines - @test GO.overlaps(l1, l3) == GO.overlaps(l3, l1) == LG.overlaps(l1, l3) - # Lines that don't touch - @test GO.overlaps(l1, l4) == LG.overlaps(l1, l4) - # Linear rings that intersect but don't overlap - r1 = LG.LinearRing([[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0], [0.0, 0.0]]) - r2 = LG.LinearRing([[1.0, 1.0], [1.0, 6.0], [6.0, 6.0], [6.0, 1.0], [1.0, 1.0]]) - @test LG.overlaps(r1, r2) == LG.overlaps(r1, r2) - end - - @testset "Polygons/MultiPolygons" begin - p1 = LG.Polygon([[[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0], [0.0, 0.0]]]) - p2 = LG.Polygon([ - [[10.0, 0.0], [10.0, 20.0], [30.0, 20.0], [30.0, 0.0], [10.0, 0.0]], - [[15.0, 1.0], [15.0, 11.0], [25.0, 11.0], [25.0, 1.0], [15.0, 1.0]] - ]) - # Test basic polygons that don't overlap - @test GO.overlaps(p1, p2) == LG.overlaps(p1, p2) - @test !GO.overlaps(p1, (1, 1)) - @test !GO.overlaps((1, 1), p2) - - p3 = LG.Polygon([[[1.0, 1.0], [1.0, 6.0], [6.0, 6.0], [6.0, 1.0], [1.0, 1.0]]]) - # Test basic polygons that overlap - @test GO.overlaps(p1, p3) == LG.overlaps(p1, p3) - - p4 = LG.Polygon([[[20.0, 5.0], [20.0, 10.0], [18.0, 10.0], [18.0, 5.0], [20.0, 5.0]]]) - # Test one polygon within the other - @test GO.overlaps(p2, p4) == GO.overlaps(p4, p2) == LG.overlaps(p2, p4) - - p5 = LG.Polygon( - [[ - [-53.57208251953125, 28.287451910503744], - [-53.33038330078125, 28.29228897739706], - [-53.34136352890625, 28.430052892335723], - [-53.57208251953125, 28.287451910503744], - ]] - ) - # Test equal polygons - @test GO.overlaps(p5, p5) == LG.overlaps(p5, p5) - - # Test multipolygons - m1 = LG.MultiPolygon([ - [[[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0], [0.0, 0.0]]], - [ - [[10.0, 0.0], [10.0, 20.0], [30.0, 20.0], [30.0, 0.0], [10.0, 0.0]], - [[15.0, 1.0], [15.0, 11.0], [25.0, 11.0], [25.0, 1.0], [15.0, 1.0]] - ] - ]) - # Test polygon that overlaps with multipolygon - @test GO.overlaps(m1, p3) == LG.overlaps(m1, p3) - # Test polygon in hole of multipolygon, doesn't overlap - @test GO.overlaps(m1, p4) == LG.overlaps(m1, p4) - end -end -@testset "Crosses" begin - line6 = GI.LineString([(1.0, 1.0), (1.0, 2.0), (1.0, 3.0), (1.0, 4.0)]) - poly7 = GI.Polygon([[(-1.0, 2.0), (3.0, 2.0), (3.0, 3.0), (-1.0, 3.0), (-1.0, 2.0)]]) - - @test GO.crosses(GI.LineString([(-2, 2), (4, 2)]), line6) == true - @test GO.crosses(GI.LineString([(0.5, 2.5), (1.0, 1.0)]), poly7) == true - @test GO.crosses(GI.MultiPoint([(1.0, 2.0), (12.0, 12.0)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == true - @test GO.crosses(GI.MultiPoint([(1.0, 0.0), (12.0, 12.0)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == false - @test GO.crosses(GI.LineString([(-2.0, 2.0), (-4.0, 2.0)]), poly7) == false -end \ No newline at end of file +@testset "Within" begin test_geom_relation(GO.within, LG.within, "within") end \ No newline at end of file