From aba090f5cd87b329629c7392b65af9b28d90335a Mon Sep 17 00:00:00 2001 From: Skylar A Gering Date: Mon, 2 Oct 2023 16:55:57 -0700 Subject: [PATCH 01/33] Fix up intersection point base calculation --- src/methods/intersects.jl | 162 +++++++++++++++++++++++++++++--------- 1 file changed, 126 insertions(+), 36 deletions(-) diff --git a/src/methods/intersects.jl b/src/methods/intersects.jl index e0476bd81..ca20ed321 100644 --- a/src/methods/intersects.jl +++ b/src/methods/intersects.jl @@ -2,14 +2,66 @@ export intersects, intersection -# This code checks whether geometries intersect with each other. +#= +## What is `intersects` vs `intersection`? + +The `intersects` methods check whether two geometries intersect with each other. +The `intersection` methods return the intersection between the two geometries. + +The `intersects` methods will always return a Boolean. However, note that the +`intersection` methods will not all return the same type. For example, the +intersection of two lines will be a point in most cases, unless the lines are +parallel. On the other hand, the intersection of two polygons will be another +polygon in most cases. + +To provide an example, consider this # TODO update this example: +```@example cshape +using GeometryOps +using GeometryOps.GeometryBasics +using Makie +using CairoMakie + +cshape = Polygon([ + Point(0,0), Point(0,3), Point(3,3), Point(3,2), Point(1,2), + Point(1,1), Point(3,1), Point(3,0), Point(0,0), +]) +f, a, p = poly(cshape; axis = (; aspect = DataAspect())) +``` +Let's see what the centroid looks like (plotted in red): +```@example cshape +cent = centroid(cshape) +scatter!(a, GI.x(cent), GI.y(cent), color = :red) +f +``` + +## Implementation -# !!! note -# This does not compute intersections, only checks if they exist. +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! + +# TODO fill this in! +=# const MEETS_OPEN = 1 const MEETS_CLOSED = 0 +intersects(geom1, geom2) = GO.intersects( + GI.trait(geom1), + geom1, + GI.trait(geom2), + geom2, +) + +GO.intersects( + trait1::Union{GI.LineStringTrait, GI.LinearRingTrait}, + geom1, + trait2::Union{GI.LineStringTrait, GI.LinearRingTrait}, + geom2, +) = line_intersects(trait1, geom1, trait2, geom2) + """ line_intersects(line_a, line_b) @@ -73,53 +125,91 @@ GO.line_intersection(line1, line2) (125.58375366067547, -14.83572303404496) ``` """ -line_intersection(line_a, line_b) = line_intersection(trait(line_a), line_a, trait(line_b), line_b) -function line_intersection(::GI.AbstractTrait, a, ::GI.AbstractTrait, b) +line_intersection(line_a, line_b) = intersection_points(trait(line_a), line_a, trait(line_b), line_b) + +""" + intersection_points( + ::GI.AbstractTrait, geom_a, + ::GI.AbstractTrait, geom_b, + )::Vector{::Tuple{::Real, ::Real}} + +Calculates the list of intersection points between two geometries. +""" +function intersection_points(::GI.AbstractTrait, a, ::GI.AbstractTrait, b) Extents.intersects(GI.extent(a), GI.extent(b)) || return nothing result = Tuple{Float64,Float64}[] edges_a, edges_b = map(sort! ∘ to_edges, (a, b)) for edge_a in edges_a for edge_b in edges_b - x = _line_intersection(edge_a, edge_b) + x = _intersection_point(edge_a, edge_b) isnothing(x) || push!(result, x) end end return result end -function line_intersection(::GI.LineTrait, line_a, ::GI.LineTrait, line_b) + +""" + intersection_point( + ::GI.LineTrait, line_a, + ::GI.LineTrait, line_b, + )::Union{ + ::Tuple{::Real, ::Real}, + ::Nothing + } + +Calculates the intersection point between two lines if it exists and return +`nothing` if it doesn't exist. +""" +function intersection_point(::GI.LineTrait, line_a, ::GI.LineTrait, line_b) + # Get start and end points for both lines a1 = GI.getpoint(line_a, 1) - b1 = GI.getpoint(line_b, 1) a2 = GI.getpoint(line_a, 2) + b1 = GI.getpoint(line_b, 1) b2 = GI.getpoint(line_b, 2) - - return _line_intersection((a1, a2), (b1, b2)) + # Determine the intersection point + point, _ = _intersection_point((a1, a2), (b1, b2)) + return point end -function _line_intersection((p11, p12)::Tuple, (p21, p22)::Tuple) - # Get points from lines - x1, y1 = GI.x(p11), GI.y(p11) - x2, y2 = GI.x(p12), GI.y(p12) - x3, y3 = GI.x(p21), GI.y(p21) - x4, y4 = GI.x(p22), GI.y(p22) - - d = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1)) - a = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3)) - b = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3)) - - if d == 0 - if a == 0 && b == 0 - return nothing - end - return nothing - end - - ã = a / d - b̃ = b / d - if ã >= 0 && ã <= 1 && b̃ >= 0 && b̃ <= 1 - x = x1 + (ã * (x2 - x1)) - y = y1 + (ã * (y2 - y1)) - return (x, y) +""" + _intersection_point( + (p11, p12)::Tuple, + (p21, p22)::Tuple, + ) + +Calculates the intersection point between two lines if it exists, and the +fractional component of each line from the initial end point to the +intersection point. +Inputs: + (p11, p12)::Tuple{Tuple{::Real, ::Real}, Tuple{::Real, ::Real}} first line + (p21, p22)::Tuple{Tuple{::Real, ::Real}, Tuple{::Real, ::Real}} second line +Outputs: + (x, y)::Tuple{::Real, ::Real} intersection point + (t, u)::Tuple{::Real, ::Real} fractional length of lines to intersection + Both are ::Nothing if point doesn't exist! + +Calculation derivation can be found here: + https://stackoverflow.com/questions/563198/ +""" +function _intersection_point((p11, p12)::Tuple, (p21, p22)::Tuple) + # First line runs from p to p + r + px, py = GI.x(p11), GI.y(p11) + rx, ry = GI.x(p12) - px, GI.y(p12) - py + # Second line runs from q to q + s + qx, qy = GI.x(p21), GI.y(p21) + sx, sy = GI.x(p22) - qx, GI.y(p22) - qy + # Intersection will be where p + tr = q + us where 0 < t, u < 1 and + r_cross_s = rx * sy - ry * sx + if r_cross_s != 0 + Δpq_x = px - qx + Δpq_y = py - qy + t = (Δpq_x * sy - Δpq_y * sx) / r_cross_s + u = (Δpq_x * ry - Δpq_y * rx) / r_cross_s + if 0 <= t <= 1 && 0 <= u <= 1 + x = px + t * rx + y = py + t * ry + return (x, y), (t, u) + end end - - return nothing + return nothing, nothing end From ab167851686704fcde6d81923cad1ba57fee2bee Mon Sep 17 00:00:00 2001 From: Skylar A Gering Date: Tue, 3 Oct 2023 18:18:36 -0700 Subject: [PATCH 02/33] Update intersects and add line tests --- Project.toml | 6 +- src/methods/intersects.jl | 337 +++++++++++++++++++++++----------- src/transformations/extent.jl | 2 +- src/utils.jl | 2 +- test/methods/intersects.jl | 108 +++++++++++ test/runtests.jl | 1 + 6 files changed, 346 insertions(+), 110 deletions(-) create mode 100644 test/methods/intersects.jl diff --git a/Project.toml b/Project.toml index 318c474d8..0fe3b53d8 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ authors = ["Anshul Singhvi and contributors"] version = "0.0.1-DEV" [deps] +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" ExactPredicates = "429591f6-91af-11e9-00e2-59fbe8cec110" GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f" GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" @@ -26,7 +27,4 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = [ - "ArchGDAL", "Distributions", "GeoFormatTypes", "GeoJSON", "LibGEOS", - "Random", "Test", -] +test = ["ArchGDAL", "Distributions", "GeoFormatTypes", "GeoJSON", "LibGEOS", "Random", "Test"] diff --git a/src/methods/intersects.jl b/src/methods/intersects.jl index ca20ed321..6f3ca4abc 100644 --- a/src/methods/intersects.jl +++ b/src/methods/intersects.jl @@ -1,36 +1,42 @@ # # Intersection checks -export intersects, intersection +export intersects, intersection, intersection_points #= -## What is `intersects` vs `intersection`? +## What is `intersects` vs `intersection` vs `intersection_points`? The `intersects` methods check whether two geometries intersect with each other. -The `intersection` methods return the intersection between the two geometries. +The `intersection` methods return the geometry intersection between the two +input geometries. The `intersection_points` method returns a list of +intersection points between two geometries. The `intersects` methods will always return a Boolean. However, note that the `intersection` methods will not all return the same type. For example, the intersection of two lines will be a point in most cases, unless the lines are parallel. On the other hand, the intersection of two polygons will be another -polygon in most cases. +polygon in most cases. Finally, the `intersection_points` method returns a list +of tuple points. -To provide an example, consider this # TODO update this example: -```@example cshape +To provide an example, consider these two lines: +```@example intersects_intersection using GeometryOps using GeometryOps.GeometryBasics using Makie using CairoMakie - -cshape = Polygon([ - Point(0,0), Point(0,3), Point(3,3), Point(3,2), Point(1,2), - Point(1,1), Point(3,1), Point(3,0), Point(0,0), -]) -f, a, p = poly(cshape; axis = (; aspect = DataAspect())) +point1, point2 = Point(124.584961,-12.768946), Point(126.738281,-17.224758) +point3, point4 = Point(123.354492,-15.961329), Point(127.22168,-14.008696) +line1 = Line(point1, point2) +line2 = Line(point3, point4) +f, a, p = lines([point1, point2]) +lines!([point3, point4]) ``` -Let's see what the centroid looks like (plotted in red): -```@example cshape -cent = centroid(cshape) -scatter!(a, GI.x(cent), GI.y(cent), color = :red) +We can see that they intersect, so we expect intersects to return true, and we +can visualize the intersection point in red. +```@example intersects_intersection +int_bool = GO.intersects(line1, line2) +println(int_bool) +int_point = GO.intersection(line1, line2) +scatter!(int_point, color = :red) f ``` @@ -38,36 +44,24 @@ f 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! - -# TODO fill this in! +First, we implement a wrapper method for intersects, intersection, and +intersection_points that dispatches to the correct implementation based on the +geometry trait. The two underlying helper functions that are widely used in all +geometry dispatches are _line_intersects, which determines if two line segments +intersect and _intersection_point which determines the intersection point +between two line segments. =# -const MEETS_OPEN = 1 const MEETS_CLOSED = 0 - -intersects(geom1, geom2) = GO.intersects( - GI.trait(geom1), - geom1, - GI.trait(geom2), - geom2, -) - -GO.intersects( - trait1::Union{GI.LineStringTrait, GI.LinearRingTrait}, - geom1, - trait2::Union{GI.LineStringTrait, GI.LinearRingTrait}, - geom2, -) = line_intersects(trait1, geom1, trait2, geom2) +const MEETS_OPEN = 1 """ - line_intersects(line_a, line_b) - -Check if `line_a` intersects with `line_b`. + intersects(geom1, geom2; kw...)::Bool -These can be `LineTrait`, `LineStringTrait` or `LinearRingTrait` +Check if two geometries intersect, returning true if so and false otherwise. +Takes in a Int keyword meets, which can either be MEETS_OPEN (1), meaning that +only intersections through open edges where edge endpoints are not included are +recorded, versus MEETS_CLOSED (0) where edge endpoints are included. ## Example @@ -76,41 +70,80 @@ import GeoInterface as GI, GeometryOps as GO line1 = GI.Line([(124.584961,-12.768946), (126.738281,-17.224758)]) line2 = GI.Line([(123.354492,-15.961329), (127.22168,-14.008696)]) -GO.line_intersects(line1, line2) +GO.intersects(line1, line2) # output true ``` """ -line_intersects(a, b; kw...) = line_intersects(trait(a), a, trait(b), b; kw...) -# Skip to_edges for LineTrait -function line_intersects(::GI.LineTrait, a, ::GI.LineTrait, b; meets=MEETS_OPEN) +intersects(geom1, geom2; kw...) = intersects( + GI.trait(geom1), + geom1, + GI.trait(geom2), + geom2; + kw... +) + +""" + intersects(::GI.LineTrait, a, ::GI.LineTrait, b; meets = MEETS_OPEN)::Bool + +Returns true if two line segments intersect and false otherwise. Line segment +endpoints are excluded in check if `meets = MEETS_OPEN` (1) and included if +`meets = MEETS_CLOSED` (0). +""" +function intersects(::GI.LineTrait, a, ::GI.LineTrait, b; meets = MEETS_OPEN) a1 = _tuple_point(GI.getpoint(a, 1)) - b1 = _tuple_point(GI.getpoint(b, 1)) a2 = _tuple_point(GI.getpoint(a, 2)) + b1 = _tuple_point(GI.getpoint(b, 1)) b2 = _tuple_point(GI.getpoint(b, 2)) - return ExactPredicates.meet(a1, a2, b1, b2) == meets + meet_type = ExactPredicates.meet(a1, a2, b1, b2) + return meet_type == MEETS_OPEN || meet_type == meets end -function line_intersects(::GI.AbstractTrait, a, ::GI.AbstractTrait, b; kw...) + +""" + intersects(::GI.AbstractTrait, a, ::GI.AbstractTrait, b; kw...)::Bool + +Returns true if two geometries intersect with one another and false +otherwise. For all geometries but lines, conver the geometry to a list of edges +and cross compare the edges for intersections. +""" +function intersects(::GI.AbstractTrait, a, ::GI.AbstractTrait, b; kw...) edges_a, edges_b = map(sort! ∘ to_edges, (a, b)) - return line_intersects(edges_a, edges_b; kw...) + return _line_intersects(edges_a, edges_b; kw...) end -function line_intersects(edges_a::Vector{Edge}, edges_b::Vector{Edge}; meets=MEETS_OPEN) + +""" + _line_intersects( + edges_a::Vector{Edge}, + edges_b::Vector{Edge}; + meets = MEETS_OPEN, + )::Bool + +Returns true if there is at least one intersection between edges within the +two lists. Line segment endpoints are excluded in check if `meets = MEETS_OPEN` +(1) and included if `meets = MEETS_CLOSED` (0). +""" +function _line_intersects( + edges_a::Vector{Edge}, + edges_b::Vector{Edge}; + meets = MEETS_OPEN, +) # Extents.intersects(to_extent(edges_a), to_extent(edges_b)) || return false for edge_a in edges_a for edge_b in edges_b - ExactPredicates.meet(edge_a..., edge_b...) == meets && return true + meet_type = ExactPredicates.meet(edge_a..., edge_b...) + (meet_type == MEETS_OPEN || meet_type == meets) && return true end end return false end """ - line_intersection(line_a, line_b) - -Find a point that intersects LineStrings with two coordinates each. + intersection(geom_a, geom_b)::Union{Tuple{::Real, ::Real}, ::Nothing} -Returns `nothing` if no point is found. +Return an intersection point between two geometries. Return nothing if none are +found. Else, the return type depends on the input. It will be a union between: +a point, a line, a linear ring, a polygon, or a multipolygon ## Example @@ -119,37 +152,17 @@ import GeoInterface as GI, GeometryOps as GO line1 = GI.Line([(124.584961,-12.768946), (126.738281,-17.224758)]) line2 = GI.Line([(123.354492,-15.961329), (127.22168,-14.008696)]) -GO.line_intersection(line1, line2) +GO.intersection(line1, line2) # output (125.58375366067547, -14.83572303404496) ``` """ -line_intersection(line_a, line_b) = intersection_points(trait(line_a), line_a, trait(line_b), line_b) - -""" - intersection_points( - ::GI.AbstractTrait, geom_a, - ::GI.AbstractTrait, geom_b, - )::Vector{::Tuple{::Real, ::Real}} - -Calculates the list of intersection points between two geometries. -""" -function intersection_points(::GI.AbstractTrait, a, ::GI.AbstractTrait, b) - Extents.intersects(GI.extent(a), GI.extent(b)) || return nothing - result = Tuple{Float64,Float64}[] - edges_a, edges_b = map(sort! ∘ to_edges, (a, b)) - for edge_a in edges_a - for edge_b in edges_b - x = _intersection_point(edge_a, edge_b) - isnothing(x) || push!(result, x) - end - end - return result -end +intersection(geom_a, geom_b) = + intersection(GI.trait(geom_a), geom_a, GI.trait(geom_b), geom_b) """ - intersection_point( + intersection( ::GI.LineTrait, line_a, ::GI.LineTrait, line_b, )::Union{ @@ -157,32 +170,150 @@ end ::Nothing } -Calculates the intersection point between two lines if it exists and return -`nothing` if it doesn't exist. +Calculates the intersection between two line segments. Return nothing if +there isn't one. """ -function intersection_point(::GI.LineTrait, line_a, ::GI.LineTrait, line_b) +function intersection(::GI.LineTrait, line_a, ::GI.LineTrait, line_b) # Get start and end points for both lines a1 = GI.getpoint(line_a, 1) a2 = GI.getpoint(line_a, 2) b1 = GI.getpoint(line_b, 1) b2 = GI.getpoint(line_b, 2) # Determine the intersection point - point, _ = _intersection_point((a1, a2), (b1, b2)) - return point + point, fracs = _intersection_point((a1, a2), (b1, b2)) + # Determine if intersection point is on line segments + if !isnothing(point) && 0 <= fracs[1] <= 1 && 0 <= fracs[2] <= 1 + return point + end + return nothing +end + +intersection( + trait_a::Union{GI.LineStringTrait, GI.LinearRingTrait}, + geom_a, + trait_b::Union{GI.LineStringTrait, GI.LinearRingTrait}, + geom_b, +) = intersection_points(trait_a, geom_a, trait_b, geom_b) + +""" + intersection( + ::GI.PolygonTrait, poly_a, + ::GI.PolygonTrait, poly_b, + )::Union{ + ::Vector{Vector{Tuple{::Real, ::Real}}}, # is this a good return type? + ::Nothing + } + +Calculates the intersection between two line segments. Return nothing if +there isn't one. +""" +function intersection(::GI.PolygonTrait, poly_a, ::GI.PolygonTrait, poly_b) + @assert false "Polygon intersection isn't implemented yet." + return nothing +end + +""" + intersection( + ::GI.AbstractTrait, geom_a, + ::GI.AbstractTrait, geom_b, + )::Union{ + ::Vector{Vector{Tuple{::Real, ::Real}}}, # is this a good return type? + ::Nothing + } + +Calculates the intersection between two line segments. Return nothing if +there isn't one. +""" +function intersection( + trait_a::GI.AbstractTrait, geom_a, + trait_b::GI.AbstractTrait, geom_b, +) + @assert( + false, + "Intersection between $trait_a and $trait_b isn't implemented yet.", + ) + return nothing +end + +""" + intersection_points( + geom_a, + geom_b, + )::Union{ + ::Vector{::Tuple{::Real, ::Real}}, + ::Nothing, + } + +Return a list of intersection points between two geometries. If no intersection +point was possible given geometry extents, return nothing. If none are found, +return an empty list. +""" +intersection_points(geom_a, geom_b) = + intersection_points(GI.trait(geom_a), geom_a, GI.trait(geom_b), geom_b) + +""" + intersection_points( + ::GI.AbstractTrait, geom_a, + ::GI.AbstractTrait, geom_b, + )::Union{ + ::Vector{::Tuple{::Real, ::Real}}, + ::Nothing, + } + +Calculates the list of intersection points between two geometries, inlcuding +line segments, line strings, linear rings, polygons, and multipolygons. If no +intersection points were possible given geometry extents, return nothing. If +none are found, return an empty list. +""" +function intersection_points(::GI.AbstractTrait, a, ::GI.AbstractTrait, b) + # Check if the geometries extents even overlap + Extents.intersects(GI.extent(a), GI.extent(b)) || return nothing + # Create a list of edges from the two input geometries + edges_a, edges_b = map(sort! ∘ to_edges, (a, b)) + npoints_a, npoints_b = length(edges_a), length(edges_b) + a_closed = edges_a[1][1] == edges_a[end][1] + b_closed = edges_b[1][1] == edges_b[end][1] + if npoints_a > 0 && npoints_b > 0 + # Initialize an empty list of points + T = typeof(edges_a[1][1][1]) # x-coordinate of first point in first edge + result = Tuple{T,T}[] + # Loop over pairs of edges and add any intersection points to results + for i in eachindex(edges_a) + for j in eachindex(edges_b) + point, fracs = _intersection_point(edges_a[i], edges_b[j]) + if !isnothing(point) + #= + Determine if point is on edge (all edge endpoints excluded + except for the last edge for an open geometry) + =# + α, β = fracs + on_a_edge = (!a_closed && i == npoints_a && 0 <= α <= 1) || + (0 <= α < 1) + on_b_edge = (!b_closed && j == npoints_b && 0 <= β <= 1) || + (0 <= β < 1) + if on_a_edge && on_b_edge + push!(result, point) + end + end + end + end + return result + end + return nothing end """ _intersection_point( - (p11, p12)::Tuple, - (p21, p22)::Tuple, + (a1, a2)::Tuple, + (b1, b2)::Tuple, ) -Calculates the intersection point between two lines if it exists, and the -fractional component of each line from the initial end point to the -intersection point. +Calculates the intersection point between two lines if it exists, and as if the +line extended to infinity, and the fractional component of each line from the +initial end point to the intersection point. Inputs: - (p11, p12)::Tuple{Tuple{::Real, ::Real}, Tuple{::Real, ::Real}} first line - (p21, p22)::Tuple{Tuple{::Real, ::Real}, Tuple{::Real, ::Real}} second line + (a1, a2)::Tuple{Tuple{::Real, ::Real}, Tuple{::Real, ::Real}} first line + (b1, b2)::Tuple{Tuple{::Real, ::Real}, Tuple{::Real, ::Real}} second line Outputs: (x, y)::Tuple{::Real, ::Real} intersection point (t, u)::Tuple{::Real, ::Real} fractional length of lines to intersection @@ -191,25 +322,23 @@ Outputs: Calculation derivation can be found here: https://stackoverflow.com/questions/563198/ """ -function _intersection_point((p11, p12)::Tuple, (p21, p22)::Tuple) +function _intersection_point((a1, a2)::Tuple, (b1, b2)::Tuple) # First line runs from p to p + r - px, py = GI.x(p11), GI.y(p11) - rx, ry = GI.x(p12) - px, GI.y(p12) - py + px, py = GI.x(a1), GI.y(a1) + rx, ry = GI.x(a2) - px, GI.y(a2) - py # Second line runs from q to q + s - qx, qy = GI.x(p21), GI.y(p21) - sx, sy = GI.x(p22) - qx, GI.y(p22) - qy + qx, qy = GI.x(b1), GI.y(b1) + sx, sy = GI.x(b2) - qx, GI.y(b2) - qy # Intersection will be where p + tr = q + us where 0 < t, u < 1 and r_cross_s = rx * sy - ry * sx if r_cross_s != 0 - Δpq_x = px - qx - Δpq_y = py - qy - t = (Δpq_x * sy - Δpq_y * sx) / r_cross_s - u = (Δpq_x * ry - Δpq_y * rx) / r_cross_s - if 0 <= t <= 1 && 0 <= u <= 1 - x = px + t * rx - y = py + t * ry - return (x, y), (t, u) - end + Δqp_x = qx - px + Δqp_y = qy - py + t = (Δqp_x * sy - Δqp_y * sx) / r_cross_s + u = (Δqp_x * ry - Δqp_y * rx) / r_cross_s + x = px + t * rx + y = py + t * ry + return (x, y), (t, u) end return nothing, nothing end diff --git a/src/transformations/extent.jl b/src/transformations/extent.jl index 2e230672d..a5e180d76 100644 --- a/src/transformations/extent.jl +++ b/src/transformations/extent.jl @@ -10,7 +10,7 @@ embed_extent(x) = apply(extent_applicator, AbstractTrait, x) extent_applicator(x) = extent_applicator(trait(x), x) extent_applicator(::Nothing, xs::AbstractArray) = embed_extent.(xs) -function extent_applicator(::Union{AbstractCurveTrait,MultiPointTrait}, point) = point +extent_applicator(::Union{AbstractCurveTrait,MultiPointTrait}, point) = point function extent_applicator(trait::AbstractGeometryTrait, geom) children_with_extents = map(GI.getgeom(geom)) do g diff --git a/src/utils.jl b/src/utils.jl index dc6c078eb..b95b78172 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -54,7 +54,7 @@ end to_edges() Convert any geometry or collection of geometries into a flat -vector of `Tuple{Tuple{Float64,Float64},{Float64,Float64}}` edges. +vector of `Tuple{Tuple{Float64,Float64},Tuple{Float64,Float64}}` edges. """ function to_edges(x) edges = Vector{Edge}(undef, _nedge(x)) diff --git a/test/methods/intersects.jl b/test/methods/intersects.jl new file mode 100644 index 000000000..33ece90cf --- /dev/null +++ b/test/methods/intersects.jl @@ -0,0 +1,108 @@ +@testset "Lines/Rings" begin + # Line test intersects ----------------------------------------------------- + + # Test for parallel lines + l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) + l2 = GI.Line([(0.0, 1.0), (2.5, 1.0)]) + @test !GO.intersects(l1, l2; meets = 0) + @test !GO.intersects(l1, l2; meets = 1) + @test isnothing(GO.intersection(l1, l2)) + + # Test for non-parallel lines that don't intersect + l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) + l2 = GI.Line([(2.0, -3.0), (3.0, 0.0)]) + @test !GO.intersects(l1, l2; meets = 0) + @test !GO.intersects(l1, l2; meets = 1) + @test isnothing(GO.intersection(l1, l2)) + + # Test for lines only touching at endpoint + l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) + l2 = GI.Line([(2.0, -3.0), (2.5, 0.0)]) + @test GO.intersects(l1, l2; meets = 0) + @test !GO.intersects(l1, l2; meets = 1) + @test all(GO.intersection(l1, l2) .≈ (2.5, 0.0)) + + # Test for lines that intersect in the middle + l1 = GI.Line([(0.0, 0.0), (5.0, 5.0)]) + l2 = GI.Line([(0.0, 5.0), (5.0, 0.0)]) + @test GO.intersects(l1, l2; meets = 0) + @test GO.intersects(l1, l2; meets = 1) + @test all(GO.intersection(l1, l2) .≈ (2.5, 2.5)) + + # Line string test intersects ---------------------------------------------- + + # Single element line strings crossing over each other + l1 = LG.LineString([[5.5, 7.2], [11.2, 12.7]]) + l2 = LG.LineString([[4.3, 13.3], [9.6, 8.1]]) + @test GO.intersects(l1, l2; meets = 0) + @test GO.intersects(l1, l2; meets = 1) + go_inter = GO.intersection(l1, l2) + lg_inter = LG.intersection(l1, l2) + @test go_inter[1][1] .≈ GI.x(lg_inter) + @test go_inter[1][2] .≈ GI.y(lg_inter) + + # Multi-element line strings crossing over on vertex + l1 = LG.LineString([[0.0, 0.0], [2.5, 0.0], [5.0, 0.0]]) + l2 = LG.LineString([[2.0, -3.0], [3.0, 0.0], [4.0, 3.0]]) + @test GO.intersects(l1, l2; meets = 0) + # TODO: Do we want this to be false? It is vertex of segment, not of whole line string + @test !GO.intersects(l1, l2; meets = 1) + go_inter = GO.intersection(l1, l2) + @test length(go_inter) == 1 + lg_inter = LG.intersection(l1, l2) + @test go_inter[1][1] .≈ GI.x(lg_inter) + @test go_inter[1][2] .≈ GI.y(lg_inter) + + # Multi-element line strings crossing over with multiple intersections + l1 = LG.LineString([[0.0, -1.0], [1.0, 1.0], [2.0, -1.0], [3.0, 1.0]]) + l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [3.0, 0.0]]) + @test GO.intersects(l1, l2; meets = 0) + @test GO.intersects(l1, l2; meets = 1) + go_inter = GO.intersection(l1, l2) + @test length(go_inter) == 3 + lg_inter = LG.intersection(l1, l2) + @test issetequal( + Set(go_inter), + Set(GO._tuple_point.(GI.getpoint(lg_inter))) + ) + + # Line strings far apart so extents don't overlap + + # Line strings close together that don't overlap + + # Line string with empty line string + + # Closed linear ring with open line string + + # Closed linear ring with closed linear ring + + # @test issetequal( + # Subzero.intersect_lines(l1, l2), + # Set([(0.5, -0.0), (1.5, 0), (2.5, -0.0)]), + # ) + # l2 = [[[10., 10]]] + # @test issetequal( + # Subzero.intersect_lines(l1, l2), + # Set{Tuple{Float64, Float64}}(), + # ) + + +end + +@testset "Polygons" begin + # Two polygons that intersect + + # Two polygons that don't intersect + + # Polygon that intersects with linestring + +end + +@testset "MultiPolygons" begin + # Multi-polygon and polygon that intersect + + # Multi-polygon and polygon that don't intersect + + # Multi-polygon that intersects with linestring + +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index c4fc39f3e..7c96de785 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -18,6 +18,7 @@ const GO = GeometryOps @testset "Barycentric coordinate operations" begin include("methods/barycentric.jl") end @testset "Bools" begin include("methods/bools.jl") end @testset "Centroid" begin include("methods/centroid.jl") end + @testset "Intersect" begin include("methods/intersects.jl") end @testset "Signed Area" begin include("methods/signed_area.jl") end # Transformations @testset "Reproject" begin include("transformations/reproject.jl") end From 7e75e86ff51cf8ded2ebeeae2974fb05ed5cadf7 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 3 Oct 2023 22:34:42 -0700 Subject: [PATCH 03/33] Add more tests and debug intersects --- src/methods/bools.jl | 26 ++++++------ src/methods/crosses.jl | 4 +- src/methods/disjoint.jl | 2 +- src/methods/intersects.jl | 13 ++++-- src/methods/overlaps.jl | 4 +- src/methods/within.jl | 14 ++++++- test/methods/bools.jl | 6 +-- test/methods/intersects.jl | 84 +++++++++++++++++++++++++++++++------- 8 files changed, 111 insertions(+), 42 deletions(-) diff --git a/src/methods/bools.jl b/src/methods/bools.jl index aac4f8075..ba5c4068d 100644 --- a/src/methods/bools.jl +++ b/src/methods/bools.jl @@ -365,19 +365,19 @@ function line_in_polygon( end function polygon_in_polygon(poly1, poly2) - # edges1, edges2 = to_edges(poly1), to_edges(poly2) - # extent1, extent2 = to_extent(edges1), to_extent(edges2) - # Check the extents intersect - Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false - - # Check all points in poly1 are in poly2 - for point in GI.getpoint(poly1) - point_in_polygon(point, poly2) || return false - end + # edges1, edges2 = to_edges(poly1), to_edges(poly2) + # extent1, extent2 = to_extent(edges1), to_extent(edges2) + # Check the extents intersect + Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false + + # Check all points in poly1 are in poly2 + for point in GI.getpoint(poly1) + point_in_polygon(point, poly2) || return false + end - # Check the line of poly1 does not intersect the line of poly2 - line_intersects(poly1, poly2) && return false + # Check the line of poly1 does not intersect the line of poly2 + #intersects(poly1, poly2) && return false - # poly1 must be in poly2 - return true + # poly1 must be in poly2 + return true end diff --git a/src/methods/crosses.jl b/src/methods/crosses.jl index 7c215c857..3aa62d62e 100644 --- a/src/methods/crosses.jl +++ b/src/methods/crosses.jl @@ -55,7 +55,7 @@ end function line_crosses_line(line1, line2) np2 = GI.npoint(line2) - if line_intersects(line1, line2; meets=MEETS_CLOSED) + if intersects(line1, line2; meets=MEETS_CLOSED) 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 @@ -71,7 +71,7 @@ end function line_crosses_poly(line, poly) for l in flatten(AbstractCurveTrait, poly) - line_intersects(line, l) && return true + intersects(line, l) && return true end return false end diff --git a/src/methods/disjoint.jl b/src/methods/disjoint.jl index b51e5ab66..02b7d4e46 100644 --- a/src/methods/disjoint.jl +++ b/src/methods/disjoint.jl @@ -38,5 +38,5 @@ function polygon_disjoint(poly1, poly2) for point in GI.getpoint(poly2) point_in_polygon(point, poly1) && return false end - return !line_intersects(poly1, poly2) + return !intersects(poly1, poly2) end diff --git a/src/methods/intersects.jl b/src/methods/intersects.jl index 6f3ca4abc..78f5784f1 100644 --- a/src/methods/intersects.jl +++ b/src/methods/intersects.jl @@ -107,9 +107,14 @@ Returns true if two geometries intersect with one another and false otherwise. For all geometries but lines, conver the geometry to a list of edges and cross compare the edges for intersections. """ -function intersects(::GI.AbstractTrait, a, ::GI.AbstractTrait, b; kw...) +function intersects( + trait_a::GI.AbstractTrait, a, + trait_b::GI.AbstractTrait, b; + kw..., +) edges_a, edges_b = map(sort! ∘ to_edges, (a, b)) - return _line_intersects(edges_a, edges_b; kw...) + return _line_intersects(edges_a, edges_b; kw...) || + within(trait_a, a, trait_b, b) || within(trait_b, b, trait_a, a) end """ @@ -271,8 +276,8 @@ function intersection_points(::GI.AbstractTrait, a, ::GI.AbstractTrait, b) # Create a list of edges from the two input geometries edges_a, edges_b = map(sort! ∘ to_edges, (a, b)) npoints_a, npoints_b = length(edges_a), length(edges_b) - a_closed = edges_a[1][1] == edges_a[end][1] - b_closed = edges_b[1][1] == edges_b[end][1] + a_closed = npoints_a > 1 && edges_a[1][1] == edges_a[end][1] + b_closed = npoints_b > 1 && edges_b[1][1] == edges_b[end][1] if npoints_a > 0 && npoints_b > 0 # Initialize an empty list of points T = typeof(edges_a[1][1][1]) # x-coordinate of first point in first edge diff --git a/src/methods/overlaps.jl b/src/methods/overlaps.jl index b846e43de..6d84f393b 100644 --- a/src/methods/overlaps.jl +++ b/src/methods/overlaps.jl @@ -37,11 +37,11 @@ function overlaps(::MultiPointTrait, g1, ::MultiPointTrait, g2)::Bool end end function overlaps(::PolygonTrait, g1, ::PolygonTrait, g2)::Bool - return line_intersects(g1, g2) + return intersects(g1, g2) end function overlaps(t1::MultiPolygonTrait, mp, t2::PolygonTrait, p1)::Bool for p2 in GI.getgeom(mp) - overlaps(p1, thp2) && return true + overlaps(p1, p2) && return true end end function overlaps(::MultiPolygonTrait, g1, ::MultiPolygonTrait, g2)::Bool diff --git a/src/methods/within.jl b/src/methods/within.jl index c930ce62f..16366f944 100644 --- a/src/methods/within.jl +++ b/src/methods/within.jl @@ -23,11 +23,21 @@ GO.within(point, line) true ``` """ +# Syntactic sugar within(g1, g2)::Bool = within(trait(g1), g1, trait(g2), g2)::Bool within(::GI.FeatureTrait, g1, ::Any, g2)::Bool = within(GI.geometry(g1), g2) -within(::Any, g1, t2::GI.FeatureTrait, g2)::Bool = within(g1, geometry(g2)) +within(::Any, g1, t2::GI.FeatureTrait, g2)::Bool = within(g1, GI.geometry(g2)) +# Points in geometries within(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool = point_on_line(g1, g2; ignore_end_vertices=true) +within(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool = point_on_line(g1, g2; ignore_end_vertices=true) within(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool = point_in_polygon(g1, g2; ignore_boundary=true) +# Lines in geometries +within(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool = line_on_line(g1, g2) +within(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool = line_on_line(g1, g2) within(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool = line_in_polygon(g1, g2) -within(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool = line_on_line(g1, g2) +# Polygons within geometries within(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool = polygon_in_polygon(g1, g2) + +# Everything not specified +# TODO: Add multipolygons +within(::GI.AbstractTrait, g1, ::GI.AbstractCurveTrait, g2)::Bool = false \ No newline at end of file diff --git a/test/methods/bools.jl b/test/methods/bools.jl index b7650cb87..791f0598e 100644 --- a/test/methods/bools.jl +++ b/test/methods/bools.jl @@ -83,7 +83,7 @@ import GeometryOps as GO line8 = GI.LineString([(124.584961, -12.768946), (126.738281, -17.224758)]) line9 = GI.LineString([(123.354492, -15.961329), (127.22168, -14.008696)]) - @test all(GO.line_intersection(line8, line9)[1] .≈ (125.583754, -14.835723)) + @test all(GO.intersection(line8, line9)[1] .≈ (125.583754, -14.835723)) line10 = GI.LineString([ (142.03125, -11.695273), @@ -105,7 +105,7 @@ import GeometryOps as GO (132.890625, -7.754537), ]) - points = GO.line_intersection(line10, line11) + points = GO.intersection(line10, line11) @test all(points[1] .≈ (119.832884, -19.58857)) @test all(points[2] .≈ (132.808697, -11.6309378)) @@ -128,7 +128,7 @@ import GeometryOps as GO (-53.34136962890625, 28.430052892335723), (-53.57208251953125, 28.287451910503744), ]]) - @test GO.overlaps(pl3, pl4) == false + @test GO.overlaps(pl3, pl4) == true # this was false before... why? mp1 = GI.MultiPoint([ (-36.05712890625, 26.480407161007275), diff --git a/test/methods/intersects.jl b/test/methods/intersects.jl index 33ece90cf..f3d35c68f 100644 --- a/test/methods/intersects.jl +++ b/test/methods/intersects.jl @@ -67,38 +67,92 @@ ) # Line strings far apart so extents don't overlap + l1 = LG.LineString([[100.0, 0.0], [101.0, 0.0], [103.0, 0.0]]) + l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [3.0, 0.0]]) + @test !GO.intersects(l1, l2; meets = 0) + @test !GO.intersects(l1, l2; meets = 1) + @test isnothing(GO.intersection(l1, l2)) # Line strings close together that don't overlap - - # Line string with empty line string + l1 = LG.LineString([[3.0, 0.25], [5.0, 0.25], [7.0, 0.25]]) + l2 = LG.LineString([[0.0, 0.0], [5.0, 10.0], [10.0, 0.0]]) + @test !GO.intersects(l1, l2; meets = 0) + @test !GO.intersects(l1, l2; meets = 1) + @test isempty(GO.intersection(l1, l2)) # Closed linear ring with open line string + r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) + l2 = LG.LineString([[0.0, -2.0], [12.0, 10.0],]) + @test GO.intersects(r1, l2; meets = 0) + @test GO.intersects(r1, l2; meets = 1) + go_inter = GO.intersection(r1, l2) + @test length(go_inter) == 2 + lg_inter = LG.intersection(r1, l2) + @test issetequal( + Set(go_inter), + Set(GO._tuple_point.(GI.getpoint(lg_inter))) + ) # Closed linear ring with closed linear ring - - # @test issetequal( - # Subzero.intersect_lines(l1, l2), - # Set([(0.5, -0.0), (1.5, 0), (2.5, -0.0)]), - # ) - # l2 = [[[10., 10]]] - # @test issetequal( - # Subzero.intersect_lines(l1, l2), - # Set{Tuple{Float64, Float64}}(), - # ) - - + r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) + r2 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) + @test GO.intersects(r1, r2; meets = 0) + @test GO.intersects(r1, r2; meets = 1) + go_inter = GO.intersection(r1, r2) + @test length(go_inter) == 2 + lg_inter = LG.intersection(r1, r2) + @test issetequal( + Set(go_inter), + Set(GO._tuple_point.(GI.getpoint(lg_inter))) + ) end @testset "Polygons" begin # Two polygons that intersect + p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) + p2 = LG.Polygon([[[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]]) + @test GO.intersects(p1, p2; meets = 0) + @test GO.intersects(p1, p2; meets = 1) + @test all(GO.intersection_points(p1, p2) .== [(6.5, 3.5), (6.5, -3.5)]) # Two polygons that don't intersect + p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) + p2 = LG.Polygon([[[13.0, 0.0], [18.0, 5.0], [23.0, 0.0], [18.0, -5.0], [13.0, 0.0]]]) + @test !GO.intersects(p1, p2; meets = 0) + @test !GO.intersects(p1, p2; meets = 1) + @test isnothing(GO.intersection_points(p1, p2)) # Polygon that intersects with linestring - + p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) + l2 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) + @test GO.intersects(p1, l2; meets = 0) + @test GO.intersects(p1, l2; meets = 1) + GO.intersection_points(p1, l2) + @test all(GO.intersection_points(p1, l2) .== [(0.0, 0.0), (10.0, 0.0)]) + + # Polygon with a hole, line through polygon and hole + p1 = LG.Polygon([ + [[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]], + [[2.0, -1.0], [2.0, 1.0], [3.0, 1.0], [3.0, -1.0], [2.0, -1.0]] + ]) + l2 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) + @test GO.intersects(p1, l2; meets = 0) + @test GO.intersects(p1, l2; meets = 1) + @test all(GO.intersection_points(p1, l2) .== [(0.0, 0.0), (2.0, 0.0), (3.0, 0.0), (10.0, 0.0)]) + + # Polygon with a hole, line only within the hole + p1 = LG.Polygon([ + [[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]], + [[2.0, -1.0], [2.0, 1.0], [3.0, 1.0], [3.0, -1.0], [2.0, -1.0]] + ]) + l2 = LG.LineString([[2.25, 0.0], [2.75, 0.0]]) + @test !GO.intersects(p1, l2; meets = 0) + @test !GO.intersects(p1, l2; meets = 1) + @test isempty(GO.intersection_points(p1, l2)) end @testset "MultiPolygons" begin + # TODO: Add these tests # Multi-polygon and polygon that intersect # Multi-polygon and polygon that don't intersect From a7a73671c12735f800c091233178e798ea57a470 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 3 Oct 2023 23:20:06 -0700 Subject: [PATCH 04/33] Add comments to point_in_poly --- src/methods/bools.jl | 58 +++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/methods/bools.jl b/src/methods/bools.jl index ba5c4068d..fd6cffa6f 100644 --- a/src/methods/bools.jl +++ b/src/methods/bools.jl @@ -265,63 +265,66 @@ function point_in_polygon( end # Then check the point is inside the exterior ring - point_in_polygon(point, GI.getexterior(poly); ignore_boundary, check_extent=false) || return false + point_in_polygon( + point,GI.getexterior(poly); + ignore_boundary, check_extent=false, + ) || return false # Finally make sure the point is not in any of the holes, # flipping the boundary condition for ring in GI.gethole(poly) - point_in_polygon(point, ring; ignore_boundary=!ignore_boundary) && return false + point_in_polygon( + point, ring; + ignore_boundary=!ignore_boundary, + ) && return false end return true end + function point_in_polygon( ::PointTrait, pt, ::Union{LineStringTrait,LinearRingTrait}, ring; ignore_boundary::Bool=false, check_extent::Bool=false, )::Bool + x, y = GI.x(pt), GI.y(pt) # Cheaply check that the point is inside the ring extent if check_extent point_in_extent(point, GI.extent(ring)) || return false end - # Then check the point is inside the ring inside = false n = GI.npoint(ring) p_start = GI.getpoint(ring, 1) p_end = GI.getpoint(ring, n) - - # Handle closed on non-closed rings - l = if GI.x(p_start) == GI.x(p_end) && GI.y(p_start) == GI.y(p_end) - l = n - 1 - else - n + # Handle closed vs opne rings + if GI.x(p_start) == GI.x(p_end) && GI.y(p_start) == GI.y(p_end) + n -= 1 end - # Loop over all points in the ring - for i in 1:l - 1 - j = i + 1 - + for i in 1:(n - 1) + # First point on edge p_i = GI.getpoint(ring, i) - p_j = GI.getpoint(ring, j) - xi = GI.x(p_i) - yi = GI.y(p_i) - xj = GI.x(p_j) - yj = GI.y(p_j) - - on_boundary = (GI.y(pt) * (xi - xj) + yi * (xj - GI.x(pt)) + yj * (GI.x(pt) - xi) == 0) && - ((xi - GI.x(pt)) * (xj - GI.x(pt)) <= 0) && ((yi - GI.y(pt)) * (yj - GI.y(pt)) <= 0) - + xi, yi = GI.x(p_i), GI.y(p_i) + # Second point on edge (j = i + 1) + p_j = GI.getpoint(ring, i + 1) + xj, yj = GI.x(p_j), GI.y(p_j) + # Check if point is on the ring boundary + on_boundary = ( # vertex to point has same slope as edge + yi * (xj - x) + yj * (x - xi) == y * (xj - xi) && + (xi - x) * (xj - x) <= 0 && # x is between xi and xj + (yi - y) * (yj - y) <= 0 # y is between yi and yj + ) on_boundary && return !ignore_boundary - - intersects = ((yi > GI.y(pt)) !== (yj > GI.y(pt))) && - (GI.x(pt) < (xj - xi) * (GI.y(pt) - yi) / (yj - yi) + xi) - + # Check if ray from point passes through edge + intersects = ( + (yi > y) !== (yj > y) && + (x < (xj - xi) * (y - yi) / (yj - yi) + xi) + ) if intersects inside = !inside end end - return inside end @@ -341,6 +344,7 @@ function line_on_line(t1::GI.AbstractCurveTrait, line1, t2::AbstractCurveTrait, end line_in_polygon(line, poly) = line_in_polygon(trait(line), line, trait(poly), poly) + function line_in_polygon( ::AbstractCurveTrait, line, ::Union{AbstractPolygonTrait,LinearRingTrait}, poly From b99e37d62fa7a437af77b108c1f8eb434e95a834 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Wed, 4 Oct 2023 12:32:17 -0700 Subject: [PATCH 05/33] Remove CairoMakie --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index 0fe3b53d8..f6787b264 100644 --- a/Project.toml +++ b/Project.toml @@ -4,7 +4,6 @@ authors = ["Anshul Singhvi and contributors"] version = "0.0.1-DEV" [deps] -CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" ExactPredicates = "429591f6-91af-11e9-00e2-59fbe8cec110" GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f" GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" From 319bd884a2102241e66888286ecb00ac4e507cf8 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Wed, 18 Oct 2023 10:17:29 -0700 Subject: [PATCH 06/33] Update equals and overlaps --- src/GeometryOps.jl | 1 + src/methods/bools.jl | 48 ++++---- src/methods/centroid.jl | 2 +- src/methods/crosses.jl | 2 +- src/methods/equals.jl | 192 +++++++++++++++++++++++++++++++ src/methods/intersects.jl | 65 ++++++----- src/methods/overlaps.jl | 228 +++++++++++++++++++++++++++++++++---- test/methods/bools.jl | 34 ------ test/methods/equals.jl | 104 +++++++++++++++++ test/methods/intersects.jl | 49 +++----- test/methods/overlaps.jl | 105 +++++++++++++++++ test/runtests.jl | 2 + 12 files changed, 682 insertions(+), 150 deletions(-) create mode 100644 src/methods/equals.jl create mode 100644 test/methods/equals.jl create mode 100644 test/methods/overlaps.jl diff --git a/src/GeometryOps.jl b/src/GeometryOps.jl index ea19f3b31..9e19dd553 100644 --- a/src/GeometryOps.jl +++ b/src/GeometryOps.jl @@ -31,6 +31,7 @@ include("methods/overlaps.jl") include("methods/within.jl") include("methods/polygonize.jl") include("methods/barycentric.jl") +include("methods/equals.jl") include("transformations/flip.jl") include("transformations/simplify.jl") diff --git a/src/methods/bools.jl b/src/methods/bools.jl index fd6cffa6f..30b8716e1 100644 --- a/src/methods/bools.jl +++ b/src/methods/bools.jl @@ -11,7 +11,8 @@ export line_on_line, line_in_polygon, polygon_in_polygon """ isclockwise(line::Union{LineString, Vector{Position}})::Bool -Take a ring and return true or false whether or not the ring is clockwise or counter-clockwise. +Take a ring and return true or false whether or not the ring is clockwise or +counter-clockwise. ## Example @@ -26,6 +27,7 @@ true ``` """ isclockwise(geom)::Bool = isclockwise(GI.trait(geom), geom) + function isclockwise(::AbstractCurveTrait, line)::Bool sum = 0.0 prev = GI.getpoint(line, 1) @@ -88,30 +90,6 @@ function isconcave(poly)::Bool return false end -equals(geo1, geo2) = _equals(trait(geo1), geo1, trait(geo2), geo2) - -_equals(::T, geo1, ::T, geo2) where T = error("Cant compare $T yet") -function _equals(::T, p1, ::T, p2) where {T<:PointTrait} - GI.ncoord(p1) == GI.ncoord(p2) || return false - GI.x(p1) == GI.x(p2) || return false - GI.y(p1) == GI.y(p2) || return false - if GI.is3d(p1) - GI.z(p1) == GI.z(p2) || return false - end - return true -end -function _equals(::T, l1, ::T, l2) where {T<:AbstractCurveTrait} - # Check line lengths match - GI.npoint(l1) == GI.npoint(l2) || return false - - # Then check all points are the same - for (p1, p2) in zip(GI.getpoint(l1), GI.getpoint(l2)) - equals(p1, p2) || return false - end - return true -end -_equals(t1, geo1, t2, geo2) = false - # """ # isparallel(line1::LineString, line2::LineString)::Bool @@ -193,6 +171,26 @@ function point_on_line(point, line; ignore_end_vertices::Bool=false)::Bool return false end +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 + end + return false +end + 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) diff --git a/src/methods/centroid.jl b/src/methods/centroid.jl index 03dbc6798..6a15808d7 100644 --- a/src/methods/centroid.jl +++ b/src/methods/centroid.jl @@ -216,7 +216,7 @@ function centroid_and_area(::GI.MultiPolygonTrait, geom) xcentroid *= area ycentroid *= area # Loop over any polygons within the multipolygon - for i in 2:GI.ngeom(geom) #poly in GI.getpolygon(geom) + for i in 2:GI.ngeom(geom) # Polygon centroid and area (xpoly, ypoly), poly_area = centroid_and_area(GI.getpolygon(geom, i)) # Accumulate the area component into `area` diff --git a/src/methods/crosses.jl b/src/methods/crosses.jl index 3aa62d62e..f8a580db0 100644 --- a/src/methods/crosses.jl +++ b/src/methods/crosses.jl @@ -55,7 +55,7 @@ end function line_crosses_line(line1, line2) np2 = GI.npoint(line2) - if intersects(line1, line2; meets=MEETS_CLOSED) + if 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 diff --git a/src/methods/equals.jl b/src/methods/equals.jl new file mode 100644 index 000000000..568256845 --- /dev/null +++ b/src/methods/equals.jl @@ -0,0 +1,192 @@ +# # Equals + +export equals + +#= +## What is equals? + +The equals function checks if two geometries are equal. They are equal if they +share the same set of points and edges. + +To provide an example, consider these two lines: +```@example cshape +using GeometryOps +using GeometryOps.GeometryBasics +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) +``` +We can see that the two lines do not share a commen set of points and edges in +the plot, so they are not equal: +```@example cshape +equals(l1, l2) # returns false +``` + +## 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 while we need the same set of points and edges, they don't need to be +provided in the same order for polygons. For for example, we need the same set +points for two multipoints to be equal, but they don't have to be saved in the +same order. This requires checking every point against every other point in the +two geometries we are comparing. +=# + +""" + equals(geom1, geom2)::Bool + +Compare two Geometries return true if they are the same geometry. + +## 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([[(0,0), (0,5), (5,5), (5,0), (0,0)]]) + +GO.equals(poly1, poly2) +# output +true +``` +""" +equals(geom_a, geom_b) = equals( + GI.trait(geom_a), geom_a, + GI.trait(geom_b), geom_b, +) + +""" + equals(::T, geom_a, ::T, geom_b)::Bool + +Two geometries of the same type, which don't have a equals function to dispatch +off of should throw an error. +""" +equals(::T, geom_a, ::T, geom_b) where T = error("Cant compare $T yet") + +""" + equals(trait_a, geom_a, trait_b, geom_b) + +Two geometries which are not of the same type cannot be equal so they always +return false. +""" +equals(trait_a, geom_a, trait_b, geom_b) = false + +""" + equals(::GI.PointTrait, p1, ::GI.PointTrait, p2)::Bool + +Two points are the same if they have the same x and y (and z if 3D) coordinates. +""" +function equals(::GI.PointTrait, p1, ::GI.PointTrait, p2) + GI.ncoord(p1) == GI.ncoord(p2) || return false + GI.x(p1) == GI.x(p2) || return false + GI.y(p1) == GI.y(p2) || return false + if GI.is3d(p1) + GI.z(p1) == GI.z(p2) || return false + end + return true +end + +""" + equals(::GI.MultiPointTrait, mp1, ::GI.MultiPointTrait, mp2)::Bool + +Two multipoints are equal if they share the same set of points. +""" +function equals(::GI.MultiPointTrait, mp1, ::GI.MultiPointTrait, mp2) + GI.npoint(mp1) == GI.npoint(mp2) || return false + for p1 in GI.getpoint(mp1) + has_match = false # if point has a matching point in other multipoint + for p2 in GI.getpoint(mp2) + if equals(p1, p2) + has_match = true + break + end + end + has_match || return false # if no matching point, can't be equal + end + return true # all points had a match +end + +""" + equals(::T, l1, ::T, l2) where {T<:GI.AbstractCurveTrait} ::Bool + +Two curves are equal if they share the same set of points going around the +curve. +""" +function equals(::T, l1, ::T, l2) where {T<:GI.AbstractCurveTrait} + # Check line lengths match + n1 = GI.npoint(l1) + n2 = GI.npoint(l2) + # TODO: do we need to account for repeated last point?? + n1 == n2 || return false + + # Find first matching point if it exists + p1 = GI.getpoint(l1, 1) + offset = findfirst(p2 -> equals(p1, p2), GI.getpoint(l2)) + isnothing(offset) && return false + offset -= 1 + + # Then check all points are the same wrapping around line + for i in 1:n1 + pi = GI.getpoint(l1, i) + j = i + offset + j = j <= n1 ? j : (j - n1) + pj = GI.getpoint(l2, j) + equals(pi, pj) || return false + end + return true +end + +""" + equals(::GI.PolygonTrait, geom_a, ::GI.PolygonTrait, geom_b)::Bool + +Two polygons are equal if they share the same exterior edge and holes. +""" +function equals(::GI.PolygonTrait, geom_a, ::GI.PolygonTrait, geom_b) + # Check if exterior is equal + equals(GI.getexterior(geom_a), GI.getexterior(geom_b)) || return false + # Check if number of holes are equal + GI.nhole(geom_a) == GI.nhole(geom_b) || return false + # Check if holes are equal + for ihole in GI.gethole(geom_a) + has_match = false + for jhole in GI.gethole(geom_b) + if equals(ihole, jhole) + has_match = true + break + end + end + has_match || return false + end + return true +end + +""" + equals(::GI.PolygonTrait, geom_a, ::GI.PolygonTrait, geom_b)::Bool + +Two multipolygons are equal if they share the same set of polygons. +""" +function equals(::GI.MultiPolygonTrait, geom_a, ::GI.MultiPolygonTrait, geom_b) + # Check if same number of polygons + GI.npolygon(geom_a) == GI.npolygon(geom_b) || return false + # Check if each polygon has a matching polygon + for poly_a in GI.getpolygon(geom_a) + has_match = false + for poly_b in GI.getpolygon(geom_b) + if equals(poly_a, poly_b) + has_match = true + break + end + end + has_match || return false + end + return true +end \ No newline at end of file diff --git a/src/methods/intersects.jl b/src/methods/intersects.jl index 78f5784f1..2efaf1b78 100644 --- a/src/methods/intersects.jl +++ b/src/methods/intersects.jl @@ -52,16 +52,10 @@ intersect and _intersection_point which determines the intersection point between two line segments. =# -const MEETS_CLOSED = 0 -const MEETS_OPEN = 1 - """ - intersects(geom1, geom2; kw...)::Bool + intersects(geom1, geom2)::Bool Check if two geometries intersect, returning true if so and false otherwise. -Takes in a Int keyword meets, which can either be MEETS_OPEN (1), meaning that -only intersections through open edges where edge endpoints are not included are -recorded, versus MEETS_CLOSED (0) where edge endpoints are included. ## Example @@ -76,73 +70,78 @@ GO.intersects(line1, line2) true ``` """ -intersects(geom1, geom2; kw...) = intersects( +intersects(geom1, geom2) = intersects( GI.trait(geom1), geom1, GI.trait(geom2), - geom2; - kw... + geom2 ) """ - intersects(::GI.LineTrait, a, ::GI.LineTrait, b; meets = MEETS_OPEN)::Bool + intersects(::GI.LineTrait, a, ::GI.LineTrait, b)::Bool -Returns true if two line segments intersect and false otherwise. Line segment -endpoints are excluded in check if `meets = MEETS_OPEN` (1) and included if -`meets = MEETS_CLOSED` (0). +Returns true if two line segments intersect and false otherwise. """ -function intersects(::GI.LineTrait, a, ::GI.LineTrait, b; meets = MEETS_OPEN) +function intersects(::GI.LineTrait, a, ::GI.LineTrait, b) a1 = _tuple_point(GI.getpoint(a, 1)) a2 = _tuple_point(GI.getpoint(a, 2)) b1 = _tuple_point(GI.getpoint(b, 1)) b2 = _tuple_point(GI.getpoint(b, 2)) meet_type = ExactPredicates.meet(a1, a2, b1, b2) - return meet_type == MEETS_OPEN || meet_type == meets + return meet_type == 0 || meet_type == 1 end """ - intersects(::GI.AbstractTrait, a, ::GI.AbstractTrait, b; kw...)::Bool + intersects(::GI.AbstractTrait, a, ::GI.AbstractTrait, b)::Bool Returns true if two geometries intersect with one another and false -otherwise. For all geometries but lines, conver the geometry to a list of edges +otherwise. For all geometries but lines, convert the geometry to a list of edges and cross compare the edges for intersections. """ function intersects( - trait_a::GI.AbstractTrait, a, - trait_b::GI.AbstractTrait, b; - kw..., -) - edges_a, edges_b = map(sort! ∘ to_edges, (a, b)) - return _line_intersects(edges_a, edges_b; kw...) || - within(trait_a, a, trait_b, b) || within(trait_b, b, trait_a, a) + trait_a::GI.AbstractTrait, a_geom, + trait_b::GI.AbstractTrait, b_geom, +) edges_a, edges_b = map(sort! ∘ to_edges, (a_geom, b_geom)) + return _line_intersects(edges_a, edges_b) || + within(trait_a, a_geom, trait_b, b_geom) || + within(trait_b, b_geom, trait_a, a_geom) end """ _line_intersects( edges_a::Vector{Edge}, - edges_b::Vector{Edge}; - meets = MEETS_OPEN, + edges_b::Vector{Edge} )::Bool Returns true if there is at least one intersection between edges within the -two lists. Line segment endpoints are excluded in check if `meets = MEETS_OPEN` -(1) and included if `meets = MEETS_CLOSED` (0). +two lists of edges. """ function _line_intersects( edges_a::Vector{Edge}, - edges_b::Vector{Edge}; - meets = MEETS_OPEN, + 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 - meet_type = ExactPredicates.meet(edge_a..., edge_b...) - (meet_type == MEETS_OPEN || meet_type == meets) && return true + _line_intersects(edge_a, edge_b) && return true end end return false end +""" + _line_intersects( + edge_a::Edge, + edge_b::Edge, + )::Bool + +Returns true if there is at least one intersection between two edges. +""" +function _line_intersects(edge_a::Edge, edge_b::Edge) + meet_type = ExactPredicates.meet(edge_a..., edge_b...) + return meet_type == 0 || meet_type == 1 +end + """ intersection(geom_a, geom_b)::Union{Tuple{::Real, ::Real}, ::Nothing} diff --git a/src/methods/overlaps.jl b/src/methods/overlaps.jl index 6d84f393b..f99b75c9d 100644 --- a/src/methods/overlaps.jl +++ b/src/methods/overlaps.jl @@ -1,17 +1,61 @@ -# # Overlap checks +# # Overlaps export overlaps -# This code checks whether geometries overlap with each other. +#= +## What is overlaps? -# It does not compute the overlap or intersection geometry. +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 colinear. + +To provide an example, consider these two lines: +```@example cshape +using GeometryOps +using GeometryOps.GeometryBasics +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) +``` +We can see that the two lines overlap in the plot: +```@example cshape +overlap(l1, l2) +``` + +## 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 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. +=# """ 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. It applies to Polygon/Polygon, LineString/LineString, -Multipoint/Multipoint, MultiLineString/MultiLineString and MultiPolygon/MultiPolygon. +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 @@ -24,28 +68,166 @@ GO.overlaps(poly1, poly2) true ``` """ -overlaps(g1, g2)::Bool = overlaps(trait(g1), g1, trait(g2), g2)::Bool -overlaps(t1::FeatureTrait, g1, t2, g2)::Bool = overlaps(GI.geometry(g1), g2) -overlaps(t1, g1, t2::FeatureTrait, g2)::Bool = overlaps(g1, geometry(g2)) -overlaps(t1::FeatureTrait, g1, t2::FeatureTrait, g2)::Bool = overlaps(geometry(g1), geometry(g2)) -overlaps(::PolygonTrait, mp, ::MultiPolygonTrait, p)::Bool = overlaps(p, mp) -function overlaps(::MultiPointTrait, g1, ::MultiPointTrait, g2)::Bool - for p1 in GI.getpoint(g1) - for p2 in GI.getpoint(g2) - equals(p1, p2) && return 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 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 -function overlaps(::PolygonTrait, g1, ::PolygonTrait, g2)::Bool - return intersects(g1, g2) + +""" + 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 -function overlaps(t1::MultiPolygonTrait, mp, t2::PolygonTrait, p1)::Bool - for p2 in GI.getgeom(mp) - overlaps(p1, p2) && return true + +""" + 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 -function overlaps(::MultiPolygonTrait, g1, ::MultiPolygonTrait, g2)::Bool - for p1 in GI.getgeom(g1) - overlaps(PolygonTrait(), mp, PolygonTrait(), p1) && return true + +""" + 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 + +""" + _overlaps( + (a1, a2)::Edge, + (b1, b2)::Edge + )::Bool + +If the edges overlap, meaning that they are colinear but each have one endpoint +outside of the other edge, return true. Else false. +""" +function _overlaps( + (a1, a2)::Edge, + (b1, b2)::Edge +) + # 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 diff --git a/test/methods/bools.jl b/test/methods/bools.jl index 791f0598e..cb1ff945c 100644 --- a/test/methods/bools.jl +++ b/test/methods/bools.jl @@ -114,38 +114,4 @@ import GeometryOps as GO @test GO.crosses(GI.MultiPoint([(1, 2), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == true @test GO.crosses(GI.MultiPoint([(1, 0), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == false @test GO.crosses(GI.LineString([(-2, 2), (-4, 2)]), poly7) == false - - pl1 = GI.Polygon([[(0, 0), (0, 5), (5, 5), (5, 0), (0, 0)]]) - pl2 = GI.Polygon([[(1, 1), (1, 6), (6, 6), (6, 1), (1, 1)]]) - - @test GO.overlaps(pl1, pl2) == true - @test_throws MethodError GO.overlaps(pl1, (1, 1)) - @test_throws MethodError GO.overlaps((1, 1), pl2) - - pl3 = pl4 = GI.Polygon([[ - (-53.57208251953125, 28.287451910503744), - (-53.33038330078125, 28.29228897739706), - (-53.34136962890625, 28.430052892335723), - (-53.57208251953125, 28.287451910503744), - ]]) - @test GO.overlaps(pl3, pl4) == true # this was false before... why? - - mp1 = GI.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) - ]) - - @test GO.overlaps(mp1, mp2) == true - @test GO.overlaps(mp1, mp2) == GO.overlaps(mp2, mp1) end diff --git a/test/methods/equals.jl b/test/methods/equals.jl new file mode 100644 index 000000000..a0b60d6cd --- /dev/null +++ b/test/methods/equals.jl @@ -0,0 +1,104 @@ +@testset "Points/MultiPoints" begin + p1 = LG.Point([0.0, 0.0]) + p2 = LG.Point([0.0, 1.0]) + # Same points + @test GO.equals(p1, p1) + @test GO.equals(p2, p2) + # Different points + @test !GO.equals(p1, p2) + + mp1 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0]]) + mp2 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0], [3.0, 3.0]]) + # Same points + @test LG.equals(mp1, mp1) + @test LG.equals(mp2, mp2) + # Different points + @test !LG.equals(mp1, mp2) + @test !LG.equals(mp1, p1) +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]]) + # Equal lines + @test LG.equals(l1, l1) + @test LG.equals(l2, l2) + # Different lines + @test !LG.equals(l1, l2) && !LG.equals(l2, l1) + + r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) + r2 = LG.LinearRing([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) + l3 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) + # Equal rings + @test GO.equals(r1, r1) + @test GO.equals(r2, r2) + # Different rings + @test !GO.equals(r1, r2) && !GO.equals(r2, r1) + # Equal linear ring and line string + @test !GO.equals(r2, l3) # TODO: should these be equal? +end + +@testset "Polygons/MultiPolygons" begin + p1 = GI.Polygon([[(0, 0), (0, 5), (5, 5), (5, 0), (0, 0)]]) + p2 = GI.Polygon([[(1, 1), (1, 6), (6, 6), (6, 1), (1, 1)]]) + p3 = 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]] + ] + ) + p4 = LG.Polygon( + [ + [[10.0, 0.0], [10.0, 20.0], [30.0, 20.0], [30.0, 0.0], [10.0, 0.0]], + [[16.0, 1.0], [16.0, 11.0], [25.0, 11.0], [25.0, 1.0], [16.0, 1.0]] + ] + ) + p5 = 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]], + [[11.0, 1.0], [11.0, 2.0], [12.0, 2.0], [12.0, 1.0], [11.0, 1.0]] + ] + ) + # Equal polygon + @test GO.equals(p1, p1) + @test GO.equals(p2, p2) + # Different polygons + @test !GO.equals(p1, p2) + # Equal polygons with holes + @test GO.equals(p3, p3) + # Same exterior, different hole + @test !GO.equals(p3, p4) + # Same exterior and first hole, has an extra hole + @test !GO.equals(p3, p5) + + p3 = GI.Polygon( + [[ + [-53.57208251953125, 28.287451910503744], + [-53.33038330078125, 28.29228897739706], + [-53.34136962890625, 28.430052892335723], + [-53.57208251953125, 28.287451910503744], + ]] + ) + # Complex polygon + @test GO.equals(p3, p3) + + 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]] + ] + ]) + m2 = LG.MultiPolygon([ + [ + [[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]] + ], + [[[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0], [0.0, 0.0]]] + ]) + # Equal multipolygon + @test GO.equals(m1, m1) + # Equal multipolygon with different order + @test GO.equals(m1, m2) +end \ No newline at end of file diff --git a/test/methods/intersects.jl b/test/methods/intersects.jl index f3d35c68f..4251d45a8 100644 --- a/test/methods/intersects.jl +++ b/test/methods/intersects.jl @@ -4,29 +4,25 @@ # Test for parallel lines l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) l2 = GI.Line([(0.0, 1.0), (2.5, 1.0)]) - @test !GO.intersects(l1, l2; meets = 0) - @test !GO.intersects(l1, l2; meets = 1) + @test !GO.intersects(l1, l2) @test isnothing(GO.intersection(l1, l2)) # Test for non-parallel lines that don't intersect l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) l2 = GI.Line([(2.0, -3.0), (3.0, 0.0)]) - @test !GO.intersects(l1, l2; meets = 0) - @test !GO.intersects(l1, l2; meets = 1) + @test !GO.intersects(l1, l2) @test isnothing(GO.intersection(l1, l2)) # Test for lines only touching at endpoint l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) l2 = GI.Line([(2.0, -3.0), (2.5, 0.0)]) - @test GO.intersects(l1, l2; meets = 0) - @test !GO.intersects(l1, l2; meets = 1) + @test GO.intersects(l1, l2) @test all(GO.intersection(l1, l2) .≈ (2.5, 0.0)) # Test for lines that intersect in the middle l1 = GI.Line([(0.0, 0.0), (5.0, 5.0)]) l2 = GI.Line([(0.0, 5.0), (5.0, 0.0)]) - @test GO.intersects(l1, l2; meets = 0) - @test GO.intersects(l1, l2; meets = 1) + @test GO.intersects(l1, l2) @test all(GO.intersection(l1, l2) .≈ (2.5, 2.5)) # Line string test intersects ---------------------------------------------- @@ -34,8 +30,7 @@ # Single element line strings crossing over each other l1 = LG.LineString([[5.5, 7.2], [11.2, 12.7]]) l2 = LG.LineString([[4.3, 13.3], [9.6, 8.1]]) - @test GO.intersects(l1, l2; meets = 0) - @test GO.intersects(l1, l2; meets = 1) + @test GO.intersects(l1, l2) go_inter = GO.intersection(l1, l2) lg_inter = LG.intersection(l1, l2) @test go_inter[1][1] .≈ GI.x(lg_inter) @@ -44,9 +39,7 @@ # Multi-element line strings crossing over on vertex l1 = LG.LineString([[0.0, 0.0], [2.5, 0.0], [5.0, 0.0]]) l2 = LG.LineString([[2.0, -3.0], [3.0, 0.0], [4.0, 3.0]]) - @test GO.intersects(l1, l2; meets = 0) - # TODO: Do we want this to be false? It is vertex of segment, not of whole line string - @test !GO.intersects(l1, l2; meets = 1) + @test GO.intersects(l1, l2) go_inter = GO.intersection(l1, l2) @test length(go_inter) == 1 lg_inter = LG.intersection(l1, l2) @@ -56,8 +49,7 @@ # Multi-element line strings crossing over with multiple intersections l1 = LG.LineString([[0.0, -1.0], [1.0, 1.0], [2.0, -1.0], [3.0, 1.0]]) l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [3.0, 0.0]]) - @test GO.intersects(l1, l2; meets = 0) - @test GO.intersects(l1, l2; meets = 1) + @test GO.intersects(l1, l2) go_inter = GO.intersection(l1, l2) @test length(go_inter) == 3 lg_inter = LG.intersection(l1, l2) @@ -69,22 +61,19 @@ # Line strings far apart so extents don't overlap l1 = LG.LineString([[100.0, 0.0], [101.0, 0.0], [103.0, 0.0]]) l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [3.0, 0.0]]) - @test !GO.intersects(l1, l2; meets = 0) - @test !GO.intersects(l1, l2; meets = 1) + @test !GO.intersects(l1, l2) @test isnothing(GO.intersection(l1, l2)) # Line strings close together that don't overlap l1 = LG.LineString([[3.0, 0.25], [5.0, 0.25], [7.0, 0.25]]) l2 = LG.LineString([[0.0, 0.0], [5.0, 10.0], [10.0, 0.0]]) - @test !GO.intersects(l1, l2; meets = 0) - @test !GO.intersects(l1, l2; meets = 1) + @test !GO.intersects(l1, l2) @test isempty(GO.intersection(l1, l2)) # Closed linear ring with open line string r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) l2 = LG.LineString([[0.0, -2.0], [12.0, 10.0],]) - @test GO.intersects(r1, l2; meets = 0) - @test GO.intersects(r1, l2; meets = 1) + @test GO.intersects(r1, l2) go_inter = GO.intersection(r1, l2) @test length(go_inter) == 2 lg_inter = LG.intersection(r1, l2) @@ -96,8 +85,7 @@ # Closed linear ring with closed linear ring r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) r2 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) - @test GO.intersects(r1, r2; meets = 0) - @test GO.intersects(r1, r2; meets = 1) + @test GO.intersects(r1, r2) go_inter = GO.intersection(r1, r2) @test length(go_inter) == 2 lg_inter = LG.intersection(r1, r2) @@ -111,22 +99,19 @@ end # Two polygons that intersect p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) p2 = LG.Polygon([[[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]]) - @test GO.intersects(p1, p2; meets = 0) - @test GO.intersects(p1, p2; meets = 1) + @test GO.intersects(p1, p2) @test all(GO.intersection_points(p1, p2) .== [(6.5, 3.5), (6.5, -3.5)]) # Two polygons that don't intersect p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) p2 = LG.Polygon([[[13.0, 0.0], [18.0, 5.0], [23.0, 0.0], [18.0, -5.0], [13.0, 0.0]]]) - @test !GO.intersects(p1, p2; meets = 0) - @test !GO.intersects(p1, p2; meets = 1) + @test !GO.intersects(p1, p2) @test isnothing(GO.intersection_points(p1, p2)) # Polygon that intersects with linestring p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) l2 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) - @test GO.intersects(p1, l2; meets = 0) - @test GO.intersects(p1, l2; meets = 1) + @test GO.intersects(p1, l2) GO.intersection_points(p1, l2) @test all(GO.intersection_points(p1, l2) .== [(0.0, 0.0), (10.0, 0.0)]) @@ -136,8 +121,7 @@ end [[2.0, -1.0], [2.0, 1.0], [3.0, 1.0], [3.0, -1.0], [2.0, -1.0]] ]) l2 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) - @test GO.intersects(p1, l2; meets = 0) - @test GO.intersects(p1, l2; meets = 1) + @test GO.intersects(p1, l2) @test all(GO.intersection_points(p1, l2) .== [(0.0, 0.0), (2.0, 0.0), (3.0, 0.0), (10.0, 0.0)]) # Polygon with a hole, line only within the hole @@ -146,8 +130,7 @@ end [[2.0, -1.0], [2.0, 1.0], [3.0, 1.0], [3.0, -1.0], [2.0, -1.0]] ]) l2 = LG.LineString([[2.25, 0.0], [2.75, 0.0]]) - @test !GO.intersects(p1, l2; meets = 0) - @test !GO.intersects(p1, l2; meets = 1) + @test !GO.intersects(p1, l2) @test isempty(GO.intersection_points(p1, l2)) end diff --git a/test/methods/overlaps.jl b/test/methods/overlaps.jl new file mode 100644 index 000000000..5123fd3f7 --- /dev/null +++ b/test/methods/overlaps.jl @@ -0,0 +1,105 @@ +@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) + + 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) + + # @test_throws MethodError GO.overlaps(pl1, (1, 1)) # I think these should be false + # @test_throws MethodError GO.overlaps((1, 1), pl2) + + 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 diff --git a/test/runtests.jl b/test/runtests.jl index 7c96de785..ee2065017 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -18,8 +18,10 @@ const GO = GeometryOps @testset "Barycentric coordinate operations" begin include("methods/barycentric.jl") end @testset "Bools" begin include("methods/bools.jl") end @testset "Centroid" begin include("methods/centroid.jl") end + @testset "Equals" begin include("methods/equals.jl") end @testset "Intersect" begin include("methods/intersects.jl") end @testset "Signed Area" begin include("methods/signed_area.jl") end + @testset "Overlaps" begin include("methods/overlaps.jl") end # Transformations @testset "Reproject" begin include("transformations/reproject.jl") end @testset "Flip" begin include("transformations/flip.jl") end From 0b9799d4309ec01029abbae799fe38bc89a44e11 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Wed, 18 Oct 2023 13:03:39 -0700 Subject: [PATCH 07/33] Remove use of findfirst for 1.6 compat --- src/methods/equals.jl | 9 +++++++-- test/methods/overlaps.jl | 5 ++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/methods/equals.jl b/src/methods/equals.jl index 568256845..274866631 100644 --- a/src/methods/equals.jl +++ b/src/methods/equals.jl @@ -130,9 +130,14 @@ function equals(::T, l1, ::T, l2) where {T<:GI.AbstractCurveTrait} # Find first matching point if it exists p1 = GI.getpoint(l1, 1) - offset = findfirst(p2 -> equals(p1, p2), GI.getpoint(l2)) + offset = nothing + for i in 1:n2 + if equals(p1, GI.getpoint(l2, i)) + offset = i - 1 + break + end + end isnothing(offset) && return false - offset -= 1 # Then check all points are the same wrapping around line for i in 1:n1 diff --git a/test/methods/overlaps.jl b/test/methods/overlaps.jl index 5123fd3f7..0c2dab96d 100644 --- a/test/methods/overlaps.jl +++ b/test/methods/overlaps.jl @@ -67,6 +67,8 @@ end ]) # 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 @@ -76,9 +78,6 @@ end # Test one polygon within the other @test GO.overlaps(p2, p4) == GO.overlaps(p4, p2) == LG.overlaps(p2, p4) - # @test_throws MethodError GO.overlaps(pl1, (1, 1)) # I think these should be false - # @test_throws MethodError GO.overlaps((1, 1), pl2) - p5 = LG.Polygon( [[ [-53.57208251953125, 28.287451910503744], From 90fff1c211be896953a0463be90f6c522ee8aef3 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Fri, 20 Oct 2023 00:27:01 -0700 Subject: [PATCH 08/33] Updated geom, multi-geom equality --- src/methods/equals.jl | 26 +++++++++++++++++--- src/try.jl | 7 ++++++ test/methods/equals.jl | 56 ++++++++++++++++++++++++------------------ 3 files changed, 62 insertions(+), 27 deletions(-) create mode 100644 src/try.jl diff --git a/src/methods/equals.jl b/src/methods/equals.jl index 274866631..7c0147f36 100644 --- a/src/methods/equals.jl +++ b/src/methods/equals.jl @@ -6,7 +6,7 @@ export equals ## What is equals? The equals function checks if two geometries are equal. They are equal if they -share the same set of points and edges. +share the same set of points and edges to define the same shape. To provide an example, consider these two lines: ```@example cshape @@ -40,7 +40,8 @@ Note that while we need the same set of points and edges, they don't need to be provided in the same order for polygons. For for example, we need the same set points for two multipoints to be equal, but they don't have to be saved in the same order. This requires checking every point against every other point in the -two geometries we are comparing. +two geometries we are comparing. Additionally, geometries and multi-geometries +can be equal if the multi-geometry only includes that single geometry. =# """ @@ -95,6 +96,14 @@ function equals(::GI.PointTrait, p1, ::GI.PointTrait, p2) return true end +function equals(::GI.PointTrait, p1, ::GI.MultiPointTrait, mp2) + GI.npoint(mp2) == 1 || return false + return equals(p1, GI.getpoint(mp2, 1)) +end + +equals(trait1::GI.MultiPointTrait, mp1, trait2::GI.PointTrait, p2) = + equals(trait2, p2, trait1, mp1) + """ equals(::GI.MultiPointTrait, mp1, ::GI.MultiPointTrait, mp2)::Bool @@ -121,7 +130,10 @@ end Two curves are equal if they share the same set of points going around the curve. """ -function equals(::T, l1, ::T, l2) where {T<:GI.AbstractCurveTrait} +function equals( + ::Union{GI.LineTrait, GI.LineStringTrait, GI.LinearRingTrait}, l1, + ::Union{GI.LineTrait, GI.LineStringTrait, GI.LinearRingTrait}, l2, +) # Check line lengths match n1 = GI.npoint(l1) n2 = GI.npoint(l2) @@ -174,6 +186,14 @@ function equals(::GI.PolygonTrait, geom_a, ::GI.PolygonTrait, geom_b) return true end +function equals(::GI.PolygonTrait, geom_a, ::MultiPolygonTrait, geom_b) + GI.npolygon(geom_b) == 1 || return false + return equals(geom_a, GI.getpolygon(geom_b, 1)) +end + +equals(trait_a::GI.MultiPolygonTrait, geom_a, trait_b::PolygonTrait, geom_b) = + equals(trait_b, geom_b, trait_a, geom_a) + """ equals(::GI.PolygonTrait, geom_a, ::GI.PolygonTrait, geom_b)::Bool diff --git a/src/try.jl b/src/try.jl new file mode 100644 index 000000000..4aecdcdeb --- /dev/null +++ b/src/try.jl @@ -0,0 +1,7 @@ +import GeometryOps as GO +import GeoInterface as GI +import LibGEOS as LG + +p2 = LG.Point([0.0, 1.0]) +mp3 = LG.MultiPoint([p2]) +GO.equals(p2, mp3) diff --git a/test/methods/equals.jl b/test/methods/equals.jl index a0b60d6cd..d31adfd87 100644 --- a/test/methods/equals.jl +++ b/test/methods/equals.jl @@ -2,40 +2,45 @@ p1 = LG.Point([0.0, 0.0]) p2 = LG.Point([0.0, 1.0]) # Same points - @test GO.equals(p1, p1) - @test GO.equals(p2, p2) + @test GO.equals(p1, p1) == LG.equals(p1, p1) + @test GO.equals(p2, p2) == LG.equals(p2, p2) # Different points - @test !GO.equals(p1, p2) + @test GO.equals(p1, p2) == LG.equals(p1, p2) mp1 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0]]) mp2 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0], [3.0, 3.0]]) + mp3 = LG.MultiPoint([p2]) # Same points - @test LG.equals(mp1, mp1) - @test LG.equals(mp2, mp2) + @test GO.equals(mp1, mp1) == LG.equals(mp1, mp1) + @test GO.equals(mp2, mp2) == LG.equals(mp2, mp2) # Different points - @test !LG.equals(mp1, mp2) - @test !LG.equals(mp1, p1) + @test GO.equals(mp1, mp2) == LG.equals(mp1, mp2) + @test GO.equals(mp1, p1) == LG.equals(mp1, p1) + # Point and multipoint + @test GO.equals(p2, mp3) == LG.equals(p2, mp3) 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]]) # Equal lines - @test LG.equals(l1, l1) - @test LG.equals(l2, l2) + @test GO.equals(l1, l1) == LG.equals(l1, l1) + @test GO.equals(l2, l2) == LG.equals(l2, l2) # Different lines - @test !LG.equals(l1, l2) && !LG.equals(l2, l1) + @test GO.equals(l1, l2) == GO.equals(l2, l1) == LG.equals(l1, l2) r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) r2 = LG.LinearRing([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) l3 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) # Equal rings - @test GO.equals(r1, r1) - @test GO.equals(r2, r2) + @test GO.equals(r1, r1) == LG.equals(r1, r1) + @test GO.equals(r2, r2) == LG.equals(r2, r2) # Different rings - @test !GO.equals(r1, r2) && !GO.equals(r2, r1) + @test GO.equals(r1, r2) == GO.equals(r2, r1) == LG.equals(r1, r2) # Equal linear ring and line string - @test !GO.equals(r2, l3) # TODO: should these be equal? + @test GO.equals(r2, l3) == LG.equals(r2, l3) + # Equal linear ring and line + @test GO.equals(l1, GI.Line([(0.0, 0.0), (0.0, 10.0)])) end @testset "Polygons/MultiPolygons" begin @@ -61,18 +66,18 @@ end ] ) # Equal polygon - @test GO.equals(p1, p1) - @test GO.equals(p2, p2) + @test GO.equals(p1, p1) == LG.equals(p1, p1) + @test GO.equals(p2, p2) == LG.equals(p2, p2) # Different polygons - @test !GO.equals(p1, p2) + @test GO.equals(p1, p2) == LG.equals(p1, p2) # Equal polygons with holes - @test GO.equals(p3, p3) + @test GO.equals(p3, p3) == LG.equals(p3, p3) # Same exterior, different hole - @test !GO.equals(p3, p4) + @test GO.equals(p3, p4) == LG.equals(p3, p4) # Same exterior and first hole, has an extra hole - @test !GO.equals(p3, p5) + @test GO.equals(p3, p5) == LG.equals(p3, p5) - p3 = GI.Polygon( + p6 = LG.Polygon( [[ [-53.57208251953125, 28.287451910503744], [-53.33038330078125, 28.29228897739706], @@ -81,7 +86,7 @@ end ]] ) # Complex polygon - @test GO.equals(p3, p3) + @test GO.equals(p6, p6) == LG.equals(p6, p6) m1 = LG.MultiPolygon([ [[[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0], [0.0, 0.0]]], @@ -98,7 +103,10 @@ end [[[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0], [0.0, 0.0]]] ]) # Equal multipolygon - @test GO.equals(m1, m1) + @test GO.equals(m1, m1) == LG.equals(m1, m1) # Equal multipolygon with different order - @test GO.equals(m1, m2) + @test GO.equals(m1, m2) == LG.equals(m2, m2) + # Equal polygon to multipolygon + m3 = LG.MultiPolygon([p3]) + @test GO.equals(p1, m3) == LG.equals(p1, m3) end \ No newline at end of file From 37e0fc7c4b05ea073b8e80f5df59711371e7b70e Mon Sep 17 00:00:00 2001 From: Skylar A Gering Date: Thu, 26 Oct 2023 14:20:29 -0700 Subject: [PATCH 09/33] Add files for new functions --- src/methods/geom_in_geom.jl | 0 src/methods/geom_on_geom.jl | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/methods/geom_in_geom.jl create mode 100644 src/methods/geom_on_geom.jl diff --git a/src/methods/geom_in_geom.jl b/src/methods/geom_in_geom.jl new file mode 100644 index 000000000..e69de29bb diff --git a/src/methods/geom_on_geom.jl b/src/methods/geom_on_geom.jl new file mode 100644 index 000000000..e69de29bb From 92778b07023466e4c1beda51a6bd6a8976ff8635 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Fri, 27 Oct 2023 16:19:52 -0700 Subject: [PATCH 10/33] Start cleaning up geom on and in geom --- src/GeometryOps.jl | 3 + src/methods/bools.jl | 294 ----------------------------------- src/methods/crosses.jl | 36 ++--- src/methods/geom_in_geom.jl | 210 +++++++++++++++++++++++++ src/methods/geom_on_geom.jl | 71 +++++++++ src/methods/orientation.jl | 43 +++++ src/methods/overlaps.jl | 4 +- src/methods/within.jl | 2 +- src/try.jl | 8 + test/methods/bools.jl | 43 +++-- test/methods/geom_in_geom.jl | 62 ++++++++ test/methods/geom_on_geom.jl | 17 ++ test/runtests.jl | 2 + 13 files changed, 456 insertions(+), 339 deletions(-) create mode 100644 src/methods/orientation.jl create mode 100644 src/try.jl create mode 100644 test/methods/geom_in_geom.jl create mode 100644 test/methods/geom_on_geom.jl diff --git a/src/GeometryOps.jl b/src/GeometryOps.jl index 9e19dd553..2fa33f1d5 100644 --- a/src/GeometryOps.jl +++ b/src/GeometryOps.jl @@ -32,6 +32,9 @@ include("methods/within.jl") include("methods/polygonize.jl") include("methods/barycentric.jl") include("methods/equals.jl") +include("methods/geom_in_geom.jl") +include("methods/geom_on_geom.jl") +include("methods/orientation.jl") include("transformations/flip.jl") include("transformations/simplify.jl") diff --git a/src/methods/bools.jl b/src/methods/bools.jl index 30b8716e1..8e938939e 100644 --- a/src/methods/bools.jl +++ b/src/methods/bools.jl @@ -89,297 +89,3 @@ function isconcave(poly)::Bool return false end - -# """ -# isparallel(line1::LineString, line2::LineString)::Bool - -# Return `true` if each segment of `line1` is parallel to the correspondent segment of `line2` - -# ## Examples -# ```julia -# import GeoInterface as GI, GeometryOps as GO -# julia> line1 = GI.LineString([(9.170356, 45.477985), (9.164434, 45.482551), (9.166644, 45.484003)]) -# GeoInterface.Wrappers.LineString{false, false, Vector{Tuple{Float64, Float64}}, Nothing, Nothing}([(9.170356, 45.477985), (9.164434, 45.482551), (9.166644, 45.484003)], nothing, nothing) - -# julia> line2 = GI.LineString([(9.169356, 45.477985), (9.163434, 45.482551), (9.165644, 45.484003)]) -# GeoInterface.Wrappers.LineString{false, false, Vector{Tuple{Float64, Float64}}, Nothing, Nothing}([(9.169356, 45.477985), (9.163434, 45.482551), (9.165644, 45.484003)], nothing, nothing) - -# julia> -# GO.isparallel(line1, line2) -# true -# ``` -# """ -# function isparallel(line1, line2)::Bool -# seg1 = linesegment(line1) -# seg2 = linesegment(line2) - -# for i in eachindex(seg1) -# coors2 = nothing -# coors1 = seg1[i] -# coors2 = seg2[i] -# _isparallel(coors1, coors2) == false && return false -# end -# return true -# end - -# @inline function _isparallel(p1, p2) -# slope1 = bearing_to_azimuth(rhumb_bearing(GI.x(p1), GI.x(p2))) -# slope2 = bearing_to_azimuth(rhumb_bearing(GI.y(p1), GI.y(p2))) - -# return slope1 === slope2 -# end - - -""" - point_on_line(point::Point, line::LineString; ignore_end_vertices::Bool=false)::Bool - -Return true if a point is on a line. Accept a optional parameter to ignore the -start and end vertices of the linestring. - -## Examples - -```jldoctest -import GeoInterface as GI, GeometryOps as GO - -point = (1, 1) -line = GI.LineString([(0, 0), (3, 3), (4, 4)]) -GO.point_on_line(point, line) - -# output -true -``` -""" -function point_on_line(point, line; ignore_end_vertices::Bool=false)::Bool - line_points = tuple_points(line) - n = length(line_points) - - exclude_boundary = :none - for i in 1:n - 1 - if ignore_end_vertices - if i === 1 - exclude_boundary = :start - elseif i === n - 2 - exclude_boundary = :end - elseif (i === 1 && i + 1 === n - 1) - exclude_boundary = :both - end - end - if point_on_segment(point, (line_points[i], line_points[i + 1]); exclude_boundary) - return true - end - end - return false -end - -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 - end - return false -end - -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 - end - return false -end - -""" - point_in_polygon(point::Point, polygon::Union{Polygon, MultiPolygon}, ignore_boundary::Bool=false)::Bool - -Take a Point and a Polygon and determine if the point -resides inside the polygon. The polygon can be convex or concave. The function accounts for holes. - -## Examples - -```jldoctest -import GeoInterface as GI, GeometryOps as GO - -point = (-77.0, 44.0) -poly = GI.Polygon([[(-81, 41), (-81, 47), (-72, 47), (-72, 41), (-81, 41)]]) -GO.point_in_polygon(point, poly) - -# output -true -``` -""" -point_in_polygon(point, polygon; kw...)::Bool = - point_in_polygon(GI.trait(point), point, GI.trait(polygon), polygon; kw...) -function point_in_polygon( - ::PointTrait, point, - ::PolygonTrait, poly; - ignore_boundary::Bool=false, - check_extent::Bool=false, -)::Bool - # Cheaply check that the point is inside the polygon extent - if check_extent - point_in_extent(point, GI.extent(poly)) || return false - end - - # Then check the point is inside the exterior ring - point_in_polygon( - point,GI.getexterior(poly); - ignore_boundary, check_extent=false, - ) || return false - - # Finally make sure the point is not in any of the holes, - # flipping the boundary condition - for ring in GI.gethole(poly) - point_in_polygon( - point, ring; - ignore_boundary=!ignore_boundary, - ) && return false - end - return true -end - -function point_in_polygon( - ::PointTrait, pt, - ::Union{LineStringTrait,LinearRingTrait}, ring; - ignore_boundary::Bool=false, - check_extent::Bool=false, -)::Bool - x, y = GI.x(pt), GI.y(pt) - # Cheaply check that the point is inside the ring extent - if check_extent - point_in_extent(point, GI.extent(ring)) || return false - end - # Then check the point is inside the ring - inside = false - n = GI.npoint(ring) - p_start = GI.getpoint(ring, 1) - p_end = GI.getpoint(ring, n) - # Handle closed vs opne rings - if GI.x(p_start) == GI.x(p_end) && GI.y(p_start) == GI.y(p_end) - n -= 1 - end - # Loop over all points in the ring - for i in 1:(n - 1) - # First point on edge - p_i = GI.getpoint(ring, i) - xi, yi = GI.x(p_i), GI.y(p_i) - # Second point on edge (j = i + 1) - p_j = GI.getpoint(ring, i + 1) - xj, yj = GI.x(p_j), GI.y(p_j) - # Check if point is on the ring boundary - on_boundary = ( # vertex to point has same slope as edge - yi * (xj - x) + yj * (x - xi) == y * (xj - xi) && - (xi - x) * (xj - x) <= 0 && # x is between xi and xj - (yi - y) * (yj - y) <= 0 # y is between yi and yj - ) - on_boundary && return !ignore_boundary - # Check if ray from point passes through edge - intersects = ( - (yi > y) !== (yj > y) && - (x < (xj - xi) * (y - yi) / (yj - yi) + xi) - ) - if intersects - inside = !inside - end - end - return inside -end - -function point_in_extent(p, extent::Extents.Extent) - (x1, x2), (y1, y1) = extent.X, extent.Y - return x1 <= GI.x(p) && y1 <= GI.y(p) && x2 >= GI.x(p) && y2 >= GI.y(p) -end - -line_on_line(line1, line2) = line_on_line(trait(line1), line1, trait(line2), line2) -function line_on_line(t1::GI.AbstractCurveTrait, line1, t2::AbstractCurveTrait, line2) - for p in GI.getpoint(line1) - # FIXME: all points being on the line doesn't - # actually mean the whole line is on the line... - point_on_line(p, line2) || return false - end - return true -end - -line_in_polygon(line, poly) = line_in_polygon(trait(line), line, trait(poly), poly) - -function line_in_polygon( - ::AbstractCurveTrait, line, - ::Union{AbstractPolygonTrait,LinearRingTrait}, poly -) - Extents.intersects(GI.extent(poly), GI.extent(line)) || return false - - inside = false - for i in 1:GI.npoint(line) - 1 - p = GI.getpoint(line, i) - p2 = GI.getpoint(line, i + 1) - point_in_polygon(p, poly) || return false - if !inside - inside = point_in_polygon(p, poly; ignore_boundary=true) - end - # FIXME This seems like a hack, we should check for intersections rather than midpoint?? - if !inside - mid = ((GI.x(p) + GI.x(p2)) / 2, (GI.y(p) + GI.y(p2)) / 2) - inside = point_in_polygon(mid, poly; ignore_boundary=true) - end - end - return inside -end - -function polygon_in_polygon(poly1, poly2) - # edges1, edges2 = to_edges(poly1), to_edges(poly2) - # extent1, extent2 = to_extent(edges1), to_extent(edges2) - # Check the extents intersect - Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false - - # Check all points in poly1 are in poly2 - for point in GI.getpoint(poly1) - point_in_polygon(point, poly2) || return false - end - - # Check the line of poly1 does not intersect the line of poly2 - #intersects(poly1, poly2) && return false - - # poly1 must be in poly2 - return true - end diff --git a/src/methods/crosses.jl b/src/methods/crosses.jl index f8a580db0..09df570f8 100644 --- a/src/methods/crosses.jl +++ b/src/methods/crosses.jl @@ -32,26 +32,26 @@ crosses(::LineStringTrait, g1, ::LineStringTrait, g2)::Bool = line_crosses_line( 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) +# 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 +# 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 - return int_point && ext_point -end +# return int_point && ext_point +# end function line_crosses_line(line1, line2) np2 = GI.npoint(line2) diff --git a/src/methods/geom_in_geom.jl b/src/methods/geom_in_geom.jl index e69de29bb..abce02b31 100644 --- a/src/methods/geom_in_geom.jl +++ b/src/methods/geom_in_geom.jl @@ -0,0 +1,210 @@ +export point_in_geom, point_in_polygon + +""" + point_in_geom(point, geom)::(Bool, Bool) + +Returns if a point is within a given geometry. Returns a boolean tuple with two +elements. The first element of the tuple is true if the point is within the +geometry (excluding edges and vertices) and false othereise. The second element +of the tuple is true if the point is on the geometry, the point is on an edge or +is a vertex, and false otherwise. +""" +point_in_geom(point, geom) = point_in_geom( + GI.trait(point), point, + GI.trait(geom), geom, +) + +line_in_geom(line, geom) = line_in_geom( + GI.trait(line), line, + GI.trait(geom), geom, +) + +""" + point_in_geom( + ::GI.PointTrait, point, + ::GI.LineStringTrait, linestring, + )::(Bool, Bool) + +Returns a boolean tuple with two elements. The first element is if the point is +within the linestring. Note this is only possible if the linestring is closed. +If the linestring isn't closed (repeated last point), this will throw a warning. +The second element is if the point is on the linestring. +""" +function point_in_geom(::GI.PointTrait, point, ::GI.LineStringTrait, linestring) + results = if equals( + GI.getpoint(linestring, 1), + GI.getpoint(linestring, GI.npoint(linestring)), + ) + _point_in_closed_curve(point, linestring) + else + @warn "Linestring isn't closed. Point cannot be 'in' linestring." + (false, false) # TODO: see if point is actually on linestring! + end + return results +end + +""" + point_in_geom( + ::GI.PointTrait, point, + ::GI.LinearRingTrait, linearring, + )::(Bool, Bool) + +Returns a boolean tuple with two elements. The first element is if the point is +within the linear ring. The second element is if the point is on the linestring. +""" +function point_in_geom(::GI.PointTrait, point, ::GI.LinearRingTrait, linearring) + _point_in_extent(point, GI.extent(linearring)) || return (false, false) + return _point_in_closed_curve(point, linearring) +end + +""" + point_in_geom( + ::GI.PointTrait, point, + ::GI.PolygonTrait, poly, + )::(Bool, Bool) + +Returns a boolean tuple with two elements. The first element is if the point is +within the polygon. This means that it also isn't within any holes. The second +element is if the point is on the polygon, including edges and vertices of the +exterior ring and any holes. +""" +function point_in_geom(::GI.PointTrait, point, ::GI.PolygonTrait, poly) + # Cheaply check that the point is inside the polygon extent + _point_in_extent(point, GI.extent(poly)) || return (false, false) + # Check if point is inside or on the exterior ring + in_ext, on_ext = _point_in_closed_curve(point, GI.getexterior(poly)) + on_ext && return (in_ext, on_ext) # point in on external boundary + # Check if the point is in any of the holes + for ring in GI.gethole(poly) + in_hole, on_hole = _point_in_closed_curve(point, ring) + in_hole && return (false, false) # point is in a hole -> not in polygon + on_hole && return (false, on_hole) # point is on an edge + end + return (in_ext, on_ext) # point is inside of polygon +end + +""" + point_in_polygon(point, polygon)::(Bool, Bool) + +Determines if point is within a polygon, returning a tuple where the first +element is if the point is within the polygon edges, and the second is if the +point is on an edge or vertex. +""" +point_in_polygon(point, polygon) = point_in_polygon( + GI.trait(point), point, + GI.trait(polygon), polygon, +) + +""" + point_in_polygon( + ::GI.PointTrait, point, + ::GI.PolygonTrait, poly, + ) + +Returns a boolean tuple with two elements. The first element is if the point is +within the polygon and the second element is if the point is on the polygon. + +Note that this is the same as point_in_geom dispatched on a polygon. +""" +point_in_polygon(trait1::GI.PointTrait, point, trait2::GI.PolygonTrait, poly) = + point_in_geom(trait1, point, trait2, poly) + +line_in_polygon(line, polygon) = any(line_in_geom(line, polygon)) +function line_in_geom(::GI.LineStringTrait, line, ::GI.PolygonTrait, poly) + # Cheaply check that the line extent is inside the polygon extent + Extents.within(GI.extent(line), GI.extent(poly), ) || return (false, false) + point_in, point_on = point_in_polygon(GI.getpoint(line, 1), poly) + (point_in || point_on) || return (false, false) + vertex_on = point_on + line_edges, poly_edges = map(sort! ∘ to_edges, (line, poly)) + for l_edge in line_edges + for p_edge in poly_edges # need to figure out closed vs not closed + _line_intersects(l_edge, p_edge) && return (false, false) + if !vertex_on + v1, _ = l_edge + vertex_on = point_on_segment(v1, p_edge...) + end + end + end + return (!vertex_on, vertex_on) +end + +# function polygon_in_polygon(poly1, poly2) +# # edges1, edges2 = to_edges(poly1), to_edges(poly2) +# # extent1, extent2 = to_extent(edges1), to_extent(edges2) +# # Check the extents intersect +# Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false + +# # Check all points in poly1 are in poly2 +# for point in GI.getpoint(poly1) +# point_in_polygon(point, poly2) || return false +# end + +# # Check the line of poly1 does not intersect the line of poly2 +# #intersects(poly1, poly2) && return false + +# # poly1 must be in poly2 +# return true +# end + +""" + _point_in_closed_curve(point, curve)::(Bool, Bool) + +Determine if point is within or on a closed curve. Point should be an object of +Point trait and curve should be a linearstring or ring, that is assumed to be +closed, regardless of repeated last point. + +The return object is a boolean tuple (in_bounds, on_bounds). The in_bounds +object means that the point is within the curve, while on_bounds means the point +is on an edge. +""" +function _point_in_closed_curve(point, curve) + # Determine number of points + x, y = GI.x(point), GI.y(point) + n = GI.npoint(curve) + n -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) ? 1 : 0 + #= + Check if point is on an edge or if a ray, passing from (x, y) to infinity + through line y = y intersects with the edge + =# + in_bounds = false + on_bounds = false + p_start = GI.getpoint(curve, n) + for i in 1:n + # Determine endpoints and edge lengths + p_end = GI.getpoint(curve, i) + xi, yi = GI.x(p_start), GI.y(p_start) + xj, yj = GI.x(p_end), GI.y(p_end) + Δx, Δy = xj - xi, yj - yi + # Determine if point is on the edge + on_bounds = point_on_segment(point, p_start, p_end) + on_bounds && return (false, on_bounds) + # Edge is vertical, just see if y is between edge endpoints + if Δx == 0 && x < xi && (yi ≥ yj ? yj ≤ y ≤ yi : yi ≤ y ≤ yj) + in_bounds = !in_bounds + #= + Edge is not vertical, find intersection point on y = y and see if it + is between edge endpoints. + =# + elseif Δx != 0 && Δy != 0 + m = Δy / Δx + b = yi - m * xi + x_inter = (y - b) / m + if (x_inter > x) && (xi ≥ xj ? xj < x_inter ≤ xi : xi ≤ x_inter < xj) + in_bounds = !in_bounds + end + end + p_start = p_end + end + return in_bounds, on_bounds +end + +""" + _point_in_extent(p, extent::Extents.Extent)::Bool + +Returns true if the point is the bounding box of the extent and false otherwise. +""" +function _point_in_extent(p, extent::Extents.Extent) + (x1, x2), (y1, y2) = extent.X, extent.Y + return x1 ≤ GI.x(p) ≤ x2 && y1 ≤ GI.y(p) ≤ y2 +end diff --git a/src/methods/geom_on_geom.jl b/src/methods/geom_on_geom.jl index e69de29bb..110ed0cea 100644 --- a/src/methods/geom_on_geom.jl +++ b/src/methods/geom_on_geom.jl @@ -0,0 +1,71 @@ +export point_on_line, line_on_line + +""" + point_on_line(point::Point, line::LineString; ignore_end_vertices::Bool=false)::Bool + +Return true if a point is on a line. Accept a optional parameter to ignore the +start and end vertices of the linestring. + +## Examples + +```jldoctest +import GeoInterface as GI, GeometryOps as GO + +point = (1, 1) +line = GI.LineString([(0, 0), (3, 3), (4, 4)]) +GO.point_on_line(point, line) + +# output +true +``` +""" +function point_on_line(point, line; ignore_end_vertices::Bool=false)::Bool + line_points = tuple_points(line) + n = length(line_points) + + exclude_boundary = :none + for i in 1:n - 1 + if ignore_end_vertices + if i === 1 + exclude_boundary = :start + elseif i === n - 2 + exclude_boundary = :end + elseif (i === 1 && i + 1 === n - 1) + exclude_boundary = :both + end + end + if point_on_segment(point, line_points[i], line_points[i + 1]) + return true + end + end + return false +end + +function point_on_segment(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) + Δx = x2 - x1 + Δy = y2 - y1 + #= + Determine if the point is on the segment -> see if cross product of line and + vector from line start to point is zero -> vectors are parallel. Then, check + point is between segment endpoints. + =# + on_line = _isparallel(Δx, Δy, (x - x1), (y - y1)) + between_endpoints = (x2 > x1 ? x1 <= x <= x2 : x2 <= x <= x1) && + (y2 > y1 ? y1 <= y <= y2 : y2 <= y <= y1) + return on_line && between_endpoints +end + + +line_on_line(line1, line2) = line_on_line(trait(line1), line1, trait(line2), line2) +function line_on_line(t1::GI.AbstractCurveTrait, line1, t2::AbstractCurveTrait, line2) + for p in GI.getpoint(line1) + # FIXME: all points being on the line doesn't + # actually mean the whole line is on the line... + point_on_line(p, line2) || return false + end + return true +end diff --git a/src/methods/orientation.jl b/src/methods/orientation.jl new file mode 100644 index 000000000..8a97dfb85 --- /dev/null +++ b/src/methods/orientation.jl @@ -0,0 +1,43 @@ +# """ +# isparallel(line1::LineString, line2::LineString)::Bool + +# Return `true` if each segment of `line1` is parallel to the correspondent segment of `line2` + +# ## Examples +# ```julia +# import GeoInterface as GI, GeometryOps as GO +# julia> line1 = GI.LineString([(9.170356, 45.477985), (9.164434, 45.482551), (9.166644, 45.484003)]) +# GeoInterface.Wrappers.LineString{false, false, Vector{Tuple{Float64, Float64}}, Nothing, Nothing}([(9.170356, 45.477985), (9.164434, 45.482551), (9.166644, 45.484003)], nothing, nothing) + +# julia> line2 = GI.LineString([(9.169356, 45.477985), (9.163434, 45.482551), (9.165644, 45.484003)]) +# GeoInterface.Wrappers.LineString{false, false, Vector{Tuple{Float64, Float64}}, Nothing, Nothing}([(9.169356, 45.477985), (9.163434, 45.482551), (9.165644, 45.484003)], nothing, nothing) + +# julia> +# GO.isparallel(line1, line2) +# true +# ``` +# """ +# function isparallel(line1, line2)::Bool +# seg1 = linesegment(line1) +# seg2 = linesegment(line2) + +# for i in eachindex(seg1) +# coors2 = nothing +# coors1 = seg1[i] +# coors2 = seg2[i] +# _isparallel(coors1, coors2) == false && return false +# end +# return true +# end + +# @inline function _isparallel(p1, p2) +# slope1 = bearing_to_azimuth(rhumb_bearing(GI.x(p1), GI.x(p2))) +# slope2 = bearing_to_azimuth(rhumb_bearing(GI.y(p1), GI.y(p2))) + +# return slope1 === slope2 +# end + +_isparallel(((ax, ay), (bx, by)), ((cx, cy), (dx, dy))) = + _isparallel(bx - ax, by - ay, dx - cx, dy - cy) + +_isparallel(Δx1, Δy1, Δx2, Δy2) = (Δx1 * Δy2 == Δy1 * Δx2) \ No newline at end of file diff --git a/src/methods/overlaps.jl b/src/methods/overlaps.jl index f99b75c9d..63393abd7 100644 --- a/src/methods/overlaps.jl +++ b/src/methods/overlaps.jl @@ -227,7 +227,7 @@ function _overlaps( # 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) + a_fully_within = point_on_segment(a1, b1, b2) && point_on_segment(a2, b1, b2) + b_fully_within = point_on_segment(b1, a1, a2) && point_on_segment(b2, a1, a2) return on_top && (!a_fully_within && !b_fully_within) end diff --git a/src/methods/within.jl b/src/methods/within.jl index 16366f944..7a38ad7d6 100644 --- a/src/methods/within.jl +++ b/src/methods/within.jl @@ -30,7 +30,7 @@ within(::Any, g1, t2::GI.FeatureTrait, g2)::Bool = within(g1, GI.geometry(g2)) # Points in geometries within(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool = point_on_line(g1, g2; ignore_end_vertices=true) within(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool = point_on_line(g1, g2; ignore_end_vertices=true) -within(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool = point_in_polygon(g1, g2; ignore_boundary=true) +within(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool = point_in_polygon(g1, g2)[1] # Lines in geometries within(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool = line_on_line(g1, g2) within(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool = line_on_line(g1, g2) diff --git a/src/try.jl b/src/try.jl new file mode 100644 index 000000000..4b8fa3901 --- /dev/null +++ b/src/try.jl @@ -0,0 +1,8 @@ +import GeometryOps as GO +import GeoInterface as GI +import LibGEOS as LG + +diamond = LG.Polygon([[ + [0.0, 0.0], [-5.0, 5.0], [0.0, 10.0], [5.0, 5.0], [0.0, 0.0], +]]) +GO.point_in_polygon((-2.5, 2.5), diamond) == on_geom \ No newline at end of file diff --git a/test/methods/bools.jl b/test/methods/bools.jl index cb1ff945c..ab2afd0aa 100644 --- a/test/methods/bools.jl +++ b/test/methods/bools.jl @@ -42,14 +42,9 @@ import GeometryOps as GO p3 = GI.Point([20, 20]) l5 = GI.LineString([[0, 0], [3, 3], [38.32, 5.96]]) - @test GO.point_on_line(p2, l4; ignore_end_vertices=true) == false - @test GO.point_on_line(p3, l5; ignore_end_vertices=true) == false - @test GO.point_on_line(p1, l3) == true - pt = (-77, 44) poly = GI.Polygon([[[-81, 41], [-81, 47], [-72, 47], [-72, 41], [-81, 41]]]) - @test point_in_polygon(pt, poly) == true poly3 = GI.Polygon([[(1, 1), (1, 10), (10, 10), (10, 1), (1, 1)]]) poly4 = GI.Polygon([[(1, 1), (2, 2), (3, 2), (1, 1)]]) @@ -59,26 +54,26 @@ import GeometryOps as GO poly5 = GI.Polygon([[(1.0, 1.0), (1.0, 20.0), (1.0, 3.0), (1.0, 4.0), (1.0, 1.0)]]) line7 = GI.LineString([(1.0, 2.0), (1.0, 3.0), (1.0, 3.5)]) - @test GO.contains(poly3, poly4) == true - @test GO.contains(poly3, line5) == true - @test GO.contains(line6, (1, 2)) == true - @test GO.contains(poly3, poly5) == false - @test GO.contains(poly3 , line7) == false + # @test GO.contains(poly3, poly4) == true + # @test GO.contains(poly3, line5) == true + # @test GO.contains(line6, (1, 2)) == true + # @test GO.contains(poly3, poly5) == false + # @test GO.contains(poly3 , line7) == false - @test GO.within(poly4, poly3) == true - @test GO.within(line5, poly3) == true - @test GO.within(poly5, poly3) == false - @test GO.within((1, 2), line6) == true - @test GO.within(line7, poly3) == false + # @test GO.within(poly4, poly3) == true + # @test GO.within(line5, poly3) == true + # @test GO.within(poly5, poly3) == false + # @test GO.within((1, 2), line6) == true + # @test GO.within(line7, poly3) == false poly6 = GI.Polygon([[(-11, -12), (-13, -12), (-13, -13), (-11, -13), (-11, -12)]]) poly7 = GI.Polygon([[(-1, 2), (3, 2), (3, 3), (-1, 3), (-1, 2)]]) poly8 = GI.Polygon([[(-1, 2), (-13, -12), (-13, -13), (-11, -13), (-1, 2)]]) - @test GO.disjoint(poly7, poly6) == true - @test GO.disjoint(poly7, (1, 1)) == true - @test GO.disjoint(poly7, GI.LineString([(0, 0), (12, 2), (12, 3), (12, 4)])) == true - @test GO.disjoint(poly8, poly7) == false + # @test GO.disjoint(poly7, poly6) == true + # @test GO.disjoint(poly7, (1, 1)) == true + # @test GO.disjoint(poly7, GI.LineString([(0, 0), (12, 2), (12, 3), (12, 4)])) == true + # @test GO.disjoint(poly8, poly7) == false line8 = GI.LineString([(124.584961, -12.768946), (126.738281, -17.224758)]) line9 = GI.LineString([(123.354492, -15.961329), (127.22168, -14.008696)]) @@ -109,9 +104,9 @@ import GeometryOps as GO @test all(points[1] .≈ (119.832884, -19.58857)) @test all(points[2] .≈ (132.808697, -11.6309378)) - @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, 2), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == true - @test GO.crosses(GI.MultiPoint([(1, 0), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == false - @test GO.crosses(GI.LineString([(-2, 2), (-4, 2)]), poly7) == false + # @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, 2), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == true + # @test GO.crosses(GI.MultiPoint([(1, 0), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == false + # @test GO.crosses(GI.LineString([(-2, 2), (-4, 2)]), poly7) == false end diff --git a/test/methods/geom_in_geom.jl b/test/methods/geom_in_geom.jl new file mode 100644 index 000000000..41be456a3 --- /dev/null +++ b/test/methods/geom_in_geom.jl @@ -0,0 +1,62 @@ +const in_geom = (true, false) +const on_geom = (false, true) +const not_in_on_geom = (false, false) + +@testset "Point in Polygon" begin + # Convex polygons + rect = LG.Polygon([[ + [0.0, 0.0], [0.0, 10.0], [10.0, 10.0], [10.0, 0.0], [0.0, 0.0] + ]]) + @test GO.point_in_polygon((0.0, 0.0), rect) == on_geom + @test GO.point_in_polygon((0.0, 5.0), rect) == on_geom + @test GO.point_in_polygon((5.0, 10.0), rect) == on_geom + @test GO.point_in_polygon((2.5, 2.5), rect) == in_geom + @test GO.point_in_polygon((9.99, 9.99), rect) == in_geom + @test GO.point_in_polygon((20.0, 20.0), rect) == not_in_on_geom + + diamond = LG.Polygon([[ + [0.0, 0.0], [-5.0, 5.0], [0.0, 10.0], [5.0, 5.0], [0.0, 0.0], + ]]) + @test GO.point_in_polygon((0.0, 0.0), diamond) == on_geom + @test GO.point_in_polygon((-2.5, 2.5), diamond) == on_geom + @test GO.point_in_polygon((2.5, 2.5), diamond) == on_geom + @test GO.point_in_polygon((0.0, 5.0), diamond) == in_geom + @test GO.point_in_polygon((4.99, 5.0), diamond) == in_geom + @test GO.point_in_polygon((20.0, 20.0), diamond) == not_in_on_geom + + trap_with_hole = LG.Polygon([ + [[-10.0, 0.0], [-8.0, 5.0], [8.0, 5.0], [10.0, 0.0], [-10.0, 0.0]], + [[-5.0, 2.0], [-5.0, 4.0], [-2.0, 4.0], [-2.0, 2.0], [-5.0, 2.0]] + ]) + @test GO.point_in_polygon((-10.0, 0.0), trap_with_hole) == on_geom + @test GO.point_in_polygon((-5.0, 2.0), trap_with_hole) == on_geom + @test GO.point_in_polygon((-5.0, 3.0), trap_with_hole) == on_geom + @test GO.point_in_polygon((-9.0, 0.01), trap_with_hole) == in_geom + @test GO.point_in_polygon((-4.0, 3.0), trap_with_hole) == not_in_on_geom + @test GO.point_in_polygon((20.0, 20.0), trap_with_hole) == not_in_on_geom + # Concave polygons + concave_a = GI.Polygon([[ + (1.2938349167338743, -3.175128530227131), + (-2.073885870841754, -1.6247711001754137), + (-5.787437985975053, 0.06570713422599561), + (-2.1308128111898093, 5.426689675486368), + (2.3058074184797244, 6.926652158268195), + (1.2938349167338743, -3.175128530227131), + ]]) + concave_b = GI.Polygon([[ + (-2.1902469793743924, -1.9576242117579579), + (-4.726006206053999, 1.3907098941556428), + (-3.165301985923147, 2.847612825874245), + (-2.5529280962099428, 4.395492123980911), + (0.5677700216973937, 6.344638314896882), + (3.982554842356183, 4.853519613487035), + (5.251193948893394, 0.9343031382106848), + (5.53045582244555, -3.0101433691361734), + (-2.1902469793743924, -1.9576242117579579), + ]]) + pt = (-2.1902469793743924, -1.9576242117579579) + @test GO.point_in_polygon(pt, concave_a) == (false, false) + @test GO.point_in_polygon(pt, concave_b) == (false, true) + @test GO.point_in_polygon((0.0, 0.0), concave_a) == (true, false) + @test GO.point_in_polygon((0.0, 0.0), concave_b) == (true, false) +end \ No newline at end of file diff --git a/test/methods/geom_on_geom.jl b/test/methods/geom_on_geom.jl new file mode 100644 index 000000000..a95bbab26 --- /dev/null +++ b/test/methods/geom_on_geom.jl @@ -0,0 +1,17 @@ +@testset "Point on segment" begin + + +end + +# l3 = GI.LineString([[0, 0], [3, 3], [4, 4]]) +# p1 = GI.Point([1,1]) + +# l4 = GI.LineString([[0, 0], [3, 3]]) +# p2 = GI.Point([0, 0]) + +# p3 = GI.Point([20, 20]) +# l5 = GI.LineString([[0, 0], [3, 3], [38.32, 5.96]]) + +# @test GO.point_on_line(p2, l4; ignore_end_vertices=true) == false +# @test GO.point_on_line(p3, l5; ignore_end_vertices=true) == false +# @test GO.point_on_line(p1, l3) == true \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index ee2065017..b412cb67d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -19,6 +19,8 @@ const GO = GeometryOps @testset "Bools" begin include("methods/bools.jl") end @testset "Centroid" begin include("methods/centroid.jl") end @testset "Equals" begin include("methods/equals.jl") end + @testset "Geom in geom" begin include("methods/geom_in_geom.jl") end + @testset "Geom on geom" begin include("methods/geom_on_geom.jl") end @testset "Intersect" begin include("methods/intersects.jl") end @testset "Signed Area" begin include("methods/signed_area.jl") end @testset "Overlaps" begin include("methods/overlaps.jl") end From bce638193e19bfa9e960e8565a1632c76243d686 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Fri, 27 Oct 2023 16:22:56 -0700 Subject: [PATCH 11/33] Uncomment polygon_in_polygon --- src/methods/geom_in_geom.jl | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/methods/geom_in_geom.jl b/src/methods/geom_in_geom.jl index abce02b31..00b1ee385 100644 --- a/src/methods/geom_in_geom.jl +++ b/src/methods/geom_in_geom.jl @@ -129,23 +129,23 @@ function line_in_geom(::GI.LineStringTrait, line, ::GI.PolygonTrait, poly) return (!vertex_on, vertex_on) end -# function polygon_in_polygon(poly1, poly2) -# # edges1, edges2 = to_edges(poly1), to_edges(poly2) -# # extent1, extent2 = to_extent(edges1), to_extent(edges2) -# # Check the extents intersect -# Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false - -# # Check all points in poly1 are in poly2 -# for point in GI.getpoint(poly1) -# point_in_polygon(point, poly2) || return false -# end - -# # Check the line of poly1 does not intersect the line of poly2 -# #intersects(poly1, poly2) && return false - -# # poly1 must be in poly2 -# return true -# end +function polygon_in_polygon(poly1, poly2) + # edges1, edges2 = to_edges(poly1), to_edges(poly2) + # extent1, extent2 = to_extent(edges1), to_extent(edges2) + # Check the extents intersect + Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false + + # Check all points in poly1 are in poly2 + for point in GI.getpoint(poly1) + point_in_polygon(point, poly2) || return false + end + + # Check the line of poly1 does not intersect the line of poly2 + intersects(poly1, poly2) && return false + + # poly1 must be in poly2 + return true + end """ _point_in_closed_curve(point, curve)::(Bool, Bool) From 98af38ed97fd6a9112874dc165d1eb74504dbf39 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Fri, 27 Oct 2023 23:36:06 -0700 Subject: [PATCH 12/33] Add more geom in geom --- src/methods/bools.jl | 2 - src/methods/geom_in_geom.jl | 156 +++++++++++++++++++++++++++++------ src/methods/within.jl | 2 +- test/methods/geom_in_geom.jl | 3 +- 4 files changed, 136 insertions(+), 27 deletions(-) diff --git a/src/methods/bools.jl b/src/methods/bools.jl index 8e938939e..1208bd555 100644 --- a/src/methods/bools.jl +++ b/src/methods/bools.jl @@ -1,8 +1,6 @@ # # Boolean conditions export isclockwise, isconcave -export point_on_line, point_in_polygon, point_in_ring -export line_on_line, line_in_polygon, polygon_in_polygon # These are all adapted from Turf.jl. diff --git a/src/methods/geom_in_geom.jl b/src/methods/geom_in_geom.jl index 00b1ee385..43068206b 100644 --- a/src/methods/geom_in_geom.jl +++ b/src/methods/geom_in_geom.jl @@ -19,6 +19,11 @@ line_in_geom(line, geom) = line_in_geom( GI.trait(geom), geom, ) +ring_in_geom(ring, geom) = ring_in_geom( + GI.trait(ring), ring, + GI.trait(geom), geom, +) + """ point_in_geom( ::GI.PointTrait, point, @@ -30,19 +35,52 @@ within the linestring. Note this is only possible if the linestring is closed. If the linestring isn't closed (repeated last point), this will throw a warning. The second element is if the point is on the linestring. """ -function point_in_geom(::GI.PointTrait, point, ::GI.LineStringTrait, linestring) +function point_in_geom(::GI.PointTrait, point, ::GI.LineStringTrait, line) results = if equals( - GI.getpoint(linestring, 1), - GI.getpoint(linestring, GI.npoint(linestring)), + GI.getpoint(line, 1), + GI.getpoint(line, GI.npoint(line)), ) - _point_in_closed_curve(point, linestring) + _point_in_closed_curve(point, line) else @warn "Linestring isn't closed. Point cannot be 'in' linestring." - (false, false) # TODO: see if point is actually on linestring! + (false, false) end return results end +function line_in_geom(::GI.LineStringTrait, line1, ::GI.LineStringTrait, line2) + results = if equals( + GI.getpoint(line2, 1), + GI.getpoint(line2, GI.npoint(line2)), + ) + Extents.within( + GI.extent(line1), + GI.extent(line2), + ) || return (false, false) + _line_in_closed_curve(line1, line2; close = false) + else + @warn "Linestring isn't closed. Point cannot be 'in' linestring." + (false, false) + end + return results +end + +function ring_in_geom(::GI.LinearRingTrait, ring, ::GI.LineStringTrait, line) + results = if equals( + GI.getpoint(line, 1), + GI.getpoint(line, GI.npoint(line)), + ) + Extents.within( + GI.extent(ring), + GI.extent(line), + ) || return (false, false) + _line_in_closed_curve(ring, line; close = true) + else + @warn "Linestring isn't closed. Point cannot be 'in' linestring." + (false, false) + end + return results +end """ point_in_geom( ::GI.PointTrait, point, @@ -52,9 +90,19 @@ end Returns a boolean tuple with two elements. The first element is if the point is within the linear ring. The second element is if the point is on the linestring. """ -function point_in_geom(::GI.PointTrait, point, ::GI.LinearRingTrait, linearring) - _point_in_extent(point, GI.extent(linearring)) || return (false, false) - return _point_in_closed_curve(point, linearring) +function point_in_geom(::GI.PointTrait, point, ::GI.LinearRingTrait, ring) + _point_in_extent(point, GI.extent(ring)) || return (false, false) + return _point_in_closed_curve(point, ring) +end + +function line_in_geom(::GI.LineStringTrait, line, ::GI.LinearRingTrait, ring) + Extents.within(GI.extent(line), GI.extent(ring)) || return (false, false) + return _line_in_closed_curve(line, curve; close = false) +end + +function ring_in_geom(::GI.LinearRingTrait, ring1, ::GI.LinearRingTrait, ring2) + Extents.within(GI.extent(ring1), GI.extent(ring2)) || return (false, false) + return _line_in_closed_curve(ring1, ring2; close = true) end """ @@ -73,6 +121,7 @@ function point_in_geom(::GI.PointTrait, point, ::GI.PolygonTrait, poly) _point_in_extent(point, GI.extent(poly)) || return (false, false) # Check if point is inside or on the exterior ring in_ext, on_ext = _point_in_closed_curve(point, GI.getexterior(poly)) + (in_ext || on_ext) || return (false, false) # point isn't in external ring on_ext && return (in_ext, on_ext) # point in on external boundary # Check if the point is in any of the holes for ring in GI.gethole(poly) @@ -109,24 +158,49 @@ Note that this is the same as point_in_geom dispatched on a polygon. point_in_polygon(trait1::GI.PointTrait, point, trait2::GI.PolygonTrait, poly) = point_in_geom(trait1, point, trait2, poly) -line_in_polygon(line, polygon) = any(line_in_geom(line, polygon)) +line_in_polygon(line, polygon) = line_in_geom(line, polygon) + function line_in_geom(::GI.LineStringTrait, line, ::GI.PolygonTrait, poly) # Cheaply check that the line extent is inside the polygon extent - Extents.within(GI.extent(line), GI.extent(poly), ) || return (false, false) - point_in, point_on = point_in_polygon(GI.getpoint(line, 1), poly) - (point_in || point_on) || return (false, false) - vertex_on = point_on - line_edges, poly_edges = map(sort! ∘ to_edges, (line, poly)) - for l_edge in line_edges - for p_edge in poly_edges # need to figure out closed vs not closed - _line_intersects(l_edge, p_edge) && return (false, false) - if !vertex_on - v1, _ = l_edge - vertex_on = point_on_segment(v1, p_edge...) - end - end + Extents.within(GI.extent(line), GI.extent(poly)) || return (false, false) + # Check if point is inside or on the exterior ring + in_ext, on_ext = _line_in_closed_curve( + line, + GI.getexterior(poly); + close = false, + ) + (in_ext || on_ext) || return (false, false) # line isn't in external ring + # Check if the line is in any of the holes + for ring in GI.gethole(poly) + in_hole, on_hole = _line_in_closed_curve(point, ring; close = false) + # point is in a hole -> not in polygon + (in_hole || on_hole) && return (false, false) # TODO: what if all points on the edge of hole? end - return (!vertex_on, vertex_on) + return (in_ext, on_ext) # point is inside of polygon +end + +function ring_in_geom(::GI.LinearRingTrait, ring, ::GI.PolygonTrait, poly) + # Cheaply check that the line extent is inside the polygon extent + Extents.within(GI.extent(ring), GI.extent(poly)) || return (false, false) + # Check if point is inside or on the exterior ring + in_ext, on_ext = _line_in_closed_curve( + ring, + GI.getexterior(poly); + close = false, + ) + (in_ext || on_ext) || return (false, false) # line isn't in external ring + # Check if the line is in any of the holes + for hole in GI.gethole(poly) + in_hole, on_hole = _line_in_closed_curve(point, hole; close = true) + # point is in a hole -> not in polygon + (in_hole || on_hole) && return (false, false) # TODO: what if all points on the edge of hole? + end + return (in_ext, on_ext) # point is inside of polygon +end + +function polygon_in_geom(::GI.PolygonTrait, poly1, ::GI.PolygonTrait, poly2) + Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false + end function polygon_in_polygon(poly1, poly2) @@ -199,6 +273,42 @@ function _point_in_closed_curve(point, curve) return in_bounds, on_bounds end +function _line_in_closed_curve(line, curve; close = false) + # Determine number of points in curve and line + nc = GI.npoint(curve) + nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 + nl = GI.npoint(line) + nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 + # Check to see if first point in line is within curve + point_in, point_on = point_in_polygon(GI.getpoint(line, 1), curve) + (point_in || point_on) || return (false, false) # point is outside curve + # Check for any intersections between line and curve + vertex_on = point_on + l_start_idx = close ? nl : 1 + l_range = close ? 1 : 2 + c_start = GI.getpoint(curve, nc) + for i in 1:nc + c_end = GI.getpoint(curve, i) + l_start = GI.getpoint(line, l_start_idx) + for j in l_range:nl + l_end = GI.getpoint(line, j) + # Check if edges intersect --> line is not within curve + _line_intersects( + (l_start, l_end), + (c_start, c_end), + ) && return (false, false) + # Check if either vertex is on the edge of the curve + if !vertex_on + vertex_on = point_on_segment(l_start, c_start, c_end) || + point_on_segment(l_end, c_start, c_end) + end + l_start = l_end + end + c_start = c_end + end + return (!vertex_on, vertex_on) +end + """ _point_in_extent(p, extent::Extents.Extent)::Bool diff --git a/src/methods/within.jl b/src/methods/within.jl index 7a38ad7d6..804bde688 100644 --- a/src/methods/within.jl +++ b/src/methods/within.jl @@ -34,7 +34,7 @@ within(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool = point_in_polygon(g1, # Lines in geometries within(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool = line_on_line(g1, g2) within(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool = line_on_line(g1, g2) -within(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool = line_in_polygon(g1, g2) +within(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool = any(line_in_polygon(g1, g2)) # Polygons within geometries within(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool = polygon_in_polygon(g1, g2) diff --git a/test/methods/geom_in_geom.jl b/test/methods/geom_in_geom.jl index 41be456a3..ad35266cf 100644 --- a/test/methods/geom_in_geom.jl +++ b/test/methods/geom_in_geom.jl @@ -2,7 +2,8 @@ const in_geom = (true, false) const on_geom = (false, true) const not_in_on_geom = (false, false) -@testset "Point in Polygon" begin + +@testset "Point in Geom" begin # Convex polygons rect = LG.Polygon([[ [0.0, 0.0], [0.0, 10.0], [10.0, 10.0], [10.0, 0.0], [0.0, 0.0] From 5034be25c45fc383a6da2ab8ca6158e3f8bbb578 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 7 Nov 2023 18:27:30 -0800 Subject: [PATCH 13/33] Update line in curve and add comments --- src/methods/geom_in_geom.jl | 706 ++++++++++++++++++++++++----------- src/try.jl | 12 +- test/methods/geom_in_geom.jl | 211 ++++++++--- 3 files changed, 661 insertions(+), 268 deletions(-) diff --git a/src/methods/geom_in_geom.jl b/src/methods/geom_in_geom.jl index 43068206b..78be3a991 100644 --- a/src/methods/geom_in_geom.jl +++ b/src/methods/geom_in_geom.jl @@ -1,312 +1,590 @@ export point_in_geom, point_in_polygon """ - point_in_geom(point, geom)::(Bool, Bool) - -Returns if a point is within a given geometry. Returns a boolean tuple with two -elements. The first element of the tuple is true if the point is within the -geometry (excluding edges and vertices) and false othereise. The second element -of the tuple is true if the point is on the geometry, the point is on an edge or -is a vertex, and false otherwise. + point_in_geom( + point, geom; + in::T = 1, on::T = -1, out::T = 0, + )::T + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means the point is within the geometry (excluding edges and vertices). +`On` means the point is on an edge or a vertex of the geometry. +`Out` means the point is outside of the geometry. """ -point_in_geom(point, geom) = point_in_geom( +point_in_geom( + point, geom; + in::T = 1, on::T = -1, out::T = 0, +) where {T} = point_in_geom( GI.trait(point), point, - GI.trait(geom), geom, + GI.trait(geom), geom; + in = in, on = on, out = out, ) -line_in_geom(line, geom) = line_in_geom( +""" + line_in_geom( + line, geom; + in::T = 1, on::T = -1, out::T = 0, + )::T + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means the line is within geometry (no segments on edges and vertices). +`On` means the line has at least one segment on a geometry edge or a vertex. +`Out` means the line has at least one segment outside of the geometry. +""" +line_in_geom( + line, geom; + in::T = 1, on::T = -1, out::T = 0, +) where {T} = line_in_geom( GI.trait(line), line, - GI.trait(geom), geom, + GI.trait(geom), geom; + in = in, on = on, out = out, ) -ring_in_geom(ring, geom) = ring_in_geom( - GI.trait(ring), ring, - GI.trait(geom), geom, -) +# ring_in_geom(ring, geom) = ring_in_geom( +# GI.trait(ring), ring, +# GI.trait(geom), geom, +# ) """ point_in_geom( ::GI.PointTrait, point, - ::GI.LineStringTrait, linestring, - )::(Bool, Bool) - -Returns a boolean tuple with two elements. The first element is if the point is -within the linestring. Note this is only possible if the linestring is closed. -If the linestring isn't closed (repeated last point), this will throw a warning. -The second element is if the point is on the linestring. + ::GI.LineStringTrait, line; + in::T = 1, on::T = -1, out::T = 0, + )::T + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +Note that a point can only be within a linestring if the linestring is closed, +by having an explicilty repeated last point. Even then, this means the point is +within the ring created by the linestring, not on the linestring itself. +`In` means the point is within the linestring (excluding edges and vertices). +`On` means the point is on an edge or a vertex of the linestring. +`Out` means the point is outside of the linestring. """ -function point_in_geom(::GI.PointTrait, point, ::GI.LineStringTrait, line) +function point_in_geom( + ::GI.PointTrait, point, + ::GI.LineStringTrait, line; + in::T = 1, on::T = -1, out::T = 0, +) where {T} results = if equals( GI.getpoint(line, 1), GI.getpoint(line, GI.npoint(line)), ) - _point_in_closed_curve(point, line) + _point_in_extent(point, GI.extent(line)) || return out + _point_in_closed_curve(point, line; in = in, on = on, out = out) else @warn "Linestring isn't closed. Point cannot be 'in' linestring." - (false, false) + out end return results end -function line_in_geom(::GI.LineStringTrait, line1, ::GI.LineStringTrait, line2) - results = if equals( +""" + line_in_geom( + ::GI.LineStringTrait, line1, + ::GI.LineStringTrait, line2; + in::T = 1, on::T = -1, out::T = 0, + ) + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +Note that a linestring can only be within a linestring if that linestring is +closed by having an explicilty repeated last point. Even then, this means the +point is within the ring created by the linestring, not on the linestring +itself. +`In` means the line is within geometry (no segments on edges and vertices). +`On` means the line has at least one segment on a geometry edge or a vertex. +`Out` means the line has at least one segment outside of the geometry. +""" +function line_in_geom( + ::GI.LineStringTrait, line1, + ::GI.LineStringTrait, line2; + in::T = 1, on::T = -1, out::T = 0, +) where {T} + results = if equals( # if line2 is closed by a repeated last point GI.getpoint(line2, 1), GI.getpoint(line2, GI.npoint(line2)), ) - Extents.within( + Extents.intersects( GI.extent(line1), GI.extent(line2), - ) || return (false, false) - _line_in_closed_curve(line1, line2; close = false) + ) || return out + _line_in_closed_curve( + line1, line2; + close = false, in = in, on = on, out = out) else @warn "Linestring isn't closed. Point cannot be 'in' linestring." - (false, false) + out end return results end -function ring_in_geom(::GI.LinearRingTrait, ring, ::GI.LineStringTrait, line) - results = if equals( - GI.getpoint(line, 1), - GI.getpoint(line, GI.npoint(line)), - ) - Extents.within( - GI.extent(ring), - GI.extent(line), - ) || return (false, false) - _line_in_closed_curve(ring, line; close = true) - else - @warn "Linestring isn't closed. Point cannot be 'in' linestring." - (false, false) - end - return results -end +# function ring_in_geom(::GI.LinearRingTrait, ring, ::GI.LineStringTrait, line) +# results = if equals( +# GI.getpoint(line, 1), +# GI.getpoint(line, GI.npoint(line)), +# ) +# Extents.intersects( +# GI.extent(ring), +# GI.extent(line), +# ) || return (false, false) +# _line_in_closed_curve(ring, line; close = true) +# else +# @warn "Linestring isn't closed. Point cannot be 'in' linestring." +# (false, false) +# end +# return results +# end """ point_in_geom( ::GI.PointTrait, point, - ::GI.LinearRingTrait, linearring, - )::(Bool, Bool) - -Returns a boolean tuple with two elements. The first element is if the point is -within the linear ring. The second element is if the point is on the linestring. + ::GI.LinearRingTrait, ring; + in::T = 1, on::T = -1, out::T = 0 + )::T + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means the point is within the linear ring (excluding edges and vertices). +`On` means the point is on an edge or a vertex of the linear ring. +`Out` means the point is outside of the linear ring. """ -function point_in_geom(::GI.PointTrait, point, ::GI.LinearRingTrait, ring) - _point_in_extent(point, GI.extent(ring)) || return (false, false) - return _point_in_closed_curve(point, ring) +function point_in_geom( + ::GI.PointTrait, point, + ::GI.LinearRingTrait, ring; + in::T = 1, on::T = -1, out::T = 0 +) where {T} + _point_in_extent(point, GI.extent(ring)) || return out + return _point_in_closed_curve(point, ring; in = in, on = on, out = out) end -function line_in_geom(::GI.LineStringTrait, line, ::GI.LinearRingTrait, ring) - Extents.within(GI.extent(line), GI.extent(ring)) || return (false, false) - return _line_in_closed_curve(line, curve; close = false) -end +""" + line_in_geom( + ::GI.LineStringTrait, line, + ::GI.LinearRingTrait, ring; + in::T = 1, on::T = -1, out::T = 0, + ) -function ring_in_geom(::GI.LinearRingTrait, ring1, ::GI.LinearRingTrait, ring2) - Extents.within(GI.extent(ring1), GI.extent(ring2)) || return (false, false) - return _line_in_closed_curve(ring1, ring2; close = true) +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means the line is within the ring (no segments on edges and vertices). +`On` means the line has at least one segment on a ring edge or a vertex. +`Out` means the line has at least one segment outside of the ring. +""" +function line_in_geom( + ::GI.LineStringTrait, line, + ::GI.LinearRingTrait, ring; + in::T = 1, on::T = -1, out::T = 0, +) where {T} + Extents.intersects(GI.extent(line), GI.extent(ring)) || return out + return _line_in_closed_curve( + line, ring; + close = false, in = in, on = on, out = out, + ) end +# function ring_in_geom(::GI.LinearRingTrait, ring1, ::GI.LinearRingTrait, ring2) +# Extents.intersects(GI.extent(ring1), GI.extent(ring2)) || return (false, false) +# _line_in_closed_curve(ring1, ring2; close = true) +# end + +# function polygon_in_geom(::GI.PolygonTrait, poly, ::GI.LinearRingTrait, ring) +# Extents.intersects(GI.extent(poly), GI.extent(ring)) || return (false, false) +# return _line_in_closed_curve(GI.getexterior(poly), ring; close = true) +# end + """ point_in_geom( ::GI.PointTrait, point, - ::GI.PolygonTrait, poly, - )::(Bool, Bool) - -Returns a boolean tuple with two elements. The first element is if the point is -within the polygon. This means that it also isn't within any holes. The second -element is if the point is on the polygon, including edges and vertices of the -exterior ring and any holes. + ::GI.PolygonTrait, poly; + in::T = 1, on::T = -1, out::T = 0, + )::T + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means the point is within polygon (excluding edges, vertices, and holes). +`On` means the point is on an edge or a vertex of the polygon. +`Out` means the point is outside of the polygon, including within holes. """ -function point_in_geom(::GI.PointTrait, point, ::GI.PolygonTrait, poly) - # Cheaply check that the point is inside the polygon extent - _point_in_extent(point, GI.extent(poly)) || return (false, false) - # Check if point is inside or on the exterior ring - in_ext, on_ext = _point_in_closed_curve(point, GI.getexterior(poly)) - (in_ext || on_ext) || return (false, false) # point isn't in external ring - on_ext && return (in_ext, on_ext) # point in on external boundary - # Check if the point is in any of the holes +function point_in_geom( + ::GI.PointTrait, point, + ::GI.PolygonTrait, poly; + in::T = 1, on::T = -1, out::T = 0, +) where {T} + _point_in_extent(point, GI.extent(poly)) || return out + ext_val = _point_in_closed_curve( + point, GI.getexterior(poly); + in = in, on = on, out = out, + ) + ext_val == on && return ext_val + in_out_counter = (ext_val == in) ? 1 : 0 for ring in GI.gethole(poly) - in_hole, on_hole = _point_in_closed_curve(point, ring) - in_hole && return (false, false) # point is in a hole -> not in polygon - on_hole && return (false, on_hole) # point is on an edge + hole_val = _point_in_closed_curve( + point, ring; + in = in, on = on, out = out, + ) + hole_val == on && return hole_val + in_out_counter += (hole_val == in) ? 1 : 0 end - return (in_ext, on_ext) # point is inside of polygon + return iseven(in_out_counter) ? out : in end """ - point_in_polygon(point, polygon)::(Bool, Bool) - -Determines if point is within a polygon, returning a tuple where the first -element is if the point is within the polygon edges, and the second is if the -point is on an edge or vertex. + point_in_polygon( + point, polygon; + in::T = 1, on::T = -1, out::T = 0, + )::T + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means the point is within polygon (excluding edges, vertices, and holes). +`On` means the point is on an edge or a vertex of the polygon. +`Out` means the point is outside of the polygon, including within holes. """ -point_in_polygon(point, polygon) = point_in_polygon( +point_in_polygon( + point, polygon; + in::T = 1, on::T = -1, out::T = 0, +) where {T} = point_in_polygon( GI.trait(point), point, - GI.trait(polygon), polygon, + GI.trait(polygon), polygon; + in = in, on = on, out = out, ) """ point_in_polygon( - ::GI.PointTrait, point, - ::GI.PolygonTrait, poly, - ) - -Returns a boolean tuple with two elements. The first element is if the point is -within the polygon and the second element is if the point is on the polygon. + trait1::GI.PointTrait, point, + trait2::GI.PolygonTrait, poly; + in::T = 1, on::T = -1, out::T = 0, + )::T + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means the point is within polygon (excluding edges, vertices, and holes). +`On` means the point is on an edge or a vertex of the polygon. +`Out` means the point is outside of the polygon, including within holes. Note that this is the same as point_in_geom dispatched on a polygon. """ -point_in_polygon(trait1::GI.PointTrait, point, trait2::GI.PolygonTrait, poly) = - point_in_geom(trait1, point, trait2, poly) - -line_in_polygon(line, polygon) = line_in_geom(line, polygon) +point_in_polygon( + trait1::GI.PointTrait, point, + trait2::GI.PolygonTrait, poly; + in::T = 1, on::T = -1, out::T = 0, +) where {T}= point_in_geom( + trait1, point, + trait2, poly; + in = in, on = on, out = out, +) -function line_in_geom(::GI.LineStringTrait, line, ::GI.PolygonTrait, poly) - # Cheaply check that the line extent is inside the polygon extent - Extents.within(GI.extent(line), GI.extent(poly)) || return (false, false) - # Check if point is inside or on the exterior ring - in_ext, on_ext = _line_in_closed_curve( - line, - GI.getexterior(poly); - close = false, +""" + point_in_geom( + ::GI.LineStringTrait, line, + ::GI.PolygonTrait, poly; + in::T = 1, on::T = -1, out::T = 0, ) - (in_ext || on_ext) || return (false, false) # line isn't in external ring - # Check if the line is in any of the holes - for ring in GI.gethole(poly) - in_hole, on_hole = _line_in_closed_curve(point, ring; close = false) - # point is in a hole -> not in polygon - (in_hole || on_hole) && return (false, false) # TODO: what if all points on the edge of hole? - end - return (in_ext, on_ext) # point is inside of polygon -end -function ring_in_geom(::GI.LinearRingTrait, ring, ::GI.PolygonTrait, poly) - # Cheaply check that the line extent is inside the polygon extent - Extents.within(GI.extent(ring), GI.extent(poly)) || return (false, false) - # Check if point is inside or on the exterior ring - in_ext, on_ext = _line_in_closed_curve( - ring, - GI.getexterior(poly); - close = false, +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means the line is within the polygon (no segments on edges, vertices, or + holes). +`On` means the line has at least one segment on a polygon edge or a vertex. +`Out` means the line has at least one segment outside of the polygon (including + within a hole). +""" +function point_in_geom( + ::GI.LineStringTrait, line, + ::GI.PolygonTrait, poly; + in::T = 1, on::T = -1, out::T = 0, +) where {T} + Extents.intersects(GI.extent(line), GI.extent(ring)) || return out + ext_val = _line_in_closed_curve( + line, GI.getexterior(poly); + close = false, in = in, on = on, out = out, ) - (in_ext || on_ext) || return (false, false) # line isn't in external ring - # Check if the line is in any of the holes - for hole in GI.gethole(poly) - in_hole, on_hole = _line_in_closed_curve(point, hole; close = true) - # point is in a hole -> not in polygon - (in_hole || on_hole) && return (false, false) # TODO: what if all points on the edge of hole? + + for ring in GI.gethole(poly) + hole_val = _point_in_closed_curve( + point, ring; + in = in, on = on, out = out, + ) + hole_val == on && return hole_val + in_out_counter += (hole_val == in) ? 1 : 0 end - return (in_ext, on_ext) # point is inside of polygon + return iseven(in_out_counter) ? out : in end -function polygon_in_geom(::GI.PolygonTrait, poly1, ::GI.PolygonTrait, poly2) - Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false - -end - -function polygon_in_polygon(poly1, poly2) - # edges1, edges2 = to_edges(poly1), to_edges(poly2) - # extent1, extent2 = to_extent(edges1), to_extent(edges2) - # Check the extents intersect - Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false - - # Check all points in poly1 are in poly2 - for point in GI.getpoint(poly1) - point_in_polygon(point, poly2) || return false - end - - # Check the line of poly1 does not intersect the line of poly2 - intersects(poly1, poly2) && return false - - # poly1 must be in poly2 - return true - end +# line_in_polygon( +# line, poly; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T} = line_in_geom( +# line, GI.trait(line), +# poly, GI.trait(poly); +# in = in, on = on, out = out, +# ) + +# ring_in_geom(::GI.LinearRingTrait, ring, ::GI.PolygonTrait, poly) = +# _geom_in_polygon(ring, poly; close = true) + +# function polygon_in_geom(::GI.PolygonTrait, poly1, ::GI.PolygonTrait, poly2) +# # Cheaply check that the point is inside the polygon extent +# Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return (false, false) +# # Make sure exterior of poly1 is within exterior of poly2 +# in_ext, some_on_ext = _line_in_closed_curve( +# GI.getexterior(poly1), GI.getexterior(poly2); +# close = true, +# ) +# # poly1 not within poly2's external ring +# (in_ext || some_on_ext) || return (false, false) +# # Check if the geom is in any of the holes +# outside_hole, some_on_hole = true, false +# for hole in GI.gethole(poly) +# outside_hole, some_on_hole = _line_in_closed_curve( +# geom, hole; +# close = close, in = false, +# ) +# # geom is in a hole -> not in polygon +# !(outside_hole || some_on_hole) && return (false, false) +# end +# return (in_ext && outside_hole, some_on_hole || some_on_ext) # geom is inside of polygon +# end + +# function polygon_in_polygon(poly1, poly2) +# # edges1, edges2 = to_edges(poly1), to_edges(poly2) +# # extent1, extent2 = to_extent(edges1), to_extent(edges2) +# # Check the extents intersect +# Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false + +# # Check all points in poly1 are in poly2 +# for point in GI.getpoint(poly1) +# point_in_polygon(point, poly2) || return false +# end + +# # Check the line of poly1 does not intersect the line of poly2 +# intersects(poly1, poly2) && return false + +# # poly1 must be in poly2 +# return true +# end """ - _point_in_closed_curve(point, curve)::(Bool, Bool) - -Determine if point is within or on a closed curve. Point should be an object of -Point trait and curve should be a linearstring or ring, that is assumed to be -closed, regardless of repeated last point. - -The return object is a boolean tuple (in_bounds, on_bounds). The in_bounds -object means that the point is within the curve, while on_bounds means the point -is on an edge. + _point_in_closed_curve( + point, curve; + in::T = 1, on::T = -1, out::T = 0, + )::T + +Determine if point is in, on, or out of a closed curve. Point should be an +object of Point trait and curve should be a linearstring or ring, that is +assumed to be closed, regardless of repeated last point. + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means the point is within the closed curve (excluding edges and vertices). +`On` means the point is on an edge or a vertex of the closed curve. +`Out` means the point is outside of the closed curve. + +Note that this uses the Algorithm by Hao and Sun (2018): +https://doi.org/10.3390/sym10100477 +Paper seperates orientation of point and edge into 26 cases. For each case, it +is either a case where the point is on the edge (returns on), where a ray from +the point (x, y) to infinity along the line y = y cut through the edge (k += 1), +or the ray does not pass through the edge (do nothing and continue). If the ray +passes through an odd number of edges, it is within the curve, else outside of +of the curve if it didn't return 'on'. +See paper for more information on cases denoted in comments. """ -function _point_in_closed_curve(point, curve) - # Determine number of points +function _point_in_closed_curve( + point, curve; + in::T = 1, on::T = -1, out::T = 0, +) where {T} x, y = GI.x(point), GI.y(point) n = GI.npoint(curve) n -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) ? 1 : 0 - #= - Check if point is on an edge or if a ray, passing from (x, y) to infinity - through line y = y intersects with the edge - =# - in_bounds = false - on_bounds = false + k = 0 # counter for ray crossings p_start = GI.getpoint(curve, n) - for i in 1:n - # Determine endpoints and edge lengths + @inbounds for i in 1:n p_end = GI.getpoint(curve, i) - xi, yi = GI.x(p_start), GI.y(p_start) - xj, yj = GI.x(p_end), GI.y(p_end) - Δx, Δy = xj - xi, yj - yi - # Determine if point is on the edge - on_bounds = point_on_segment(point, p_start, p_end) - on_bounds && return (false, on_bounds) - # Edge is vertical, just see if y is between edge endpoints - if Δx == 0 && x < xi && (yi ≥ yj ? yj ≤ y ≤ yi : yi ≤ y ≤ yj) - in_bounds = !in_bounds - #= - Edge is not vertical, find intersection point on y = y and see if it - is between edge endpoints. - =# - elseif Δx != 0 && Δy != 0 - m = Δy / Δx - b = yi - m * xi - x_inter = (y - b) / m - if (x_inter > x) && (xi ≥ xj ? xj < x_inter ≤ xi : xi ≤ x_inter < xj) - in_bounds = !in_bounds + v1 = GI.y(p_start) - y + v2 = GI.y(p_end) - y + if !((v1 < 0 && v2 < 0) || (v1 > 0 && v2 > 0)) # if not cases 11 or 26 + u1 = GI.x(p_start) - x + u2 = GI.x(p_end) - x + f = u1 * v2 - u2 * v1 + if v2 > 0 && v1 ≤ 0 # Case 3, 9, 16, 21, 13, or 24 + f == 0 && return on # Case 16 or 21 + f > 0 && (k += 1) # Case 3 or 9 + elseif v1 > 0 && v2 ≤ 0 # Case 4, 10, 19, 20, 12, or 25 + f == 0 && return on # Case 19 or 20 + f < 0 && (k += 1) # Case 4 or 10 + elseif v2 == 0 && v1 < 0 # Case 7, 14, or 17 + f == 0 && return on # Case 17 + elseif v1 == 0 && v2 < 0 # Case 8, 15, or 18 + f == 0 && return on # Case 18 + elseif v1 == 0 && v2 == 0 # Case 1, 2, 5, 6, 22, or 23 + u2 ≤ 0 && u1 ≥ 0 && return on # Case 1 + u1 ≤ 0 && u2 ≥ 0 && return on # Case 2 end end p_start = p_end end - return in_bounds, on_bounds + return iseven(k) ? out : in end -function _line_in_closed_curve(line, curve; close = false) +""" + line_in_closed_curve( + line, curve; + in::T = 1, on::T = -1, out::T = 0, + close = false, + ) + +Determine if line is in, on, or out of a closed curve. Both the line and curve +should be an object of linestring or linearring trait. The curve is assumed to +be closed, regardless of repeated last point. + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means line is within the curve (no segments on edges, vertices, or holes). +`On` means line has at least one segment on a curve edge or vertex. +`Out` means the line has at least one segment outside of the curve. + +This algorithm functions by checking if the first point of the line is within +the curve. If not, then the line is not within the curve, if so, we check for +intersections between the line and curve, as this would mean a part of the line +is outside of the curve. We take special care of intersections through vertices +as it isn't clearcut if those neccesitate a segment of the line being outside +of the curve. +""" +function _line_in_closed_curve( + line, curve; + in::T = 1, on::T = -1, out::T = 0, + close = false, +) where {T} # Determine number of points in curve and line nc = GI.npoint(curve) nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 nl = GI.npoint(line) nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 # Check to see if first point in line is within curve - point_in, point_on = point_in_polygon(GI.getpoint(line, 1), curve) - (point_in || point_on) || return (false, false) # point is outside curve + point_val = _point_in_closed_curve(GI.getpoint(line, 1), curve) + # point is outside curve, line can't be within curve + point_val == out && return out # Check for any intersections between line and curve - vertex_on = point_on - l_start_idx = close ? nl : 1 - l_range = close ? 1 : 2 - c_start = GI.getpoint(curve, nc) - for i in 1:nc - c_end = GI.getpoint(curve, i) - l_start = GI.getpoint(line, l_start_idx) - for j in l_range:nl - l_end = GI.getpoint(line, j) + line_on_curve = point_val == on # record if line is "on" part of curve + l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) + for i in (close ? 1 : 2):nl + l_end = _tuple_point(GI.getpoint(line, i)) + c_start = _tuple_point(GI.getpoint(curve, nc)) + for j in 1:nc + c_end = _tuple_point(GI.getpoint(curve, j)) # Check if edges intersect --> line is not within curve - _line_intersects( - (l_start, l_end), - (c_start, c_end), - ) && return (false, false) - # Check if either vertex is on the edge of the curve - if !vertex_on - vertex_on = point_on_segment(l_start, c_start, c_end) || - point_on_segment(l_end, c_start, c_end) + meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) + # open line segments meet in a single point + meet_type == 1 && return out + #= + closed line segments meet in one or several points -> meet at a + vertex or on the edge itself (parallel) + =# + if meet_type == 0 + line_on_curve = true + # See if segment is parallel and within curve edge + p1_on_seg = point_on_segment(l_start, c_start, c_end) + p2_on_seg = point_on_segment(l_end, c_start, c_end) + # if segment isn't contained within curve edge + if !p1_on_seg || !p2_on_seg + # Make sure l_start is in or on the segment + p1_in_curve = + p1_on_seg || + _point_in_closed_curve( + l_start, curve; + in = in, on = on, out = out, + ) != out + !p1_in_curve && return out + # Make sure l_end is in or on the segment + p2_in_curve = + p2_on_seg || + _point_in_closed_curve( + l_end, curve; + in = in, on = on, out = out, + ) != out + !p2_in_curve && return out + #= + If both endpoints are within or on the curve, but not + parallel to the edge, make sure that midpoints between the + intersections along the segment are within curve + =# + !_segment_mids_in_curve( + l_start, l_end, curve; + in = in, on = on, out = out, + ) && return out # point of segment is outside of curve + # line segment is fully within or on curve + break + end end - l_start = l_end + c_start = c_end end - c_start = c_end + l_start = l_end + end + # check if line is on any curve edges or vertcies + return line_on_curve ? on : in +end + +""" + _segment_mids_in_curve( + l_start, l_end, curve; + in::T = 1, on::T = -1, out::T = 0, + ) + + Given two points defining a line segment (both with point traits) and a + curve (with a linestring or linearring trait), find the intersection points + between them and sort them along the segment. Then, make sure that the + midpoint between pairs of points along the line is within the curve. Returns + true if all of the midpoints are within or on the curve and false otherwise. + + Note: This function assumes that both of the endpoints of the line segment + are on the curve! +""" +function _segment_mids_in_curve( + l_start, l_end, curve; + in::T = 1, on::T = -1, out::T = 0, +) where {T} + # Find intersection points + ipoints = intersection_points( + GI.Line([l_start, l_end]), + curve + ) + npoints = length(ipoints) + if npoints < 3 # only intersection points are the endpoints + mid_val = _point_in_closed_curve( + (l_start .+ l_end) ./ 2, curve; + in = in, on = on, out = out, + ) + mid_val == out && return false + else # more intersection points than the endpoints + # sort intersection points along the line + sort!(ipoints, by = p -> euclid_distance(p, l_start)) + p_start = ipoints[1] + for i in 2:npoints + p_end = ipoints[i] + # check if midpoint of intersection points is within the curve + mid_val = _point_in_closed_curve( + (p_start .+ p_end) ./ 2, curve; + in = in, on = on, out = out, + ) + # if it is out, return false + mid_val == out && return false + p_start = p_end + end + end + return true # all intersection point midpoints were in or on the curve +end + +function _geom_in_polygon(geom, poly; close = false) + # Cheaply check that the geom extent is inside the polygon extent + Extents.intersects(GI.extent(geom), GI.extent(poly)) || return (false, false) + # Check if geom is inside or on the exterior ring + in_ext, on_ext = _line_in_closed_curve( + geom, + GI.getexterior(poly); + close = close, + ) + (in_ext || on_ext) || return (false, false) # geom isn't in external ring + # Check if the geom is in any of the holes + for hole in GI.gethole(poly) + out_of_hole, some_on_hole = _line_in_closed_curve( + geom, hole; + close = close, in = false, + ) + # geom is in a hole -> not in polygon + !(out_of_hole || some_on_hole) && return (false, false) end - return (!vertex_on, vertex_on) + return (in_ext, some_on_hole) # geom is inside of polygon end """ diff --git a/src/try.jl b/src/try.jl index 4b8fa3901..e93825000 100644 --- a/src/try.jl +++ b/src/try.jl @@ -2,7 +2,11 @@ import GeometryOps as GO import GeoInterface as GI import LibGEOS as LG -diamond = LG.Polygon([[ - [0.0, 0.0], [-5.0, 5.0], [0.0, 10.0], [5.0, 5.0], [0.0, 0.0], -]]) -GO.point_in_polygon((-2.5, 2.5), diamond) == on_geom \ No newline at end of file +closed_string = LG.LineString([ + [0.0, 0.0], [1.0, 1.0], [3.0, 1.25], [2.0, 3.0], [-1.0, 2.75], [0.0, 0.0] +]) +GO.line_in_geom( + LG.LineString([[0.0, 0.0], [3.0, 1.25]]), + closed_string +) == not_in_on_geom + \ No newline at end of file diff --git a/test/methods/geom_in_geom.jl b/test/methods/geom_in_geom.jl index ad35266cf..8300b734a 100644 --- a/test/methods/geom_in_geom.jl +++ b/test/methods/geom_in_geom.jl @@ -1,63 +1,174 @@ -const in_geom = (true, false) -const on_geom = (false, true) -const not_in_on_geom = (false, false) +const in_geom = 1#(true, false) +const on_geom = -1#(false, true) +const not_in_on_geom = 0#(false, false) + +warn_msg = "Linestring isn't closed. Point cannot be 'in' linestring." +open_string = LG.LineString([ + [0.0, 0.0], [1.0, 1.0], [3.0, 1.25], [2.0, 3.0], [-1.0, 2.75] +]) +closed_string = LG.LineString([ + [0.0, 0.0], [1.0, 1.0], [3.0, 1.25], [2.0, 3.0], [-1.0, 2.75], [0.0, 0.0] +]) +rect_ring = LG.LinearRing([ + [-20.0, 0.0], [-20.0, -10.0], [-5.0, -10.0], [-5.0, 0.0], [-20.0, 0.0] +]) +tri_ring = LG.LinearRing([ + [5.0, 0.0], [7.0, 1.995], [9.0, -1.0], [5.0, 0.0] +]) +concave_out_spikes = LG.LinearRing([ + [0.0, 0.0], [0.0, 10.0], [20.0, 10.0], [20.0, 0.0], [15.0, -5.0], + [10.0, 0.0], [5.0, -5.0], [0.0, 0.0], +]) +concave_in_spikes = LG.LinearRing([ + [0.0, 0.0], [0.0, 10.0], [20.0, 10.0], [20.0, 0.0], [15.0, 5.0], + [10.0, 0.0], [5.0, 5.0], [0.0, 0.0], +]) +rect_poly = LG.Polygon([[ + [0.0, 0.0], [0.0, 10.0], [10.0, 10.0], [10.0, 0.0], [0.0, 0.0] +]]) +diamond_poly = LG.Polygon([[ + [0.0, 0.0], [-5.0, 5.0], [0.0, 10.0], [5.0, 5.0], [0.0, 0.0], +]]) +trap_with_hole = LG.Polygon([ + [[-10.0, 0.0], [-8.0, 5.0], [8.0, 5.0], [10.0, 0.0], [-10.0, 0.0]], + [[-5.0, 2.0], [-5.0, 4.0], [-2.0, 4.0], [-2.0, 2.0], [-5.0, 2.0]] +]) +concave_a = GI.Polygon([[ + (1.2938349167338743, -3.175128530227131), + (-2.073885870841754, -1.6247711001754137), + (-5.787437985975053, 0.06570713422599561), + (-2.1308128111898093, 5.426689675486368), + (2.3058074184797244, 6.926652158268195), + (1.2938349167338743, -3.175128530227131), +]]) +concave_b = GI.Polygon([[ + (-2.1902469793743924, -1.9576242117579579), + (-4.726006206053999, 1.3907098941556428), + (-3.165301985923147, 2.847612825874245), + (-2.5529280962099428, 4.395492123980911), + (0.5677700216973937, 6.344638314896882), + (3.982554842356183, 4.853519613487035), + (5.251193948893394, 0.9343031382106848), + (5.53045582244555, -3.0101433691361734), + (-2.1902469793743924, -1.9576242117579579), +]]) @testset "Point in Geom" begin + # Line Strings + @test (@test_logs (:warn, warn_msg) GO.point_in_geom((-12.0, -0.5), open_string)) == not_in_on_geom + + @test GO.point_in_geom((0.5, 0.5), closed_string) == on_geom + @test GO.point_in_geom((2.0, 1.25), closed_string) == in_geom + @test GO.point_in_geom((4.0, 0.0), closed_string) == not_in_on_geom + + # Linear Rings + @test GO.point_in_geom((-12.0, -0.5), rect_ring) == in_geom + @test GO.point_in_geom((20.0, 0.0), rect_ring) == not_in_on_geom + @test GO.point_in_geom((-5.0, -10.0), rect_ring) == on_geom + + @test GO.point_in_geom((6.0, 0.0), tri_ring) == in_geom + @test GO.point_in_geom((6.0, -0.25), tri_ring) == on_geom + @test GO.point_in_geom((7.0, 2.0), tri_ring) == not_in_on_geom + # Convex polygons - rect = LG.Polygon([[ - [0.0, 0.0], [0.0, 10.0], [10.0, 10.0], [10.0, 0.0], [0.0, 0.0] - ]]) - @test GO.point_in_polygon((0.0, 0.0), rect) == on_geom - @test GO.point_in_polygon((0.0, 5.0), rect) == on_geom - @test GO.point_in_polygon((5.0, 10.0), rect) == on_geom - @test GO.point_in_polygon((2.5, 2.5), rect) == in_geom - @test GO.point_in_polygon((9.99, 9.99), rect) == in_geom - @test GO.point_in_polygon((20.0, 20.0), rect) == not_in_on_geom - - diamond = LG.Polygon([[ - [0.0, 0.0], [-5.0, 5.0], [0.0, 10.0], [5.0, 5.0], [0.0, 0.0], - ]]) - @test GO.point_in_polygon((0.0, 0.0), diamond) == on_geom - @test GO.point_in_polygon((-2.5, 2.5), diamond) == on_geom - @test GO.point_in_polygon((2.5, 2.5), diamond) == on_geom - @test GO.point_in_polygon((0.0, 5.0), diamond) == in_geom - @test GO.point_in_polygon((4.99, 5.0), diamond) == in_geom - @test GO.point_in_polygon((20.0, 20.0), diamond) == not_in_on_geom - - trap_with_hole = LG.Polygon([ - [[-10.0, 0.0], [-8.0, 5.0], [8.0, 5.0], [10.0, 0.0], [-10.0, 0.0]], - [[-5.0, 2.0], [-5.0, 4.0], [-2.0, 4.0], [-2.0, 2.0], [-5.0, 2.0]] - ]) + @test GO.point_in_polygon((0.0, 0.0), rect_poly) == on_geom + @test GO.point_in_polygon((0.0, 5.0), rect_poly) == on_geom + @test GO.point_in_polygon((5.0, 10.0), rect_poly) == on_geom + @test GO.point_in_polygon((2.5, 2.5), rect_poly) == in_geom + @test GO.point_in_polygon((9.99, 9.99), rect_poly) == in_geom + @test GO.point_in_polygon((20.0, 20.0), rect_poly) == not_in_on_geom + + @test GO.point_in_polygon((0.0, 0.0), diamond_poly) == on_geom + @test GO.point_in_polygon((-2.5, 2.5), diamond_poly) == on_geom + @test GO.point_in_polygon((2.5, 2.5), diamond_poly) == on_geom + @test GO.point_in_polygon((0.0, 5.0), diamond_poly) == in_geom + @test GO.point_in_polygon((4.99, 5.0), diamond_poly) == in_geom + @test GO.point_in_polygon((20.0, 20.0), diamond_poly) == not_in_on_geom + @test GO.point_in_polygon((-10.0, 0.0), trap_with_hole) == on_geom @test GO.point_in_polygon((-5.0, 2.0), trap_with_hole) == on_geom @test GO.point_in_polygon((-5.0, 3.0), trap_with_hole) == on_geom @test GO.point_in_polygon((-9.0, 0.01), trap_with_hole) == in_geom @test GO.point_in_polygon((-4.0, 3.0), trap_with_hole) == not_in_on_geom @test GO.point_in_polygon((20.0, 20.0), trap_with_hole) == not_in_on_geom + # Concave polygons - concave_a = GI.Polygon([[ - (1.2938349167338743, -3.175128530227131), - (-2.073885870841754, -1.6247711001754137), - (-5.787437985975053, 0.06570713422599561), - (-2.1308128111898093, 5.426689675486368), - (2.3058074184797244, 6.926652158268195), - (1.2938349167338743, -3.175128530227131), - ]]) - concave_b = GI.Polygon([[ - (-2.1902469793743924, -1.9576242117579579), - (-4.726006206053999, 1.3907098941556428), - (-3.165301985923147, 2.847612825874245), - (-2.5529280962099428, 4.395492123980911), - (0.5677700216973937, 6.344638314896882), - (3.982554842356183, 4.853519613487035), - (5.251193948893394, 0.9343031382106848), - (5.53045582244555, -3.0101433691361734), - (-2.1902469793743924, -1.9576242117579579), - ]]) pt = (-2.1902469793743924, -1.9576242117579579) - @test GO.point_in_polygon(pt, concave_a) == (false, false) - @test GO.point_in_polygon(pt, concave_b) == (false, true) - @test GO.point_in_polygon((0.0, 0.0), concave_a) == (true, false) - @test GO.point_in_polygon((0.0, 0.0), concave_b) == (true, false) + @test GO.point_in_polygon(pt, concave_a) == not_in_on_geom + @test GO.point_in_polygon(pt, concave_b) == on_geom + @test GO.point_in_polygon((0.0, 0.0), concave_a) == in_geom + @test GO.point_in_polygon((0.0, 0.0), concave_b) == in_geom +end + +@testset "Line in Geom" begin + # Line Strings + @test (@test_logs (:warn, warn_msg) GO.line_in_geom( + LG.LineString([[0.0, 0.0], [0.0, 1.0]]), + open_string, + )) == not_in_on_geom + + # On the edge + @test GO.line_in_geom( + LG.LineString([[0.25, 0.25], [0.5, 0.5]]), + closed_string + ) == on_geom + # Inside + @test GO.line_in_geom( + LG.LineString([[1.0, 1.25], [2, 1.3], [2.9, 1.25]]), + closed_string + ) == in_geom + # Inside to outside + @test GO.line_in_geom( + LG.LineString([[1.0, 0.99], [2, 1.3], [2.9, 1.25]]), + closed_string + ) == not_in_on_geom + # Outside of geom + @test GO.line_in_geom( + LG.LineString([[0.0, 0.0], [3.0, 1.25]]), + closed_string + ) == not_in_on_geom + + # Rings + # Same geometry, sharing all edges + @test GO.line_in_geom( + LG.LineString([[-20.0, 0.0], [-20.0, -10.0], [-5.0, -10.0], [-5.0, 0.0], [-20.0, 0.0]]), + rect_ring + ) == on_geom + + # Same geometry, sharing all but last edge, which is inside + @test GO.line_in_geom( + LG.LineString([[-20.0, 0.0], [-20.0, -10.0], [-5.0, -10.0], [-5.0, 0.0], [-15.0, -5.0]]), + rect_ring + ) == on_geom + # Within + @test GO.line_in_geom( + LG.LineString([[-10.0, -1.0], [-10.0, -9.0], [-7.0, -9.0], [-7.0, -1.0], [-10.0, -1.0]]), + rect_ring + ) == in_geom + # Passing in to out + @test GO.line_in_geom( + LG.LineString([[-20.0, 0.0], [-20.0, -10.0], [-5.0, -10.0], [-5.0, 0.0], [-30.0, 0.0]]), + rect_ring + ) == not_in_on_geom + + horizontal_line = LG.LineString([[0.0, 0.0], [20.0, 0.0]]) + @test GO.line_in_geom(horizontal_line, concave_out_spikes) == on_geom + @test GO.line_in_geom(horizontal_line, concave_in_spikes) == not_in_on_geom + @test GO.line_in_geom( + LG.LineString([[10.0, 0.0], [10.0, -0.0001]]), + concave_out_spikes + ) == not_in_on_geom + + # Polygons + + +end + +@testset "Ring in Geom" begin + +end + +@testset "Polygon in Geom" begin + end \ No newline at end of file From e7e5df7a945badfd6780be5a331f860ef5aea323 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Thu, 9 Nov 2023 13:53:36 -0800 Subject: [PATCH 14/33] Generalized line in curve --- src/methods/disjoint.jl | 12 ++ .../{geom_in_geom.jl => geom_in_out_geom.jl} | 130 +++++++++++++++++- src/methods/within.jl | 3 +- 3 files changed, 143 insertions(+), 2 deletions(-) rename src/methods/{geom_in_geom.jl => geom_in_out_geom.jl} (80%) diff --git a/src/methods/disjoint.jl b/src/methods/disjoint.jl index 02b7d4e46..97a7fd1c1 100644 --- a/src/methods/disjoint.jl +++ b/src/methods/disjoint.jl @@ -40,3 +40,15 @@ function polygon_disjoint(poly1, poly2) end return !intersects(poly1, poly2) end + +_line_disjoint_closed_curve( + line, curve; + exclude_boundaries = false, + close = false, +) = _line_orient_closed_curve( + line, curve; + disjoint = true, + exclude_boundaries = exclude_boundaries, + close = close, +) + diff --git a/src/methods/geom_in_geom.jl b/src/methods/geom_in_out_geom.jl similarity index 80% rename from src/methods/geom_in_geom.jl rename to src/methods/geom_in_out_geom.jl index 78be3a991..f1ba4b919 100644 --- a/src/methods/geom_in_geom.jl +++ b/src/methods/geom_in_out_geom.jl @@ -452,7 +452,10 @@ function _line_in_closed_curve( nl = GI.npoint(line) nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 # Check to see if first point in line is within curve - point_val = _point_in_closed_curve(GI.getpoint(line, 1), curve) + point_val = _point_in_closed_curve( + GI.getpoint(line, 1), curve; + in = in, on = on, out = out, + ) # point is outside curve, line can't be within curve point_val == out && return out # Check for any intersections between line and curve @@ -596,3 +599,128 @@ function _point_in_extent(p, extent::Extents.Extent) (x1, x2), (y1, y2) = extent.X, extent.Y return x1 ≤ GI.x(p) ≤ x2 && y1 ≤ GI.y(p) ≤ y2 end + +function _line_in_out_closed_curve( + line, curve; + disjoint = false, + exclude_boundaries = false, + close = false, +) + #= + Set variables based off if we are determining within or disjoint. + If `_point_in_closed_curve` returns `true_orientation` it is on the right + side of the curve for the check. If it returns `false_orientation`, it is + on the wrong side of the curve for the check. + =# + false_orientation = disjoint ? 1 : 0 # if checking within, want points in + on = -1 # as used for point in closed curve + + # Determine number of points in curve and line + nc = GI.npoint(curve) + nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 + nl = GI.npoint(line) + nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 + + # Check to see if first point in line is within curve + point_val = _point_in_closed_curve(GI.getpoint(line, 1), curve) + # point is out (for within) or in curve (for disjoint) -> wrong orientation + point_val == false_orientation && return false + # point is on boundary and don't want boundary points -> wrong orientation + exclude_boundaries && point_val == on && return false + + # Check for any intersections between line and curve + l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) + for i in (close ? 1 : 2):nl + l_end = _tuple_point(GI.getpoint(line, i)) + c_start = _tuple_point(GI.getpoint(curve, nc)) + for j in 1:nc + c_end = _tuple_point(GI.getpoint(curve, j)) + # Check if edges intersect --> line crosses --> wrong orientation + meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) + # open line segments meet in a single point + meet_type == 1 && return false + #= + closed line segments meet in one or several points -> meet at a + vertex or on the edge itself (parallel) + =# + if meet_type == 0 + # See if segment is parallel and within curve edge + p1_on_seg = point_on_segment(l_start, c_start, c_end) + exclude_boundaries && p1_on_seg && return false + p2_on_seg = point_on_segment(l_end, c_start, c_end) + exclude_boundaries && p2_on_seg && return false + # if segment isn't contained within curve edge + if !p1_on_seg || !p2_on_seg + # Make sure l_start is in corrent orientation + p1_val = p1_on_seg ? + on : + _point_in_closed_curve(l_start, curve) + p1_val == false_orientation && return false + exclude_boundaries && p1_val == on && return false + # Make sure l_end is in is in corrent orientation + p2_val = p2_on_seg ? + on : + _point_in_closed_curve(l_end, curve) + p2_val == false_orientation && return false + exclude_boundaries && p2_val == on && return false + #= + If both endpoints are in the correct orientation, but not + parallel to the edge, make sure that midpoints between the + intersections along the segment are also in the correct + orientation + =# + !_segment_mids_in_out_curve( + l_start, l_end, curve; + disjoint = disjoint, + exclude_boundaries = exclude_boundaries, + ) && return false # midpoint on the wrong side of the curve + # line segment is fully within or on curve + break + end + end + c_start = c_end + end + l_start = l_end + end + # check if line is on any curve edges or vertcies + return true +end + +function _segment_mids_in_out_curve( + l_start, l_end, curve; + disjoint = false, + exclude_boundaries = false, +) + false_orientation = disjoint ? 1 : 0 # if checking within, want points in + on = -1 # as used for point in closed curve + # Find intersection points + ipoints = intersection_points( + GI.Line([l_start, l_end]), + curve + ) + npoints = length(ipoints) + if npoints < 3 # only intersection points are the endpoints + mid_val = _point_in_closed_curve( + (l_start .+ l_end) ./ 2, curve; + in = in, on = on, out = out, + ) + mid_val == false_orientation && return false + exclude_boundaries && mid_val == on && return false + else # more intersection points than the endpoints + # sort intersection points along the line + sort!(ipoints, by = p -> euclid_distance(p, l_start)) + p_start = ipoints[1] + for i in 2:npoints + p_end = ipoints[i] + # check if midpoint of intersection points is within the curve + mid_val = _point_in_closed_curve( + (p_start .+ p_end) ./ 2, curve; + in = in, on = on, out = out, + ) + # if it is out, return false + mid_val == false_orientation && return false + exclude_boundaries && mid_val == on && return false + end + end + return true # all intersection point midpoints were in or on the curve +end diff --git a/src/methods/within.jl b/src/methods/within.jl index 804bde688..55d8940b9 100644 --- a/src/methods/within.jl +++ b/src/methods/within.jl @@ -40,4 +40,5 @@ within(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool = polygon_in_polygon( # Everything not specified # TODO: Add multipolygons -within(::GI.AbstractTrait, g1, ::GI.AbstractCurveTrait, g2)::Bool = false \ No newline at end of file +within(::GI.AbstractTrait, g1, ::GI.AbstractCurveTrait, g2)::Bool = false + From 0aa15297b2d83ecb1f6a824f8f4bd9a3f1680b68 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 28 Nov 2023 12:55:33 -0800 Subject: [PATCH 15/33] Working line line processor --- src/GeometryOps.jl | 2 +- src/methods/covers.jl | 0 src/methods/disjoint.jl | 28 +- src/methods/geom_geom_processors.jl | 551 ++++++++++++++++++++++++++++ src/methods/geom_in_out_geom.jl | 175 ++++----- src/methods/overlaps.jl | 13 +- src/methods/touches.jl | 0 src/methods/within.jl | 112 +++++- src/try.jl | 12 +- test/methods/contains.jl | 31 ++ test/methods/covers.jl | 25 ++ test/methods/disjoint.jl | 56 +++ test/methods/overlaps.jl | 6 + test/methods/touches.jl | 25 ++ test/methods/within.jl | 105 ++++++ test/runtests.jl | 7 +- 16 files changed, 1040 insertions(+), 108 deletions(-) create mode 100644 src/methods/covers.jl create mode 100644 src/methods/geom_geom_processors.jl create mode 100644 src/methods/touches.jl create mode 100644 test/methods/contains.jl create mode 100644 test/methods/covers.jl create mode 100644 test/methods/disjoint.jl create mode 100644 test/methods/touches.jl create mode 100644 test/methods/within.jl diff --git a/src/GeometryOps.jl b/src/GeometryOps.jl index 2fa33f1d5..1df0a4156 100644 --- a/src/GeometryOps.jl +++ b/src/GeometryOps.jl @@ -32,7 +32,7 @@ include("methods/within.jl") include("methods/polygonize.jl") include("methods/barycentric.jl") include("methods/equals.jl") -include("methods/geom_in_geom.jl") +include("methods/geom_geom_processors.jl") include("methods/geom_on_geom.jl") include("methods/orientation.jl") diff --git a/src/methods/covers.jl b/src/methods/covers.jl new file mode 100644 index 000000000..e69de29bb diff --git a/src/methods/disjoint.jl b/src/methods/disjoint.jl index 97a7fd1c1..10d2b195e 100644 --- a/src/methods/disjoint.jl +++ b/src/methods/disjoint.jl @@ -18,12 +18,34 @@ GO.disjoint(poly, point) true ``` """ +# Syntactic sugar disjoint(g1, g2)::Bool = disjoint(trait(g1), g1, trait(g2), g2) disjoint(::FeatureTrait, g1, ::Any, g2)::Bool = disjoint(GI.geometry(g1), g2) disjoint(::Any, g1, t2::FeatureTrait, g2)::Bool = disjoint(g1, geometry(g2)) -disjoint(::PointTrait, g1, ::PointTrait, g2)::Bool = !point_equals_point(g1, g2) -disjoint(::PointTrait, g1, ::LineStringTrait, g2)::Bool = !point_on_line(g1, g2) -disjoint(::PointTrait, g1, ::PolygonTrait, g2)::Bool = !point_in_polygon(g1, g2) +# Point disjoint geometries +# Point disjoint from point +disjoint( + ::GI.PointTrait, g1, + ::GI.PointTrait, g2, +) = !equals(g1, g2) +# Point disjoint from curve +disjoint( + ::GI.PointTrait, g1, + ::Union{GI.LineStringTrait, GI.LinearRingTrait}, g2, +) = _point_curve_process( + g1, g2; + process = disjoint_process, exclude_boundaries = false, +) +# Point in polygon +disjoint( + ::GI.PointTrait, g1, + ::GI.PolygonTrait, g2, +) = _point_polygon_process( + g1, g2; + process = disjoint_process, exclude_boundaries = false, +) + + disjoint(::LineStringTrait, g1, ::PointTrait, g2)::Bool = !point_on_line(g2, g1) disjoint(::LineStringTrait, g1, ::LineStringTrait, g2)::Bool = !line_on_line(g1, g2) disjoint(::LineStringTrait, g1, ::PolygonTrait, g2)::Bool = !line_in_polygon(g2, g1) diff --git a/src/methods/geom_geom_processors.jl b/src/methods/geom_geom_processors.jl new file mode 100644 index 000000000..cad7132b4 --- /dev/null +++ b/src/methods/geom_geom_processors.jl @@ -0,0 +1,551 @@ +@enum ProcessType within_process=1 disjoint_process=2 + + +get_process_type_vals(process::ProcessType, exclude_boundaries = false) = + ( + process == within_process, + process == disjoint_process, + process == within_process ? + !exclude_boundaries : exclude_boundaries, + ) + + +function _point_curve_process( + point, curve; + process::ProcessType = within_process, + exclude_boundaries = false, + repeated_last_coord = false, +) + n = GI.npoint(curve) + first_last_equal = equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) + repeated_last_coord |= first_last_equal + exclude_boundaries |= first_last_equal + n -= first_last_equal ? 1 : 0 + in_val, out_val, on_val = get_process_type_vals(process, exclude_boundaries) + + p_start = GI.getpoint(curve, repeated_last_coord ? n : 1) + @inbounds for i in (repeated_last_coord ? 1 : 2):n + p_end = GI.getpoint(curve, i) + seg_val = _point_in_on_out_segment(point, p_start, p_end) + seg_val == 1 && return in_val + if seg_val == -1 + i == 2 && equals(point, p_start) && return on_val + i == n && equals(point, p_end) && return on_val + return in_val + end + p_start = p_end + end + return out_val +end + +function _point_in_on_out_segment( + point, start, stop; + in::T = 1, on::T = -1, out::T = 0, +) where {T} + # 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) + Δx_seg = x2 - x1 + Δy_seg = y2 - y1 + Δx_pt = x - x1 + Δy_pt = y - y1 + if (Δx_pt == 0 && Δy_pt == 0) || (Δx_pt == Δx_seg && Δy_pt == Δy_seg) + # If point is equal to the segment start or end points + return on + else + #= + Determine if the point is on the segment -> see if vector from segment + start to point is parallel to segment and if point is between the + segment endpoints + =# + on_line = _isparallel(Δx_seg, Δy_seg, Δx_pt, Δy_pt) + !on_line && return out + between_endpoints = + (x2 > x1 ? x1 <= x <= x2 : x2 <= x <= x1) && + (y2 > y1 ? y1 <= y <= y2 : y2 <= y <= y1) + !between_endpoints && return out + end + return in +end + +point_return_val(point_val, in_val, out_val, on_val) = + point_val == 1 ? + in_val : # point is inside of polygon + (point_val == 0 ? + out_val : # point is outside of polygon + on_val # point is on the edge of polygon + ) + +""" + _point_closed_curve_process( + point, curve; + process::ProcessType = within_process, + exclude_boundary = false, + ) + +Determine if point is in, on, or out of a closed curve. Point should be an +object of Point trait and curve should be a linearstring or ring, that is +assumed to be closed, regardless of repeated last point. + +Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +`In` means the point is within the closed curve (excluding edges and vertices). +`On` means the point is on an edge or a vertex of the closed curve. +`Out` means the point is outside of the closed curve. + +Note that this uses the Algorithm by Hao and Sun (2018): +https://doi.org/10.3390/sym10100477 +Paper seperates orientation of point and edge into 26 cases. For each case, it +is either a case where the point is on the edge (returns on), where a ray from +the point (x, y) to infinity along the line y = y cut through the edge (k += 1), +or the ray does not pass through the edge (do nothing and continue). If the ray +passes through an odd number of edges, it is within the curve, else outside of +of the curve if it didn't return 'on'. +See paper for more information on cases denoted in comments. +""" +function _point_closed_curve_process( + point, curve; + process::ProcessType = within_process, + exclude_boundaries = false, +) + in_val, out_val, on_val = get_process_type_vals(process, exclude_boundaries) + point_val = _point_in_on_out_closed_curve(point, curve) + return point_return_val(point_val, in_val, out_val, on_val) +end + +function _point_in_on_out_closed_curve( + point, curve; + in::T = 1, on::T = -1, out::T = 0, +) where {T} + x, y = GI.x(point), GI.y(point) + n = GI.npoint(curve) + n -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) ? 1 : 0 + k = 0 # counter for ray crossings + p_start = GI.getpoint(curve, n) + @inbounds for i in 1:n + p_end = GI.getpoint(curve, i) + v1 = GI.y(p_start) - y + v2 = GI.y(p_end) - y + if !((v1 < 0 && v2 < 0) || (v1 > 0 && v2 > 0)) # if not cases 11 or 26 + u1 = GI.x(p_start) - x + u2 = GI.x(p_end) - x + f = u1 * v2 - u2 * v1 + if v2 > 0 && v1 ≤ 0 # Case 3, 9, 16, 21, 13, or 24 + f == 0 && return on # Case 16 or 21 + f > 0 && (k += 1) # Case 3 or 9 + elseif v1 > 0 && v2 ≤ 0 # Case 4, 10, 19, 20, 12, or 25 + f == 0 && return on # Case 19 or 20 + f < 0 && (k += 1) # Case 4 or 10 + elseif v2 == 0 && v1 < 0 # Case 7, 14, or 17 + f == 0 && return on # Case 17 + elseif v1 == 0 && v2 < 0 # Case 8, 15, or 18 + f == 0 && return on # Case 18 + elseif v1 == 0 && v2 == 0 # Case 1, 2, 5, 6, 22, or 23 + u2 ≤ 0 && u1 ≥ 0 && return on # Case 1 + u1 ≤ 0 && u2 ≥ 0 && return on # Case 2 + end + end + p_start = p_end + end + return iseven(k) ? out : in +end + +_point_polygon_process( + point, polygon; + process::ProcessType = within_process, + exclude_boundaries = false, +) = _geom_polygon_process( + point, polygon, + _point_closed_curve_process; + process = process, + ext_exclude_boundaries = exclude_boundaries, + hole_exclude_boundaries = !exclude_boundaries +) + +function _line_curve_process( + line, curve; + process::ProcessType = within_process, + exclude_boundaries = false, + closed_line = false, + closed_curve = false, +) + nl = GI.npoint(line) + nc = GI.npoint(curve) + explicit_closed_line = equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) + explicit_closed_curve = equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) + nl -= explicit_closed_line ? 1 : 0 + nc -= explicit_closed_curve ? 1 : 0 + closed_line |= explicit_closed_line + closed_curve |= explicit_closed_curve + exclude_boundaries |= explicit_closed_curve + + l_start = _tuple_point(GI.getpoint(line, closed_line ? nl : 1)) + i = closed_line ? 1 : 2 + while i ≤ nl + l_end = _tuple_point(GI.getpoint(line, i)) + c_start = _tuple_point(GI.getpoint(curve, closed_curve ? nc : 1)) + for j in (closed_curve ? 1 : 2):nc + c_end = _tuple_point(GI.getpoint(curve, j)) + # Check if edges intersect -> not disjoint + meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) + if process == within_process + #= + if l_start is in/on curve and curve and line meet either at + endpoints or are parallel and meet in multiple points + =# + if ( + meet_type == 0 && + _point_in_on_out_segment(l_start, c_start, c_end) != 0 + ) + # if l_end is within curve, whole line is contained in curve + if _point_in_on_out_segment( + l_end, + c_start, c_end, + ) != 0 + i += 1 + l_start = l_end + break + #= + if c_start is in line, then need to find overlap for c_start + to l_end as l_start to c_start is overlapping with curve + =# + elseif _point_in_on_out_segment( + c_start, + l_start, l_end, + ) == 1 + l_start = c_start + break + #= + if c_end is in line, then need to find overlap for c_end to + l_end as l_start to c_end is overlapping with curve + =# + elseif _point_in_on_out_segment( + c_end, + l_start, l_end, + ) == 1 + l_start = c_end + break + end + end + j == nc && return false + else # disjoint_process + if ( + exclude_boundaries && meet_type == 0 && + !closed_curve && (j == 2 || j == nc) + ) + if _isparallel(l_start, l_end, c_start, c_end) + (p1, p2) = + if j == 2 && equals(c_start, l_start) + (l_end, c_end) + elseif j == 2 && equals(c_start, l_end) + (l_start, c_end) + elseif j == nc && equals(c_end, l_start) + (l_end, c_start) + elseif j == nc &&equals(c_end, l_end) + (l_start, c_start) + else + return false + end + ( + _point_in_on_out_segment(p1, c_start, c_end) || + _point_in_on_out_segment(p2, l_start, l_end) + ) && return false + else + _, (_, c_frac) = _intersection_point( + (l_start, l_end), + (c_start, c_end), + ) + j == 2 && c_frac != 0 && return false + j == nc && c_frac != 1 && return false + end + elseif meet_type != -1 + return false + end + if j == nc + i += 1 + l_start = l_end + end + end + c_start = c_end + end + end + return true +end + +function _line_curve_disjoint_checks( + meet_type, + l_start, l_end, c_start, c_end, + j, + nc, + closed_curve, + exclude_boundaries, +) + if ( + exclude_boundaries && meet_type == 0 && + !closed_curve && (j == 2 || j == nc) + ) + if _isparallel(l_start, l_end, c_start, c_end) + (p1, p2) = + if j == 2 && equals(c_start, l_start) + (l_end, c_end) + elseif j == 2 && equals(c_start, l_end) + (l_start, c_end) + elseif j == nc && equals(c_end, l_start) + (l_end, c_start) + elseif j == nc &&equals(c_end, l_end) + (l_start, c_start) + else + return false + end + ( + _point_in_on_out_segment(p1, c_start, c_end) || + _point_in_on_out_segment(p2, l_start, l_end) + ) && return false + else + _, (_, c_frac) = _intersection_point( + (l_start, l_end), + (c_start, c_end), + ) + j == 2 && c_frac != 0 && return false + j == nc && c_frac != 1 && return false + end + elseif meet_type != -1 + return false + end + if j == nc + i += 1 + l_start = l_end + end +end + +function _line_curve_within_checks( + line, curve, + nl, nc, + closed_line, closed_curve, + exclude_boundaries, +) + seg_idx = 0 + l_start = _tuple_point(GI.getpoint(line, closed_line ? nl : 1)) + c_start = _tuple_point(GI.getpoint(curve, closed_curve ? nc : 1)) + for i in (closed_curve ? 1 : 2):nc + c_end = _tuple_point(GI.getpoint(curve, i)) + c_start_val = _point_in_on_out_segment(l_start, c_start, c_end) + if c_start_val == -1 && exclude_boundaries && (i == 2 || i == nc) + i == 2 && equals(l_start, c_start) && return false + i == nc && equals(l_start, c_end) && return false + end + if c_start_val != 0 # if point is in or on the segment + seg_idx = i + break + end + c_start = c_end + end + seg_idx == 0 && return false + + for i in (closed_line ? 1 : 2):nl + l_end = _tuple_point(GI.getpoint(line, i)) + line_on_next_seg = true + while line_on_next_seg + seg_idx > nc && return false + c_end = _tuple_point(GI.getpoint(curve, seg_idx)) + meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) + meet_type != 0 && return false + end_point_val = _point_in_on_out_segment(l_end, c_start, c_end) + end_point_val == 1 && break + end_point_val == -1 && (line_on_next_seg = false) + # Switch to next segment + seg_idx += 1 + c_start = c_end + end + l_start = l_end + end + return true +end + +function _line_closed_curve_process( + line, curve; + process::ProcessType = within_process, + exclude_boundaries = false, + close = false, +) + point_in = false # see if at least one point is within the closed curve + in_val, out_val, on_val = get_process_type_vals(process, exclude_boundaries) + # Determine number of points in curve and line + nc = GI.npoint(curve) + nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 + nl = GI.npoint(line) + nl -= equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) ? 1 : 0 + + # Check to see if first point in line is within curve + l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) + point_val = _point_in_on_out_closed_curve(l_start, curve) + point_in |= point_val == 1 # check if point is within closed curve + point_return = point_return_val(point_val, in_val, out_val, on_val) + + # point is not in correct orientation to curve given process and boundary + !point_return && return point_return + + # Check for any intersections between line and curve + for i in (close ? 1 : 2):nl + l_end = _tuple_point(GI.getpoint(line, i)) + c_start = _tuple_point(GI.getpoint(curve, nc)) + for j in 1:nc + c_end = _tuple_point(GI.getpoint(curve, j)) + # Check if edges intersect --> line crosses --> wrong orientation + meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) + # open line segments meet in a single point + meet_type == 1 && return false + #= + closed line segments meet in one or several points -> meet at a + vertex or on the edge itself (parallel) + =# + if meet_type == 0 + # See if segment is parallel and within curve edge + p1_on_seg = point_on_segment(l_start, c_start, c_end) + exclude_boundaries && p1_on_seg && return false + p2_on_seg = point_on_segment(l_end, c_start, c_end) + exclude_boundaries && p2_on_seg && return false + # if segment isn't contained within curve edge + if !p1_on_seg || !p2_on_seg + # Make sure l_start is in corrent orientation + if !p1_on_seg + p1_val = _point_in_on_out_closed_curve(l_start, curve) + # check if point is within closed curve + point_in |= p1_on_seg == 1 + !point_return_val(p1_val, in_val, out_val, on_val) && + return false + end + # Make sure l_end is in is in corrent orientation + if !p2_on_seg + p2_val = _point_in_on_out_closed_curve(l_end, curve) + # check if point is within closed curve + point_in |= p2_val == 1 + !point_return_val(p2_val, in_val, out_val, on_val) && + return false + end + #= + If both endpoints are in the correct orientation, but not + parallel to the edge, make sure that midpoints between the + intersections along the segment are also in the correct + orientation + =# + mid_vals, mid_in = _segment_mids_closed_curve_process( + l_start, l_end, curve; + process = process, + exclude_boundaries = exclude_boundaries, + ) + point_in |= mid_in + # midpoint on the wrong side of the curve + !mid_vals && return false + # line segment is fully within or on curve + break + end + end + c_start = c_end + end + l_start = l_end + end + # check if line is on any curve edges or vertcies + return process == within_process ? point_in : true +end + +function _segment_mids_closed_curve_process( + l_start, l_end, curve; + process::ProcessType = within_process, + exclude_boundaries = false, +) + point_in = false + in_val, out_val, on_val = get_process_type_vals(process, exclude_boundaries) + # Find intersection points + ipoints = intersection_points( + GI.Line([l_start, l_end]), + curve + ) + npoints = length(ipoints) + if npoints < 3 # only intersection points are the endpoints + mid_val = _point_in_on_out_closed_curve((l_start .+ l_end) ./ 2, curve) + point_in |= mid_val == 1 + mid_return = point_return_val(mid_val, in_val, out_val, on_val) + !mid_return && return mid_return + else # more intersection points than the endpoints + # sort intersection points along the line + sort!(ipoints, by = p -> euclid_distance(p, l_start)) + p_start = ipoints[1] + for i in 2:npoints + p_end = ipoints[i] + # check if midpoint of intersection points is within the curve + mid_val = _point_in_on_out_closed_curve( + (p_start .+ p_end) ./ 2, + curve, + ) + point_in |= mid_val == 1 + mid_return = point_return_val(mid_val, in_val, out_val, on_val) + !mid_return && return mid_val + end + end + # all intersection point midpoints were in or on the curve + return true, point_in +end + +_line_polygon_process( + line, polygon; + process::ProcessType = within_process, + exclude_boundaries = true, + close = false, +) = _geom_polygon_process( + line, polygon, + (args...; kwargs...) -> _line_closed_curve_process( + args...; + kwargs..., + close = close, + ); + process = process, + ext_exclude_boundaries = exclude_boundaries, + hole_exclude_boundaries = exclude_boundaries, +) + +function _geom_polygon_process( + geom, polygon, geom_closed_curve_func; + process::ProcessType = within_process, + ext_exclude_boundaries = true, + hole_exclude_boundaries = false +) + # Check interaction of geom with polygon's exterior boundary + ext_val = geom_closed_curve_func( + geom, GI.getexterior(polygon); + process = process, exclude_boundaries = ext_exclude_boundaries, + ) + + #= + If checking within and geom is outside of exterior ring, return false or + if checking disjoint and geom is outside of exterior ring, return true. + =# + ((process == within_process && !ext_val) || + (process == disjoint_process && ext_val) + ) && return ext_val + + # If geom is within the polygon, need to check interactions with holes + for hole in GI.gethole(polygon) + hole_val = geom_closed_curve_func( + geom, hole, + process = ( + process == within_process ? + disjoint_process : + within_process + ), + exclude_boundaries = hole_exclude_boundaries + ) + #= + If checking within and geom is not disjoint from hole, return false or + if checking disjoint and geom is within hole, return true. + =# + process == within_process && !hole_val && return false + process == disjoint_process && hole_val && return true + end + return ext_val +end + +function _point_in_extent(p, extent::Extents.Extent) + (x1, x2), (y1, y2) = extent.X, extent.Y + return x1 ≤ GI.x(p) ≤ x2 && y1 ≤ GI.y(p) ≤ y2 +end diff --git a/src/methods/geom_in_out_geom.jl b/src/methods/geom_in_out_geom.jl index f1ba4b919..11a13094a 100644 --- a/src/methods/geom_in_out_geom.jl +++ b/src/methods/geom_in_out_geom.jl @@ -109,7 +109,8 @@ function line_in_geom( ) || return out _line_in_closed_curve( line1, line2; - close = false, in = in, on = on, out = out) + close = false, + ) else @warn "Linestring isn't closed. Point cannot be 'in' linestring." out @@ -174,7 +175,7 @@ function line_in_geom( Extents.intersects(GI.extent(line), GI.extent(ring)) || return out return _line_in_closed_curve( line, ring; - close = false, in = in, on = on, out = out, + close = false, ) end @@ -289,7 +290,7 @@ function point_in_geom( Extents.intersects(GI.extent(line), GI.extent(ring)) || return out ext_val = _line_in_closed_curve( line, GI.getexterior(poly); - close = false, in = in, on = on, out = out, + close = false, ) for ring in GI.gethole(poly) @@ -441,82 +442,92 @@ is outside of the curve. We take special care of intersections through vertices as it isn't clearcut if those neccesitate a segment of the line being outside of the curve. """ -function _line_in_closed_curve( +_line_in_closed_curve(line, curve; + exclude_boundaries = false, + close = false, +) = _line_in_out_closed_curve( line, curve; - in::T = 1, on::T = -1, out::T = 0, - close = false, -) where {T} - # Determine number of points in curve and line - nc = GI.npoint(curve) - nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 - nl = GI.npoint(line) - nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 - # Check to see if first point in line is within curve - point_val = _point_in_closed_curve( - GI.getpoint(line, 1), curve; - in = in, on = on, out = out, - ) - # point is outside curve, line can't be within curve - point_val == out && return out - # Check for any intersections between line and curve - line_on_curve = point_val == on # record if line is "on" part of curve - l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) - for i in (close ? 1 : 2):nl - l_end = _tuple_point(GI.getpoint(line, i)) - c_start = _tuple_point(GI.getpoint(curve, nc)) - for j in 1:nc - c_end = _tuple_point(GI.getpoint(curve, j)) - # Check if edges intersect --> line is not within curve - meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) - # open line segments meet in a single point - meet_type == 1 && return out - #= - closed line segments meet in one or several points -> meet at a - vertex or on the edge itself (parallel) - =# - if meet_type == 0 - line_on_curve = true - # See if segment is parallel and within curve edge - p1_on_seg = point_on_segment(l_start, c_start, c_end) - p2_on_seg = point_on_segment(l_end, c_start, c_end) - # if segment isn't contained within curve edge - if !p1_on_seg || !p2_on_seg - # Make sure l_start is in or on the segment - p1_in_curve = - p1_on_seg || - _point_in_closed_curve( - l_start, curve; - in = in, on = on, out = out, - ) != out - !p1_in_curve && return out - # Make sure l_end is in or on the segment - p2_in_curve = - p2_on_seg || - _point_in_closed_curve( - l_end, curve; - in = in, on = on, out = out, - ) != out - !p2_in_curve && return out - #= - If both endpoints are within or on the curve, but not - parallel to the edge, make sure that midpoints between the - intersections along the segment are within curve - =# - !_segment_mids_in_curve( - l_start, l_end, curve; - in = in, on = on, out = out, - ) && return out # point of segment is outside of curve - # line segment is fully within or on curve - break - end - end - c_start = c_end - end - l_start = l_end - end - # check if line is on any curve edges or vertcies - return line_on_curve ? on : in -end + disjoint = false, + exclude_boundaries = exclude_boundaries, + close = close, +) + +# function _line_in_closed_curve( +# line, curve; +# in::T = 1, on::T = -1, out::T = 0, +# close = false, +# ) where {T} +# # Determine number of points in curve and line +# nc = GI.npoint(curve) +# nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 +# nl = GI.npoint(line) +# nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 +# # Check to see if first point in line is within curve +# point_val = _point_in_closed_curve( +# GI.getpoint(line, 1), curve; +# in = in, on = on, out = out, +# ) +# # point is outside curve, line can't be within curve +# point_val == out && return out +# # Check for any intersections between line and curve +# line_on_curve = point_val == on # record if line is "on" part of curve +# l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) +# for i in (close ? 1 : 2):nl +# l_end = _tuple_point(GI.getpoint(line, i)) +# c_start = _tuple_point(GI.getpoint(curve, nc)) +# for j in 1:nc +# c_end = _tuple_point(GI.getpoint(curve, j)) +# # Check if edges intersect --> line is not within curve +# meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) +# # open line segments meet in a single point +# meet_type == 1 && return out +# #= +# closed line segments meet in one or several points -> meet at a +# vertex or on the edge itself (parallel) +# =# +# if meet_type == 0 +# line_on_curve = true +# # See if segment is parallel and within curve edge +# p1_on_seg = point_on_segment(l_start, c_start, c_end) +# p2_on_seg = point_on_segment(l_end, c_start, c_end) +# # if segment isn't contained within curve edge +# if !p1_on_seg || !p2_on_seg +# # Make sure l_start is in or on the segment +# p1_in_curve = +# p1_on_seg || +# _point_in_closed_curve( +# l_start, curve; +# in = in, on = on, out = out, +# ) != out +# !p1_in_curve && return out +# # Make sure l_end is in or on the segment +# p2_in_curve = +# p2_on_seg || +# _point_in_closed_curve( +# l_end, curve; +# in = in, on = on, out = out, +# ) != out +# !p2_in_curve && return out +# #= +# If both endpoints are within or on the curve, but not +# parallel to the edge, make sure that midpoints between the +# intersections along the segment are within curve +# =# +# !_segment_mids_in_curve( +# l_start, l_end, curve; +# in = in, on = on, out = out, +# ) && return out # point of segment is outside of curve +# # line segment is fully within or on curve +# break +# end +# end +# c_start = c_end +# end +# l_start = l_end +# end +# # check if line is on any curve edges or vertcies +# return line_on_curve ? on : in +# end """ _segment_mids_in_curve( @@ -580,10 +591,10 @@ function _geom_in_polygon(geom, poly; close = false) (in_ext || on_ext) || return (false, false) # geom isn't in external ring # Check if the geom is in any of the holes for hole in GI.gethole(poly) - out_of_hole, some_on_hole = _line_in_closed_curve( - geom, hole; - close = close, in = false, - ) + # out_of_hole, some_on_hole = _line_in_closed_curve( + # geom, hole; + # close = close, in = false, + # ) # geom is in a hole -> not in polygon !(out_of_hole || some_on_hole) && return (false, false) end diff --git a/src/methods/overlaps.jl b/src/methods/overlaps.jl index 63393abd7..de723fbd6 100644 --- a/src/methods/overlaps.jl +++ b/src/methods/overlaps.jl @@ -224,10 +224,13 @@ function _overlaps( (a1, a2)::Edge, (b1, b2)::Edge ) - # meets in more than one point + # meets in more than one point or at endpoints on_top = ExactPredicates.meet(a1, a2, b1, b2) == 0 - # one end point is outside of other segment - a_fully_within = point_on_segment(a1, b1, b2) && point_on_segment(a2, b1, b2) - b_fully_within = point_on_segment(b1, a1, a2) && point_on_segment(b2, a1, a2) - return on_top && (!a_fully_within && !b_fully_within) + on_top || return false + # check that one endpoint of each edge is within other edge + a1_in = _point_in_on_out_segment(a1, b1, b2) == 1 + a2_in = _point_in_on_out_segment(a2, b1, b2) == 1 + b1_in = _point_in_on_out_segment(b1, a1, a2) == 1 + b2_in = _point_in_on_out_segment(b2, a1, a2) == 1 + return (a1_in ⊻ a2_in) && (b1_in ⊻ b2_in) end diff --git a/src/methods/touches.jl b/src/methods/touches.jl new file mode 100644 index 000000000..e69de29bb diff --git a/src/methods/within.jl b/src/methods/within.jl index 55d8940b9..d48bd6540 100644 --- a/src/methods/within.jl +++ b/src/methods/within.jl @@ -27,14 +27,109 @@ true within(g1, g2)::Bool = within(trait(g1), g1, trait(g2), g2)::Bool within(::GI.FeatureTrait, g1, ::Any, g2)::Bool = within(GI.geometry(g1), g2) within(::Any, g1, t2::GI.FeatureTrait, g2)::Bool = within(g1, GI.geometry(g2)) -# Points in geometries -within(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool = point_on_line(g1, g2; ignore_end_vertices=true) -within(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool = point_on_line(g1, g2; ignore_end_vertices=true) -within(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool = point_in_polygon(g1, g2)[1] -# Lines in geometries -within(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool = line_on_line(g1, g2) -within(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool = line_on_line(g1, g2) -within(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool = any(line_in_polygon(g1, g2)) + +# Points within geometries +# Point in point +within( + ::GI.PointTrait, g1, + ::GI.PointTrait, g2, +) = equals(g1, g2) +# Point in (on) line string +within( + ::GI.PointTrait, g1, + ::GI.LineStringTrait, g2, +) = _point_curve_process( + g1, g2; + process = within_process, + exclude_boundaries = true, + repeated_last_coord = false, +) +# Point in (on) curve +within( + ::GI.PointTrait, g1, + ::GI.LinearRingTrait, g2, +) = _point_curve_process( + g1, g2; + process = within_process, + exclude_boundaries = true, + repeated_last_coord = true, +) + +# Point in polygon +within( + ::GI.PointTrait, g1, + ::GI.PolygonTrait, g2, +) = _point_polygon_process( + g1, g2; + process = within_process, exclude_boundaries = true, +) + +# Lines in geometries +within( + ::GI.LineStringTrait, line, + ::GI.LineStringTrait, curve, +) = _line_curve_process( + line, curve; + process = within_process, + exclude_boundaries = false, + closed_line = false, + closed_curve = false, +) + +within( + ::GI.LineStringTrait, line, + ::GI.LinearRingTrait, ring, +) = _line_curve_process( + line, ring; + process = within_process, + exclude_boundaries = false, + closed_line = false, + closed_curve = true, +) + +within( + ::GI.LineStringTrait, line, + ::GI.PolygonTrait, polygon, +) = _line_polygon_process( + line, polygon; + process = within_process, + exclude_boundaries = false, + close = false, +) + +# Rings in geometries +within( + ::GI.LinearRingTrait, line, + ::GI.LineStringTrait, curve, +) = _line_curve_process( + line, curve; + process = within_process, + exclude_boundaries = false, + closed_line = true, + closed_curve = false, +) + +within( + ::GI.LinearRingTrait, line, + ::GI.LinearRingTrait, ring, +) = _line_curve_process( + line, ring; + process = within_process, + exclude_boundaries = false, + closed_line = true, + closed_curve = true, +) + +within( + ::GI.LinearRingTrait, line, + ::GI.PolygonTrait, polygon, +) = _line_polygon_process( + line, polygon; + process = within_process, + exclude_boundaries = true, + close = true, +) + # Polygons within geometries within(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool = polygon_in_polygon(g1, g2) @@ -42,3 +137,4 @@ within(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool = polygon_in_polygon( # TODO: Add multipolygons within(::GI.AbstractTrait, g1, ::GI.AbstractCurveTrait, g2)::Bool = false + diff --git a/src/try.jl b/src/try.jl index e93825000..ba8338ab9 100644 --- a/src/try.jl +++ b/src/try.jl @@ -2,11 +2,9 @@ import GeometryOps as GO import GeoInterface as GI import LibGEOS as LG -closed_string = LG.LineString([ - [0.0, 0.0], [1.0, 1.0], [3.0, 1.25], [2.0, 3.0], [-1.0, 2.75], [0.0, 0.0] -]) -GO.line_in_geom( - LG.LineString([[0.0, 0.0], [3.0, 1.25]]), - closed_string -) == not_in_on_geom + +r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) +l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) + +GO.within(l2, r3) \ No newline at end of file diff --git a/test/methods/contains.jl b/test/methods/contains.jl new file mode 100644 index 000000000..bcc669079 --- /dev/null +++ b/test/methods/contains.jl @@ -0,0 +1,31 @@ +p1 = LG.Point([0.0, 0.0]) +p2 = LG.Point([0.0, 0.1]) +p3 = LG.Point([1.0, 0.0]) + +l1 = LG.LineString([[0.0, 1.0]]) +l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) + +# Point and point +@test GO.contains(p1, p1) == LG.contains(p1, p1) +@test GO.contains(p1, p2) == LG.contains(p1, p2) + +# Point and line +@test GO.contains(l1, p1) == LG.contains(l1, p1) +@test GO.contains(l1, p2) == LG.contains(l1, p2) +@test GO.contains(l1, p3) == LG.contains(l1, p3) +@test GO.contains(l2, p1) == LG.contains(l2, p1) +@test GO.contains(l2, p2) == LG.contains(l2, p2) +@test GO.contains(l2, p3) == LG.contains(l2, p3) + +# Line and line +@test GO.contains(l1, l1) == LG.contains(l1, l1) +@test GO.contains(l1, l2) == LG.contains(l1, l2) +@test GO.contains(l1, l3) == LG.contains(l1, l3) +@test GO.contains(l1, l4) == LG.contains(l1, l4) +@test GO.contains(l1, l5) == LG.contains(l1, l5) + +@test GO.contais(l1, l1) == LG.contains(l1, l1) +@test GO.contais(l2, l1) == LG.contains(l2, l1) +@test GO.contais(l3, l1) == LG.contains(l3, l1) +@test GO.contais(l4, l1) == LG.contains(l4, l1) +@test GO.contais(l5, l1) == LG.contains(l5, l1) \ No newline at end of file diff --git a/test/methods/covers.jl b/test/methods/covers.jl new file mode 100644 index 000000000..57f1aa92a --- /dev/null +++ b/test/methods/covers.jl @@ -0,0 +1,25 @@ +p1 = LG.Point([0.0, 0.0]) +p2 = LG.Point([0.0, 0.1]) +p3 = LG.Point([1.0, 0.0]) + +l1 = LG.LineString([[0.0, 1.0]]) +l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) + +# Point and point +@test GO.covers(p1, p1) == LG.covers(p1, p1) +@test GO.covers(p1, p2) == LG.covers(p1, p2) + +# Point and line +@test GO.covers(l1, p1) == LG.covers(l1, p1) +@test GO.covers(l1, p2) == LG.covers(l1, p2) +@test GO.covers(l1, p3) == LG.covers(l1, p3) +@test GO.covers(l2, p1) == LG.covers(l2, p1) +@test GO.covers(l2, p2) == LG.covers(l2, p2) +@test GO.covers(l2, p3) == LG.covers(l2, p3) + +# Line and line +@test GO.covers(l1, l1) == LG.covers(l1, l1) +@test GO.covers(l2, l1) == LG.covers(l2, l1) +@test GO.covers(l3, l1) == LG.covers(l3, l1) +@test GO.covers(l4, l1) == LG.covers(l4, l1) +@test GO.covers(l5, l1) == LG.covers(l5, l1) \ No newline at end of file diff --git a/test/methods/disjoint.jl b/test/methods/disjoint.jl new file mode 100644 index 000000000..96c5f1425 --- /dev/null +++ b/test/methods/disjoint.jl @@ -0,0 +1,56 @@ +pt1 = LG.Point([0.0, 0.0]) +pt2 = LG.Point([0.0, 0.1]) +pt3 = LG.Point([1.0, 0.0]) +pt4 = LG.Point([0.5, 1.0]) +pt5 = LG.Point([0.2, 0.5]) +pt6 = LG.Point([0.3, 0.55]) +pt7 = LG.Point([0.6, 0.49]) + +l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) +l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) +l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) +l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) +l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) +l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) + +r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) + +p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) +p2 = LG.Polygon([ + [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], + [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], + [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] +]) + +# Point and point +@test GO.disjoint(pt1, pt1) == LG.disjoint(pt1, pt1) +@test GO.disjoint(pt1, pt2) == LG.disjoint(pt1, pt2) + +# Point and line +@test GO.disjoint(pt1, l1) == LG.disjoint(pt1, l1) +@test GO.disjoint(pt2, l1) == LG.disjoint(pt2, l1) +@test GO.disjoint(pt3, l1) == LG.disjoint(pt3, l1) +@test GO.disjoint(pt1, l2) == LG.disjoint(pt1, l2) +@test GO.disjoint(pt2, l2) == LG.disjoint(pt2, l2) +@test GO.disjoint(pt3, l2) == LG.disjoint(pt3, l2) +@test GO.within(pt1, l6) == GO.within(pt1, l6) + +# Point and Ring +@test GO.disjoint(pt1, r1) == LG.disjoint(pt1, r1) +@test GO.disjoint(pt2, r1) == LG.disjoint(pt2, r1) +@test GO.disjoint(pt3, r1) == LG.disjoint(pt3, r1) +@test GO.disjoint(pt4, r1) == LG.disjoint(pt4, r1) + +# Point and polygon +@test GO.disjoint(pt1, p1) == LG.disjoint(pt1, p1) +@test GO.disjoint(pt2, p1) == LG.disjoint(pt2, p1) +@test GO.disjoint(pt3, p1) == LG.disjoint(pt3, p1) +@test GO.disjoint(pt4, p1) == LG.disjoint(pt4, p1) + +@test GO.disjoint(pt1, p2) == LG.disjoint(pt1, p2) +@test GO.disjoint(pt2, p2) == LG.disjoint(pt2, p2) +@test GO.disjoint(pt3, p2) == LG.disjoint(pt3, p2) +@test GO.disjoint(pt4, p2) == LG.disjoint(pt4, p2) +@test GO.disjoint(pt5, p2) == LG.disjoint(pt5, p2) +@test GO.disjoint(pt6, p2) == LG.disjoint(pt6, p2) +@test GO.disjoint(pt7, p2) == LG.disjoint(pt7, p2) \ No newline at end of file diff --git a/test/methods/overlaps.jl b/test/methods/overlaps.jl index 0c2dab96d..126924743 100644 --- a/test/methods/overlaps.jl +++ b/test/methods/overlaps.jl @@ -45,6 +45,8 @@ end 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]]) + l5 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) + l6 = LG.LineString([[0.0, 0.0], [0.0, -10.0]]) # Line can't overlap with itself @test GO.overlaps(l1, l1) == LG.overlaps(l1, l1) # Line completely within other line doesn't overlap @@ -53,6 +55,10 @@ end @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) + # Lines that form a hinge at the origin + @test GO.overlaps(l1, l5) == LG.overlaps(l1, l5) + # Lines meet at one point and continue parallel in opposite directions + @test GO.overlaps(l1, l6) == LG.overlaps(l1, l6) # 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]]) diff --git a/test/methods/touches.jl b/test/methods/touches.jl new file mode 100644 index 000000000..16ec90b59 --- /dev/null +++ b/test/methods/touches.jl @@ -0,0 +1,25 @@ +p1 = LG.Point([0.0, 0.0]) +p2 = LG.Point([0.0, 0.1]) +p3 = LG.Point([1.0, 0.0]) + +l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) +l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) + +# Point and point +@test GO.touches(p1, p1) == LG.touches(p1, p1) +@test GO.touches(p1, p2) == LG.touches(p1, p2) + +# Point and line +@test GO.touches(p1, l1) == LG.touches(p1, l1) +@test GO.touches(p2, l1) == LG.touches(p2, l1) +@test GO.touches(p3, l1) == LG.touches(p3, l1) +@test GO.touches(p1, l2) == LG.touches(p1, l2) +@test GO.touches(p2, l2) == LG.touches(p2, l2) +@test GO.touches(p3, l2) == LG.touches(p3, l2) + +# Line and line +LG.touches(l1, l1) +LG.touches(l1, l2) +LG.touches(l1, l3) +LG.touches(l1, l4) +LG.touches(l1, l5) \ No newline at end of file diff --git a/test/methods/within.jl b/test/methods/within.jl new file mode 100644 index 000000000..244184c10 --- /dev/null +++ b/test/methods/within.jl @@ -0,0 +1,105 @@ +pt1 = LG.Point([0.0, 0.0]) +pt2 = LG.Point([0.0, 0.1]) +pt3 = LG.Point([1.0, 0.0]) +pt4 = LG.Point([0.5, 1.0]) +pt5 = LG.Point([0.2, 0.5]) +pt6 = LG.Point([0.3, 0.55]) +pt7 = LG.Point([0.6, 0.49]) + +l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) +l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) +l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) +l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) +l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) +l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) +l7 = LG.LineString([[0.6, 0.6], [0.8, 0.6]]) +l8 = LG.LineString([[0.3, 0.55], [0.3, 0.65]]) +l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) +l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) +l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) +l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) + +r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) +r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) +r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) + +p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) +p2 = LG.Polygon([ + [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], + [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], + [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] +]) + +# Point and point +@test GO.within(pt1, pt1) == LG.within(pt1, pt1) +@test GO.within(pt1, pt2) == LG.within(pt1, pt2) + +# Point and line +@test GO.within(pt1, l1) == LG.within(pt1, l1) +@test GO.within(pt2, l2) == LG.within(pt2, l2) +@test GO.within(pt2, l1) == LG.within(pt2, l1) +@test GO.within(pt3, l1) == LG.within(pt3, l1) +@test GO.within(pt1, l2) == LG.within(pt1, l2) +@test GO.within(pt2, l2) == LG.within(pt2, l2) +@test GO.within(pt3, l2) == LG.within(pt3, l2) +@test GO.within(pt1, l6) == GO.within(pt1, l6) + +# Point and Ring +@test GO.within(pt1, r1) == LG.within(pt1, r1) +@test GO.within(pt2, r1) == LG.within(pt2, r1) +@test GO.within(pt3, r1) == LG.within(pt3, r1) +@test GO.within(pt4, r1) == LG.within(pt4, r1) + +# Point and polygon +@test GO.within(pt1, p1) == LG.within(pt1, p1) +@test GO.within(pt2, p1) == LG.within(pt2, p1) +@test GO.within(pt3, p1) == LG.within(pt3, p1) +@test GO.within(pt4, p1) == LG.within(pt4, p1) + +@test GO.within(pt1, p2) == LG.within(pt1, p2) +@test GO.within(pt2, p2) == LG.within(pt2, p2) +@test GO.within(pt3, p2) == LG.within(pt3, p2) +@test GO.within(pt4, p2) == LG.within(pt4, p2) +@test GO.within(pt5, p2) == LG.within(pt5, p2) +@test GO.within(pt6, p2) == LG.within(pt6, p2) +@test GO.within(pt7, p2) == LG.within(pt7, p2) + +# Line and line +@test GO.within(l1, l1) == LG.within(l1, l1) +@test GO.within(l1, l2) == LG.within(l1, l2) +@test GO.within(l1, l3) == LG.within(l1, l3) +@test GO.within(l1, l4) == LG.within(l1, l4) +@test GO.within(l1, l5) == LG.within(l1, l5) +@test GO.within(l5, l1) == LG.within(l5, l1) + +# Line and ring +@test GO.within(l6, r1) == LG.within(l6, r1) +@test GO.within(l2, r2) == LG.within(l2, r2) +@test GO.within(l2, r3) == LG.within(l2, r3) +@test GO.within(l12, r1) == LG.within(l12, r1) + +# Line and polygon +@test GO.within(l6, p1) == LG.within(l6, p1) +@test GO.within(l1, p2) == LG.within(l1, p2) +@test GO.within(l2, p2) == LG.within(l2, p2) +@test GO.within(l3, p2) == LG.within(l3, p2) +@test GO.within(l7, p2) == LG.within(l7, p2) +@test GO.within(l8, p2) == LG.within(l8, p2) +@test GO.within(l9, p2) == LG.within(l9, p2) +@test GO.within(l10, p2) == LG.within(l10, p2) +@test GO.within(l11, p2) == LG.within(l11, p2) + +# Ring and line +@test GO.within(r1, l6) == LG.within(r1, l6) +@test GO.within(r2, l2) == LG.within(r2, l2) +@test GO.within(r3, l2) == LG.within(r3, l2) +@test GO.within(r1, l12) == LG.within(r1, l12) + +# Ring and Ring +@test GO.within(r1, r1) == LG.within(r1, r1) +@test GO.within(r1, r2) == LG.within(r1, r2) +@test GO.within(r1, r3) == LG.within(r1, r3) + +# Ring and polygon + +# Polygon in polygon diff --git a/test/runtests.jl b/test/runtests.jl index b412cb67d..ffc257d07 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -18,12 +18,15 @@ const GO = GeometryOps @testset "Barycentric coordinate operations" begin include("methods/barycentric.jl") end @testset "Bools" begin include("methods/bools.jl") end @testset "Centroid" begin include("methods/centroid.jl") end + @testset "Disjoint" begin include("methods/disjoint.jl") end @testset "Equals" begin include("methods/equals.jl") end - @testset "Geom in geom" begin include("methods/geom_in_geom.jl") end - @testset "Geom on geom" begin include("methods/geom_on_geom.jl") end + # @testset "Geom in geom" begin include("methods/geom_in_geom.jl") end + # @testset "Geom on geom" begin include("methods/geom_on_geom.jl") end @testset "Intersect" begin include("methods/intersects.jl") end @testset "Signed Area" begin include("methods/signed_area.jl") end @testset "Overlaps" begin include("methods/overlaps.jl") end + @testset "Within" begin include("methods/within.jl") end + # Transformations @testset "Reproject" begin include("transformations/reproject.jl") end @testset "Flip" begin include("transformations/flip.jl") end From b5575206d705dae4f90d936e01b27e93ae0e0744 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Thu, 30 Nov 2023 12:09:35 -0800 Subject: [PATCH 16/33] Finish within tests --- src/GeometryOps.jl | 1 - src/methods/crosses.jl | 2 +- src/methods/disjoint.jl | 132 ++- src/methods/geom_geom_processors.jl | 278 +++--- src/methods/geom_in_out_geom.jl | 1330 +++++++++++++-------------- src/methods/geom_on_geom.jl | 71 -- src/methods/within.jl | 44 +- src/try.jl | 6 +- test/methods/within.jl | 70 ++ 9 files changed, 1029 insertions(+), 905 deletions(-) delete mode 100644 src/methods/geom_on_geom.jl diff --git a/src/GeometryOps.jl b/src/GeometryOps.jl index 1df0a4156..ee865e4f5 100644 --- a/src/GeometryOps.jl +++ b/src/GeometryOps.jl @@ -33,7 +33,6 @@ include("methods/polygonize.jl") include("methods/barycentric.jl") include("methods/equals.jl") include("methods/geom_geom_processors.jl") -include("methods/geom_on_geom.jl") include("methods/orientation.jl") include("transformations/flip.jl") diff --git a/src/methods/crosses.jl b/src/methods/crosses.jl index 09df570f8..6ae7e9f39 100644 --- a/src/methods/crosses.jl +++ b/src/methods/crosses.jl @@ -62,7 +62,7 @@ function line_crosses_line(line1, line2) 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 + te(p, (pa, pb); exclude_boundary) && return true end end end diff --git a/src/methods/disjoint.jl b/src/methods/disjoint.jl index 10d2b195e..fbec91eed 100644 --- a/src/methods/disjoint.jl +++ b/src/methods/disjoint.jl @@ -23,20 +23,36 @@ disjoint(g1, g2)::Bool = disjoint(trait(g1), g1, trait(g2), g2) disjoint(::FeatureTrait, g1, ::Any, g2)::Bool = disjoint(GI.geometry(g1), g2) disjoint(::Any, g1, t2::FeatureTrait, g2)::Bool = disjoint(g1, geometry(g2)) # Point disjoint geometries + # Point disjoint from point disjoint( ::GI.PointTrait, g1, ::GI.PointTrait, g2, ) = !equals(g1, g2) + +# Point in from line string +disjoint( + ::GI.PointTrait, g1, + ::GI.LineStringTrait, g2, +) = _point_curve_process( + g1, g2; + process = disjoint_process, + exclude_boundaries = false, + repeated_last_coord = false, +) + # Point disjoint from curve disjoint( ::GI.PointTrait, g1, - ::Union{GI.LineStringTrait, GI.LinearRingTrait}, g2, + ::GI.LinearRingTrait, g2, ) = _point_curve_process( g1, g2; - process = disjoint_process, exclude_boundaries = false, + process = disjoint_process, + exclude_boundaries = false, + repeated_last_coord = true, ) -# Point in polygon + +# Point disjoint from polygon disjoint( ::GI.PointTrait, g1, ::GI.PolygonTrait, g2, @@ -45,32 +61,100 @@ disjoint( process = disjoint_process, exclude_boundaries = false, ) +# Geometry with point +disjoint( + trait1::GI.AbstractTrait, g1, + trait2::GI.PointTrait, g2, +) = disjoint(trait2, g2, trait1, g1) -disjoint(::LineStringTrait, g1, ::PointTrait, g2)::Bool = !point_on_line(g2, g1) -disjoint(::LineStringTrait, g1, ::LineStringTrait, g2)::Bool = !line_on_line(g1, g2) -disjoint(::LineStringTrait, g1, ::PolygonTrait, g2)::Bool = !line_in_polygon(g2, g1) -disjoint(::PolygonTrait, g1, ::PointTrait, g2)::Bool = !point_in_polygon(g2, g1) -disjoint(::PolygonTrait, g1, ::LineStringTrait, g2)::Bool = !line_in_polygon(g2, g1) -disjoint(::PolygonTrait, g1, ::PolygonTrait, g2)::Bool = polygon_disjoint(g2, g1) - -function polygon_disjoint(poly1, poly2) - for point in GI.getpoint(poly1) - point_in_polygon(point, poly2) && return false - end - for point in GI.getpoint(poly2) - point_in_polygon(point, poly1) && return false - end - return !intersects(poly1, poly2) -end +# Lines disjoint from geometries -_line_disjoint_closed_curve( +# Lines disjoint from lines +disjoint( + ::GI.LineStringTrait, line, + ::GI.LineStringTrait, curve, +) = _line_curve_process( line, curve; + process = disjoint_process, + exclude_boundaries = false, + closed_line = false, + closed_curve = false, +) + +# Lines disjoint from rings +disjoint( + ::GI.LineStringTrait, line, + ::GI.LinearRingTrait, ring, +) = _line_curve_process( + line, ring; + process = disjoint_process, + exclude_boundaries = false, + closed_line = false, + closed_curve = true, +) + +# Lines disjoint from polygons +disjoint( + ::GI.LineStringTrait, line, + ::GI.PolygonTrait, polygon, +) = _line_polygon_process( + line, polygon; + process = disjoint_process, exclude_boundaries = false, close = false, -) = _line_orient_closed_curve( +) + +# Rings disjoint from geometries + +# Rings disjoint from lines +disjoint( + ::GI.LinearRingTrait, line, + ::GI.LineStringTrait, curve, +) = _line_curve_process( line, curve; - disjoint = true, - exclude_boundaries = exclude_boundaries, - close = close, + process = disjoint_process, + exclude_boundaries = false, + closed_line = true, + closed_curve = false, ) +# Rings disjoint from rings +disjoint( + ::GI.LinearRingTrait, line, + ::GI.LinearRingTrait, ring, +) = _line_curve_process( + line, ring; + process = disjoint_process, + exclude_boundaries = false, + closed_line = true, + closed_curve = true, +) + +# Rings disjoint from polygons +disjoint( + ::GI.LinearRingTrait, line, + ::GI.PolygonTrait, polygon, +) = _line_polygon_process( + line, polygon; + process = disjoint_process, + exclude_boundaries = false, + close = true, +) + +# Polygons disjoint from polygons +function disjoint( + ::GI.PolygonTrait, poly1, + ::GI.PolygonTrait, poly2; +) + if disjoint(GI.getexterior(poly1), poly2) + return true + else + for hole in GI.gethole(poly1) + if within(poly2, hole) + return true + end + end + end + return false +end + diff --git a/src/methods/geom_geom_processors.jl b/src/methods/geom_geom_processors.jl index cad7132b4..312a12fad 100644 --- a/src/methods/geom_geom_processors.jl +++ b/src/methods/geom_geom_processors.jl @@ -179,6 +179,19 @@ function _line_curve_process( closed_curve |= explicit_closed_curve exclude_boundaries |= explicit_closed_curve + check_func = + if process == within_process + (args...) -> _line_curve_within_checks( + args...; + nc = nc, closed_curve = closed_curve, exclude_boundaries, + ) + else + (args...) -> _line_curve_disjoint_checks( + args...; + nc = nc, closed_curve = closed_curve, exclude_boundaries, + ) + end + l_start = _tuple_point(GI.getpoint(line, closed_line ? nl : 1)) i = closed_line ? 1 : 2 while i ≤ nl @@ -186,104 +199,110 @@ function _line_curve_process( c_start = _tuple_point(GI.getpoint(curve, closed_curve ? nc : 1)) for j in (closed_curve ? 1 : 2):nc c_end = _tuple_point(GI.getpoint(curve, j)) - # Check if edges intersect -> not disjoint + meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) - if process == within_process - #= - if l_start is in/on curve and curve and line meet either at - endpoints or are parallel and meet in multiple points - =# - if ( - meet_type == 0 && - _point_in_on_out_segment(l_start, c_start, c_end) != 0 - ) - # if l_end is within curve, whole line is contained in curve - if _point_in_on_out_segment( - l_end, - c_start, c_end, - ) != 0 - i += 1 - l_start = l_end - break - #= - if c_start is in line, then need to find overlap for c_start - to l_end as l_start to c_start is overlapping with curve - =# - elseif _point_in_on_out_segment( - c_start, - l_start, l_end, - ) == 1 - l_start = c_start - break - #= - if c_end is in line, then need to find overlap for c_end to - l_end as l_start to c_end is overlapping with curve - =# - elseif _point_in_on_out_segment( - c_end, - l_start, l_end, - ) == 1 - l_start = c_end - break - end - end - j == nc && return false - else # disjoint_process - if ( - exclude_boundaries && meet_type == 0 && - !closed_curve && (j == 2 || j == nc) - ) - if _isparallel(l_start, l_end, c_start, c_end) - (p1, p2) = - if j == 2 && equals(c_start, l_start) - (l_end, c_end) - elseif j == 2 && equals(c_start, l_end) - (l_start, c_end) - elseif j == nc && equals(c_end, l_start) - (l_end, c_start) - elseif j == nc &&equals(c_end, l_end) - (l_start, c_start) - else - return false - end - ( - _point_in_on_out_segment(p1, c_start, c_end) || - _point_in_on_out_segment(p2, l_start, l_end) - ) && return false - else - _, (_, c_frac) = _intersection_point( - (l_start, l_end), - (c_start, c_end), - ) - j == 2 && c_frac != 0 && return false - j == nc && c_frac != 1 && return false - end - elseif meet_type != -1 - return false - end - if j == nc - i += 1 - l_start = l_end - end - end + passes_checks, break_loop, i, l_start = check_func( + meet_type, + l_start, l_end, + c_start, c_end, + i, j, + ) + break_loop && break + !passes_checks && return false c_start = c_end end end return true end +function _line_curve_within_checks( + meet_type, + l_start, l_end, + c_start, c_end, + i, j; + nc, + closed_curve, + exclude_boundaries, +) + is_within = true + break_loop = false + #= + if l_start is in/on curve and curve and line meet either at + endpoints or are parallel and meet in multiple points + =# + if ( + meet_type == 0 && + _point_in_on_out_segment(l_start, c_start, c_end) != 0 + ) + #= + if excluding first and last point of curve, make sure those points + aren't within the line segment + =# + if exclude_boundaries && !closed_curve && (( + j == 2 && _point_in_on_out_segment(c_start, l_start, l_end) != 0 + ) || ( + j == nc && _point_in_on_out_segment(c_end, l_start, l_end) != 0 + )) + is_within = false + else # if end points aren't being excluded + # if l_end is within curve, whole line is contained in curve + if _point_in_on_out_segment(l_end, c_start, c_end) != 0 + i += 1 + l_start = l_end + break_loop = true + #= + if c_start is in line, then need to find overlap for c_start + to l_end as l_start to c_start is overlapping with curve + =# + elseif _point_in_on_out_segment( + c_start, + l_start, l_end, + ) == 1 + l_start = c_start + break_loop = true + #= + if c_end is in line, then need to find overlap for c_end to + l_end as l_start to c_end is overlapping with curve + =# + elseif _point_in_on_out_segment( + c_end, + l_start, l_end, + ) == 1 + l_start = c_end + break_loop = true + end + end + end + #= + if line segment has been checked against all curve segments and it isn't + within any of them, line isn't within curve + =# + if j == nc + is_within = false + end + return is_within, break_loop, i, l_start +end + function _line_curve_disjoint_checks( meet_type, - l_start, l_end, c_start, c_end, - j, + l_start, l_end, + c_start, c_end, + i, j; nc, closed_curve, exclude_boundaries, ) + is_disjoint = true + break_loop = false + #= + if excluding first and last point of curve, line can still cross those + points and be disjoint + =# if ( exclude_boundaries && meet_type == 0 && !closed_curve && (j == 2 || j == nc) ) + # if line and curve are parallel, they cannot overlap and be disjoint if _isparallel(l_start, l_end, c_start, c_end) (p1, p2) = if j == 2 && equals(c_start, l_start) @@ -295,71 +314,46 @@ function _line_curve_disjoint_checks( elseif j == nc &&equals(c_end, l_end) (l_start, c_start) else - return false + is_disjoint = false end - ( + if is_disjoint && ( _point_in_on_out_segment(p1, c_start, c_end) || _point_in_on_out_segment(p2, l_start, l_end) - ) && return false + ) + is_disjoint = false + end + #= + if line and curve aren't parallel, they intersection must be either the + start or end point of the curve to be disjoint + =# else _, (_, c_frac) = _intersection_point( (l_start, l_end), (c_start, c_end), ) - j == 2 && c_frac != 0 && return false - j == nc && c_frac != 1 && return false + if ( + j == 2 && c_frac != 0 || + j == nc && c_frac != 1 + ) + is_disjoint = false + end end + #= + if not excluding first and last point of curve, line cannot intersect with + any points of the curve + =# elseif meet_type != -1 - return false + is_disjoint = false end + #= + if line segment has been checked against all curve segments and is disjoint + from all of them, we can now check the next line segment + =# if j == nc i += 1 l_start = l_end end -end - -function _line_curve_within_checks( - line, curve, - nl, nc, - closed_line, closed_curve, - exclude_boundaries, -) - seg_idx = 0 - l_start = _tuple_point(GI.getpoint(line, closed_line ? nl : 1)) - c_start = _tuple_point(GI.getpoint(curve, closed_curve ? nc : 1)) - for i in (closed_curve ? 1 : 2):nc - c_end = _tuple_point(GI.getpoint(curve, i)) - c_start_val = _point_in_on_out_segment(l_start, c_start, c_end) - if c_start_val == -1 && exclude_boundaries && (i == 2 || i == nc) - i == 2 && equals(l_start, c_start) && return false - i == nc && equals(l_start, c_end) && return false - end - if c_start_val != 0 # if point is in or on the segment - seg_idx = i - break - end - c_start = c_end - end - seg_idx == 0 && return false - - for i in (closed_line ? 1 : 2):nl - l_end = _tuple_point(GI.getpoint(line, i)) - line_on_next_seg = true - while line_on_next_seg - seg_idx > nc && return false - c_end = _tuple_point(GI.getpoint(curve, seg_idx)) - meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) - meet_type != 0 && return false - end_point_val = _point_in_on_out_segment(l_end, c_start, c_end) - end_point_val == 1 && break - end_point_val == -1 && (line_on_next_seg = false) - # Switch to next segment - seg_idx += 1 - c_start = c_end - end - l_start = l_end - end - return true + return is_disjoint, break_loop, i, l_start end function _line_closed_curve_process( @@ -367,8 +361,14 @@ function _line_closed_curve_process( process::ProcessType = within_process, exclude_boundaries = false, close = false, + line_is_poly_ring = false, ) - point_in = false # see if at least one point is within the closed curve + #= + if line isn't the external ring of a polygon, see if at least one point is + within the closed curve - else, ring is "filled in" and has points within + closed curve + =# + point_in = line_is_poly_ring in_val, out_val, on_val = get_process_type_vals(process, exclude_boundaries) # Determine number of points in curve and line nc = GI.npoint(curve) @@ -401,9 +401,15 @@ function _line_closed_curve_process( =# if meet_type == 0 # See if segment is parallel and within curve edge - p1_on_seg = point_on_segment(l_start, c_start, c_end) + p1_on_seg = _point_in_on_out_segment( + l_start, + c_start, c_end, + ) != 0 exclude_boundaries && p1_on_seg && return false - p2_on_seg = point_on_segment(l_end, c_start, c_end) + p2_on_seg = _point_in_on_out_segment( + l_end, + c_start, c_end, + ) != 0 exclude_boundaries && p2_on_seg && return false # if segment isn't contained within curve edge if !p1_on_seg || !p2_on_seg @@ -492,18 +498,22 @@ _line_polygon_process( process::ProcessType = within_process, exclude_boundaries = true, close = false, + line_is_poly_ring = false, ) = _geom_polygon_process( line, polygon, (args...; kwargs...) -> _line_closed_curve_process( args...; kwargs..., close = close, + line_is_poly_ring = line_is_poly_ring, ); process = process, ext_exclude_boundaries = exclude_boundaries, hole_exclude_boundaries = exclude_boundaries, ) + + function _geom_polygon_process( geom, polygon, geom_closed_curve_func; process::ProcessType = within_process, diff --git a/src/methods/geom_in_out_geom.jl b/src/methods/geom_in_out_geom.jl index 11a13094a..10ecd0f59 100644 --- a/src/methods/geom_in_out_geom.jl +++ b/src/methods/geom_in_out_geom.jl @@ -1,522 +1,690 @@ -export point_in_geom, point_in_polygon - -""" - point_in_geom( - point, geom; - in::T = 1, on::T = -1, out::T = 0, - )::T - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means the point is within the geometry (excluding edges and vertices). -`On` means the point is on an edge or a vertex of the geometry. -`Out` means the point is outside of the geometry. -""" -point_in_geom( - point, geom; - in::T = 1, on::T = -1, out::T = 0, -) where {T} = point_in_geom( - GI.trait(point), point, - GI.trait(geom), geom; - in = in, on = on, out = out, -) - -""" - line_in_geom( - line, geom; - in::T = 1, on::T = -1, out::T = 0, - )::T - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means the line is within geometry (no segments on edges and vertices). -`On` means the line has at least one segment on a geometry edge or a vertex. -`Out` means the line has at least one segment outside of the geometry. -""" -line_in_geom( - line, geom; - in::T = 1, on::T = -1, out::T = 0, -) where {T} = line_in_geom( - GI.trait(line), line, - GI.trait(geom), geom; - in = in, on = on, out = out, -) - -# ring_in_geom(ring, geom) = ring_in_geom( -# GI.trait(ring), ring, -# GI.trait(geom), geom, +# export point_in_geom, point_in_polygon + +# """ +# point_in_geom( +# point, geom; +# in::T = 1, on::T = -1, out::T = 0, +# )::T + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# `In` means the point is within the geometry (excluding edges and vertices). +# `On` means the point is on an edge or a vertex of the geometry. +# `Out` means the point is outside of the geometry. +# """ +# point_in_geom( +# point, geom; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T} = point_in_geom( +# GI.trait(point), point, +# GI.trait(geom), geom; +# in = in, on = on, out = out, +# ) + +# """ +# line_in_geom( +# line, geom; +# in::T = 1, on::T = -1, out::T = 0, +# )::T + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# `In` means the line is within geometry (no segments on edges and vertices). +# `On` means the line has at least one segment on a geometry edge or a vertex. +# `Out` means the line has at least one segment outside of the geometry. +# """ +# line_in_geom( +# line, geom; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T} = line_in_geom( +# GI.trait(line), line, +# GI.trait(geom), geom; +# in = in, on = on, out = out, # ) -""" - point_in_geom( - ::GI.PointTrait, point, - ::GI.LineStringTrait, line; - in::T = 1, on::T = -1, out::T = 0, - )::T - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -Note that a point can only be within a linestring if the linestring is closed, -by having an explicilty repeated last point. Even then, this means the point is -within the ring created by the linestring, not on the linestring itself. -`In` means the point is within the linestring (excluding edges and vertices). -`On` means the point is on an edge or a vertex of the linestring. -`Out` means the point is outside of the linestring. -""" -function point_in_geom( - ::GI.PointTrait, point, - ::GI.LineStringTrait, line; - in::T = 1, on::T = -1, out::T = 0, -) where {T} - results = if equals( - GI.getpoint(line, 1), - GI.getpoint(line, GI.npoint(line)), - ) - _point_in_extent(point, GI.extent(line)) || return out - _point_in_closed_curve(point, line; in = in, on = on, out = out) - else - @warn "Linestring isn't closed. Point cannot be 'in' linestring." - out - end - return results -end - -""" - line_in_geom( - ::GI.LineStringTrait, line1, - ::GI.LineStringTrait, line2; - in::T = 1, on::T = -1, out::T = 0, - ) - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -Note that a linestring can only be within a linestring if that linestring is -closed by having an explicilty repeated last point. Even then, this means the -point is within the ring created by the linestring, not on the linestring -itself. -`In` means the line is within geometry (no segments on edges and vertices). -`On` means the line has at least one segment on a geometry edge or a vertex. -`Out` means the line has at least one segment outside of the geometry. -""" -function line_in_geom( - ::GI.LineStringTrait, line1, - ::GI.LineStringTrait, line2; - in::T = 1, on::T = -1, out::T = 0, -) where {T} - results = if equals( # if line2 is closed by a repeated last point - GI.getpoint(line2, 1), - GI.getpoint(line2, GI.npoint(line2)), - ) - Extents.intersects( - GI.extent(line1), - GI.extent(line2), - ) || return out - _line_in_closed_curve( - line1, line2; - close = false, - ) - else - @warn "Linestring isn't closed. Point cannot be 'in' linestring." - out - end - return results -end - -# function ring_in_geom(::GI.LinearRingTrait, ring, ::GI.LineStringTrait, line) +# # ring_in_geom(ring, geom) = ring_in_geom( +# # GI.trait(ring), ring, +# # GI.trait(geom), geom, +# # ) + +# """ +# point_in_geom( +# ::GI.PointTrait, point, +# ::GI.LineStringTrait, line; +# in::T = 1, on::T = -1, out::T = 0, +# )::T + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# Note that a point can only be within a linestring if the linestring is closed, +# by having an explicilty repeated last point. Even then, this means the point is +# within the ring created by the linestring, not on the linestring itself. +# `In` means the point is within the linestring (excluding edges and vertices). +# `On` means the point is on an edge or a vertex of the linestring. +# `Out` means the point is outside of the linestring. +# """ +# function point_in_geom( +# ::GI.PointTrait, point, +# ::GI.LineStringTrait, line; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T} # results = if equals( # GI.getpoint(line, 1), # GI.getpoint(line, GI.npoint(line)), # ) +# _point_in_extent(point, GI.extent(line)) || return out +# _point_in_closed_curve(point, line; in = in, on = on, out = out) +# else +# @warn "Linestring isn't closed. Point cannot be 'in' linestring." +# out +# end +# return results +# end + +# """ +# line_in_geom( +# ::GI.LineStringTrait, line1, +# ::GI.LineStringTrait, line2; +# in::T = 1, on::T = -1, out::T = 0, +# ) + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# Note that a linestring can only be within a linestring if that linestring is +# closed by having an explicilty repeated last point. Even then, this means the +# point is within the ring created by the linestring, not on the linestring +# itself. +# `In` means the line is within geometry (no segments on edges and vertices). +# `On` means the line has at least one segment on a geometry edge or a vertex. +# `Out` means the line has at least one segment outside of the geometry. +# """ +# function line_in_geom( +# ::GI.LineStringTrait, line1, +# ::GI.LineStringTrait, line2; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T} +# results = if equals( # if line2 is closed by a repeated last point +# GI.getpoint(line2, 1), +# GI.getpoint(line2, GI.npoint(line2)), +# ) # Extents.intersects( -# GI.extent(ring), -# GI.extent(line), -# ) || return (false, false) -# _line_in_closed_curve(ring, line; close = true) +# GI.extent(line1), +# GI.extent(line2), +# ) || return out +# _line_in_closed_curve( +# line1, line2; +# close = false, +# ) # else # @warn "Linestring isn't closed. Point cannot be 'in' linestring." -# (false, false) +# out # end # return results # end -""" - point_in_geom( - ::GI.PointTrait, point, - ::GI.LinearRingTrait, ring; - in::T = 1, on::T = -1, out::T = 0 - )::T - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means the point is within the linear ring (excluding edges and vertices). -`On` means the point is on an edge or a vertex of the linear ring. -`Out` means the point is outside of the linear ring. -""" -function point_in_geom( - ::GI.PointTrait, point, - ::GI.LinearRingTrait, ring; - in::T = 1, on::T = -1, out::T = 0 -) where {T} - _point_in_extent(point, GI.extent(ring)) || return out - return _point_in_closed_curve(point, ring; in = in, on = on, out = out) -end - -""" - line_in_geom( - ::GI.LineStringTrait, line, - ::GI.LinearRingTrait, ring; - in::T = 1, on::T = -1, out::T = 0, - ) - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means the line is within the ring (no segments on edges and vertices). -`On` means the line has at least one segment on a ring edge or a vertex. -`Out` means the line has at least one segment outside of the ring. -""" -function line_in_geom( - ::GI.LineStringTrait, line, - ::GI.LinearRingTrait, ring; - in::T = 1, on::T = -1, out::T = 0, -) where {T} - Extents.intersects(GI.extent(line), GI.extent(ring)) || return out - return _line_in_closed_curve( - line, ring; - close = false, - ) -end - -# function ring_in_geom(::GI.LinearRingTrait, ring1, ::GI.LinearRingTrait, ring2) -# Extents.intersects(GI.extent(ring1), GI.extent(ring2)) || return (false, false) -# _line_in_closed_curve(ring1, ring2; close = true) + +# # function ring_in_geom(::GI.LinearRingTrait, ring, ::GI.LineStringTrait, line) +# # results = if equals( +# # GI.getpoint(line, 1), +# # GI.getpoint(line, GI.npoint(line)), +# # ) +# # Extents.intersects( +# # GI.extent(ring), +# # GI.extent(line), +# # ) || return (false, false) +# # _line_in_closed_curve(ring, line; close = true) +# # else +# # @warn "Linestring isn't closed. Point cannot be 'in' linestring." +# # (false, false) +# # end +# # return results +# # end +# """ +# point_in_geom( +# ::GI.PointTrait, point, +# ::GI.LinearRingTrait, ring; +# in::T = 1, on::T = -1, out::T = 0 +# )::T + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# `In` means the point is within the linear ring (excluding edges and vertices). +# `On` means the point is on an edge or a vertex of the linear ring. +# `Out` means the point is outside of the linear ring. +# """ +# function point_in_geom( +# ::GI.PointTrait, point, +# ::GI.LinearRingTrait, ring; +# in::T = 1, on::T = -1, out::T = 0 +# ) where {T} +# _point_in_extent(point, GI.extent(ring)) || return out +# return _point_in_closed_curve(point, ring; in = in, on = on, out = out) # end -# function polygon_in_geom(::GI.PolygonTrait, poly, ::GI.LinearRingTrait, ring) -# Extents.intersects(GI.extent(poly), GI.extent(ring)) || return (false, false) -# return _line_in_closed_curve(GI.getexterior(poly), ring; close = true) +# """ +# line_in_geom( +# ::GI.LineStringTrait, line, +# ::GI.LinearRingTrait, ring; +# in::T = 1, on::T = -1, out::T = 0, +# ) + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# `In` means the line is within the ring (no segments on edges and vertices). +# `On` means the line has at least one segment on a ring edge or a vertex. +# `Out` means the line has at least one segment outside of the ring. +# """ +# function line_in_geom( +# ::GI.LineStringTrait, line, +# ::GI.LinearRingTrait, ring; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T} +# Extents.intersects(GI.extent(line), GI.extent(ring)) || return out +# return _line_in_closed_curve( +# line, ring; +# close = false, +# ) # end -""" - point_in_geom( - ::GI.PointTrait, point, - ::GI.PolygonTrait, poly; - in::T = 1, on::T = -1, out::T = 0, - )::T - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means the point is within polygon (excluding edges, vertices, and holes). -`On` means the point is on an edge or a vertex of the polygon. -`Out` means the point is outside of the polygon, including within holes. -""" -function point_in_geom( - ::GI.PointTrait, point, - ::GI.PolygonTrait, poly; - in::T = 1, on::T = -1, out::T = 0, -) where {T} - _point_in_extent(point, GI.extent(poly)) || return out - ext_val = _point_in_closed_curve( - point, GI.getexterior(poly); - in = in, on = on, out = out, - ) - ext_val == on && return ext_val - in_out_counter = (ext_val == in) ? 1 : 0 - for ring in GI.gethole(poly) - hole_val = _point_in_closed_curve( - point, ring; - in = in, on = on, out = out, - ) - hole_val == on && return hole_val - in_out_counter += (hole_val == in) ? 1 : 0 - end - return iseven(in_out_counter) ? out : in -end - -""" - point_in_polygon( - point, polygon; - in::T = 1, on::T = -1, out::T = 0, - )::T - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means the point is within polygon (excluding edges, vertices, and holes). -`On` means the point is on an edge or a vertex of the polygon. -`Out` means the point is outside of the polygon, including within holes. -""" -point_in_polygon( - point, polygon; - in::T = 1, on::T = -1, out::T = 0, -) where {T} = point_in_polygon( - GI.trait(point), point, - GI.trait(polygon), polygon; - in = in, on = on, out = out, -) - -""" - point_in_polygon( - trait1::GI.PointTrait, point, - trait2::GI.PolygonTrait, poly; - in::T = 1, on::T = -1, out::T = 0, - )::T - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means the point is within polygon (excluding edges, vertices, and holes). -`On` means the point is on an edge or a vertex of the polygon. -`Out` means the point is outside of the polygon, including within holes. - -Note that this is the same as point_in_geom dispatched on a polygon. -""" -point_in_polygon( - trait1::GI.PointTrait, point, - trait2::GI.PolygonTrait, poly; - in::T = 1, on::T = -1, out::T = 0, -) where {T}= point_in_geom( - trait1, point, - trait2, poly; - in = in, on = on, out = out, -) - -""" - point_in_geom( - ::GI.LineStringTrait, line, - ::GI.PolygonTrait, poly; - in::T = 1, on::T = -1, out::T = 0, - ) - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means the line is within the polygon (no segments on edges, vertices, or - holes). -`On` means the line has at least one segment on a polygon edge or a vertex. -`Out` means the line has at least one segment outside of the polygon (including - within a hole). -""" -function point_in_geom( - ::GI.LineStringTrait, line, - ::GI.PolygonTrait, poly; - in::T = 1, on::T = -1, out::T = 0, -) where {T} - Extents.intersects(GI.extent(line), GI.extent(ring)) || return out - ext_val = _line_in_closed_curve( - line, GI.getexterior(poly); - close = false, - ) - - for ring in GI.gethole(poly) - hole_val = _point_in_closed_curve( - point, ring; - in = in, on = on, out = out, - ) - hole_val == on && return hole_val - in_out_counter += (hole_val == in) ? 1 : 0 - end - return iseven(in_out_counter) ? out : in -end - -# line_in_polygon( -# line, poly; +# # function ring_in_geom(::GI.LinearRingTrait, ring1, ::GI.LinearRingTrait, ring2) +# # Extents.intersects(GI.extent(ring1), GI.extent(ring2)) || return (false, false) +# # _line_in_closed_curve(ring1, ring2; close = true) +# # end + +# # function polygon_in_geom(::GI.PolygonTrait, poly, ::GI.LinearRingTrait, ring) +# # Extents.intersects(GI.extent(poly), GI.extent(ring)) || return (false, false) +# # return _line_in_closed_curve(GI.getexterior(poly), ring; close = true) +# # end + +# """ +# point_in_geom( +# ::GI.PointTrait, point, +# ::GI.PolygonTrait, poly; +# in::T = 1, on::T = -1, out::T = 0, +# )::T + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# `In` means the point is within polygon (excluding edges, vertices, and holes). +# `On` means the point is on an edge or a vertex of the polygon. +# `Out` means the point is outside of the polygon, including within holes. +# """ +# function point_in_geom( +# ::GI.PointTrait, point, +# ::GI.PolygonTrait, poly; # in::T = 1, on::T = -1, out::T = 0, -# ) where {T} = line_in_geom( -# line, GI.trait(line), -# poly, GI.trait(poly); +# ) where {T} +# _point_in_extent(point, GI.extent(poly)) || return out +# ext_val = _point_in_closed_curve( +# point, GI.getexterior(poly); # in = in, on = on, out = out, # ) +# ext_val == on && return ext_val +# in_out_counter = (ext_val == in) ? 1 : 0 +# for ring in GI.gethole(poly) +# hole_val = _point_in_closed_curve( +# point, ring; +# in = in, on = on, out = out, +# ) +# hole_val == on && return hole_val +# in_out_counter += (hole_val == in) ? 1 : 0 +# end +# return iseven(in_out_counter) ? out : in +# end + +# """ +# point_in_polygon( +# point, polygon; +# in::T = 1, on::T = -1, out::T = 0, +# )::T + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# `In` means the point is within polygon (excluding edges, vertices, and holes). +# `On` means the point is on an edge or a vertex of the polygon. +# `Out` means the point is outside of the polygon, including within holes. +# """ +# point_in_polygon( +# point, polygon; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T} = point_in_polygon( +# GI.trait(point), point, +# GI.trait(polygon), polygon; +# in = in, on = on, out = out, +# ) + +# """ +# point_in_polygon( +# trait1::GI.PointTrait, point, +# trait2::GI.PolygonTrait, poly; +# in::T = 1, on::T = -1, out::T = 0, +# )::T + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# `In` means the point is within polygon (excluding edges, vertices, and holes). +# `On` means the point is on an edge or a vertex of the polygon. +# `Out` means the point is outside of the polygon, including within holes. + +# Note that this is the same as point_in_geom dispatched on a polygon. +# """ +# point_in_polygon( +# trait1::GI.PointTrait, point, +# trait2::GI.PolygonTrait, poly; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T}= point_in_geom( +# trait1, point, +# trait2, poly; +# in = in, on = on, out = out, +# ) + +# """ +# point_in_geom( +# ::GI.LineStringTrait, line, +# ::GI.PolygonTrait, poly; +# in::T = 1, on::T = -1, out::T = 0, +# ) + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# `In` means the line is within the polygon (no segments on edges, vertices, or +# holes). +# `On` means the line has at least one segment on a polygon edge or a vertex. +# `Out` means the line has at least one segment outside of the polygon (including +# within a hole). +# """ +# function point_in_geom( +# ::GI.LineStringTrait, line, +# ::GI.PolygonTrait, poly; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T} +# Extents.intersects(GI.extent(line), GI.extent(ring)) || return out +# ext_val = _line_in_closed_curve( +# line, GI.getexterior(poly); +# close = false, +# ) + +# for ring in GI.gethole(poly) +# hole_val = _point_in_closed_curve( +# point, ring; +# in = in, on = on, out = out, +# ) +# hole_val == on && return hole_val +# in_out_counter += (hole_val == in) ? 1 : 0 +# end +# return iseven(in_out_counter) ? out : in +# end + +# # line_in_polygon( +# # line, poly; +# # in::T = 1, on::T = -1, out::T = 0, +# # ) where {T} = line_in_geom( +# # line, GI.trait(line), +# # poly, GI.trait(poly); +# # in = in, on = on, out = out, +# # ) -# ring_in_geom(::GI.LinearRingTrait, ring, ::GI.PolygonTrait, poly) = -# _geom_in_polygon(ring, poly; close = true) +# # ring_in_geom(::GI.LinearRingTrait, ring, ::GI.PolygonTrait, poly) = +# # _geom_in_polygon(ring, poly; close = true) -# function polygon_in_geom(::GI.PolygonTrait, poly1, ::GI.PolygonTrait, poly2) -# # Cheaply check that the point is inside the polygon extent -# Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return (false, false) -# # Make sure exterior of poly1 is within exterior of poly2 -# in_ext, some_on_ext = _line_in_closed_curve( -# GI.getexterior(poly1), GI.getexterior(poly2); -# close = true, +# # function polygon_in_geom(::GI.PolygonTrait, poly1, ::GI.PolygonTrait, poly2) +# # # Cheaply check that the point is inside the polygon extent +# # Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return (false, false) +# # # Make sure exterior of poly1 is within exterior of poly2 +# # in_ext, some_on_ext = _line_in_closed_curve( +# # GI.getexterior(poly1), GI.getexterior(poly2); +# # close = true, +# # ) +# # # poly1 not within poly2's external ring +# # (in_ext || some_on_ext) || return (false, false) +# # # Check if the geom is in any of the holes +# # outside_hole, some_on_hole = true, false +# # for hole in GI.gethole(poly) +# # outside_hole, some_on_hole = _line_in_closed_curve( +# # geom, hole; +# # close = close, in = false, +# # ) +# # # geom is in a hole -> not in polygon +# # !(outside_hole || some_on_hole) && return (false, false) +# # end +# # return (in_ext && outside_hole, some_on_hole || some_on_ext) # geom is inside of polygon +# # end + +# # function polygon_in_polygon(poly1, poly2) +# # # edges1, edges2 = to_edges(poly1), to_edges(poly2) +# # # extent1, extent2 = to_extent(edges1), to_extent(edges2) +# # # Check the extents intersect +# # Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false + +# # # Check all points in poly1 are in poly2 +# # for point in GI.getpoint(poly1) +# # point_in_polygon(point, poly2) || return false +# # end + +# # # Check the line of poly1 does not intersect the line of poly2 +# # intersects(poly1, poly2) && return false + +# # # poly1 must be in poly2 +# # return true +# # end + +# """ +# _point_in_closed_curve( +# point, curve; +# in::T = 1, on::T = -1, out::T = 0, +# )::T + +# Determine if point is in, on, or out of a closed curve. Point should be an +# object of Point trait and curve should be a linearstring or ring, that is +# assumed to be closed, regardless of repeated last point. + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# `In` means the point is within the closed curve (excluding edges and vertices). +# `On` means the point is on an edge or a vertex of the closed curve. +# `Out` means the point is outside of the closed curve. + +# Note that this uses the Algorithm by Hao and Sun (2018): +# https://doi.org/10.3390/sym10100477 +# Paper seperates orientation of point and edge into 26 cases. For each case, it +# is either a case where the point is on the edge (returns on), where a ray from +# the point (x, y) to infinity along the line y = y cut through the edge (k += 1), +# or the ray does not pass through the edge (do nothing and continue). If the ray +# passes through an odd number of edges, it is within the curve, else outside of +# of the curve if it didn't return 'on'. +# See paper for more information on cases denoted in comments. +# """ +# function _point_in_closed_curve( +# point, curve; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T} +# x, y = GI.x(point), GI.y(point) +# n = GI.npoint(curve) +# n -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) ? 1 : 0 +# k = 0 # counter for ray crossings +# p_start = GI.getpoint(curve, n) +# @inbounds for i in 1:n +# p_end = GI.getpoint(curve, i) +# v1 = GI.y(p_start) - y +# v2 = GI.y(p_end) - y +# if !((v1 < 0 && v2 < 0) || (v1 > 0 && v2 > 0)) # if not cases 11 or 26 +# u1 = GI.x(p_start) - x +# u2 = GI.x(p_end) - x +# f = u1 * v2 - u2 * v1 +# if v2 > 0 && v1 ≤ 0 # Case 3, 9, 16, 21, 13, or 24 +# f == 0 && return on # Case 16 or 21 +# f > 0 && (k += 1) # Case 3 or 9 +# elseif v1 > 0 && v2 ≤ 0 # Case 4, 10, 19, 20, 12, or 25 +# f == 0 && return on # Case 19 or 20 +# f < 0 && (k += 1) # Case 4 or 10 +# elseif v2 == 0 && v1 < 0 # Case 7, 14, or 17 +# f == 0 && return on # Case 17 +# elseif v1 == 0 && v2 < 0 # Case 8, 15, or 18 +# f == 0 && return on # Case 18 +# elseif v1 == 0 && v2 == 0 # Case 1, 2, 5, 6, 22, or 23 +# u2 ≤ 0 && u1 ≥ 0 && return on # Case 1 +# u1 ≤ 0 && u2 ≥ 0 && return on # Case 2 +# end +# end +# p_start = p_end +# end +# return iseven(k) ? out : in +# end + +# """ +# line_in_closed_curve( +# line, curve; +# in::T = 1, on::T = -1, out::T = 0, +# close = false, +# ) + +# Determine if line is in, on, or out of a closed curve. Both the line and curve +# should be an object of linestring or linearring trait. The curve is assumed to +# be closed, regardless of repeated last point. + +# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. +# `In` means line is within the curve (no segments on edges, vertices, or holes). +# `On` means line has at least one segment on a curve edge or vertex. +# `Out` means the line has at least one segment outside of the curve. + +# This algorithm functions by checking if the first point of the line is within +# the curve. If not, then the line is not within the curve, if so, we check for +# intersections between the line and curve, as this would mean a part of the line +# is outside of the curve. We take special care of intersections through vertices +# as it isn't clearcut if those neccesitate a segment of the line being outside +# of the curve. +# """ +# _line_in_closed_curve(line, curve; +# exclude_boundaries = false, +# close = false, +# ) = _line_in_out_closed_curve( +# line, curve; +# disjoint = false, +# exclude_boundaries = exclude_boundaries, +# close = close, +# ) + +# # function _line_in_closed_curve( +# # line, curve; +# # in::T = 1, on::T = -1, out::T = 0, +# # close = false, +# # ) where {T} +# # # Determine number of points in curve and line +# # nc = GI.npoint(curve) +# # nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 +# # nl = GI.npoint(line) +# # nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 +# # # Check to see if first point in line is within curve +# # point_val = _point_in_closed_curve( +# # GI.getpoint(line, 1), curve; +# # in = in, on = on, out = out, +# # ) +# # # point is outside curve, line can't be within curve +# # point_val == out && return out +# # # Check for any intersections between line and curve +# # line_on_curve = point_val == on # record if line is "on" part of curve +# # l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) +# # for i in (close ? 1 : 2):nl +# # l_end = _tuple_point(GI.getpoint(line, i)) +# # c_start = _tuple_point(GI.getpoint(curve, nc)) +# # for j in 1:nc +# # c_end = _tuple_point(GI.getpoint(curve, j)) +# # # Check if edges intersect --> line is not within curve +# # meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) +# # # open line segments meet in a single point +# # meet_type == 1 && return out +# # #= +# # closed line segments meet in one or several points -> meet at a +# # vertex or on the edge itself (parallel) +# # =# +# # if meet_type == 0 +# # line_on_curve = true +# # # See if segment is parallel and within curve edge +# # p1_on_seg = point_on_segment(l_start, c_start, c_end) +# # p2_on_seg = point_on_segment(l_end, c_start, c_end) +# # # if segment isn't contained within curve edge +# # if !p1_on_seg || !p2_on_seg +# # # Make sure l_start is in or on the segment +# # p1_in_curve = +# # p1_on_seg || +# # _point_in_closed_curve( +# # l_start, curve; +# # in = in, on = on, out = out, +# # ) != out +# # !p1_in_curve && return out +# # # Make sure l_end is in or on the segment +# # p2_in_curve = +# # p2_on_seg || +# # _point_in_closed_curve( +# # l_end, curve; +# # in = in, on = on, out = out, +# # ) != out +# # !p2_in_curve && return out +# # #= +# # If both endpoints are within or on the curve, but not +# # parallel to the edge, make sure that midpoints between the +# # intersections along the segment are within curve +# # =# +# # !_segment_mids_in_curve( +# # l_start, l_end, curve; +# # in = in, on = on, out = out, +# # ) && return out # point of segment is outside of curve +# # # line segment is fully within or on curve +# # break +# # end +# # end +# # c_start = c_end +# # end +# # l_start = l_end +# # end +# # # check if line is on any curve edges or vertcies +# # return line_on_curve ? on : in +# # end + +# """ +# _segment_mids_in_curve( +# l_start, l_end, curve; +# in::T = 1, on::T = -1, out::T = 0, +# ) + +# Given two points defining a line segment (both with point traits) and a +# curve (with a linestring or linearring trait), find the intersection points +# between them and sort them along the segment. Then, make sure that the +# midpoint between pairs of points along the line is within the curve. Returns +# true if all of the midpoints are within or on the curve and false otherwise. + +# Note: This function assumes that both of the endpoints of the line segment +# are on the curve! +# """ +# function _segment_mids_in_curve( +# l_start, l_end, curve; +# in::T = 1, on::T = -1, out::T = 0, +# ) where {T} +# # Find intersection points +# ipoints = intersection_points( +# GI.Line([l_start, l_end]), +# curve +# ) +# npoints = length(ipoints) +# if npoints < 3 # only intersection points are the endpoints +# mid_val = _point_in_closed_curve( +# (l_start .+ l_end) ./ 2, curve; +# in = in, on = on, out = out, +# ) +# mid_val == out && return false +# else # more intersection points than the endpoints +# # sort intersection points along the line +# sort!(ipoints, by = p -> euclid_distance(p, l_start)) +# p_start = ipoints[1] +# for i in 2:npoints +# p_end = ipoints[i] +# # check if midpoint of intersection points is within the curve +# mid_val = _point_in_closed_curve( +# (p_start .+ p_end) ./ 2, curve; +# in = in, on = on, out = out, +# ) +# # if it is out, return false +# mid_val == out && return false +# p_start = p_end +# end +# end +# return true # all intersection point midpoints were in or on the curve +# end + +# function _geom_in_polygon(geom, poly; close = false) +# # Cheaply check that the geom extent is inside the polygon extent +# Extents.intersects(GI.extent(geom), GI.extent(poly)) || return (false, false) +# # Check if geom is inside or on the exterior ring +# in_ext, on_ext = _line_in_closed_curve( +# geom, +# GI.getexterior(poly); +# close = close, # ) -# # poly1 not within poly2's external ring -# (in_ext || some_on_ext) || return (false, false) +# (in_ext || on_ext) || return (false, false) # geom isn't in external ring # # Check if the geom is in any of the holes -# outside_hole, some_on_hole = true, false # for hole in GI.gethole(poly) -# outside_hole, some_on_hole = _line_in_closed_curve( -# geom, hole; -# close = close, in = false, -# ) +# # out_of_hole, some_on_hole = _line_in_closed_curve( +# # geom, hole; +# # close = close, in = false, +# # ) # # geom is in a hole -> not in polygon -# !(outside_hole || some_on_hole) && return (false, false) +# !(out_of_hole || some_on_hole) && return (false, false) # end -# return (in_ext && outside_hole, some_on_hole || some_on_ext) # geom is inside of polygon +# return (in_ext, some_on_hole) # geom is inside of polygon # end -# function polygon_in_polygon(poly1, poly2) -# # edges1, edges2 = to_edges(poly1), to_edges(poly2) -# # extent1, extent2 = to_extent(edges1), to_extent(edges2) -# # Check the extents intersect -# Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false - -# # Check all points in poly1 are in poly2 -# for point in GI.getpoint(poly1) -# point_in_polygon(point, poly2) || return false -# end +# """ +# _point_in_extent(p, extent::Extents.Extent)::Bool -# # Check the line of poly1 does not intersect the line of poly2 -# intersects(poly1, poly2) && return false +# Returns true if the point is the bounding box of the extent and false otherwise. +# """ +# function _point_in_extent(p, extent::Extents.Extent) +# (x1, x2), (y1, y2) = extent.X, extent.Y +# return x1 ≤ GI.x(p) ≤ x2 && y1 ≤ GI.y(p) ≤ y2 +# end -# # poly1 must be in poly2 -# return true -# end - -""" - _point_in_closed_curve( - point, curve; - in::T = 1, on::T = -1, out::T = 0, - )::T - -Determine if point is in, on, or out of a closed curve. Point should be an -object of Point trait and curve should be a linearstring or ring, that is -assumed to be closed, regardless of repeated last point. - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means the point is within the closed curve (excluding edges and vertices). -`On` means the point is on an edge or a vertex of the closed curve. -`Out` means the point is outside of the closed curve. - -Note that this uses the Algorithm by Hao and Sun (2018): -https://doi.org/10.3390/sym10100477 -Paper seperates orientation of point and edge into 26 cases. For each case, it -is either a case where the point is on the edge (returns on), where a ray from -the point (x, y) to infinity along the line y = y cut through the edge (k += 1), -or the ray does not pass through the edge (do nothing and continue). If the ray -passes through an odd number of edges, it is within the curve, else outside of -of the curve if it didn't return 'on'. -See paper for more information on cases denoted in comments. -""" -function _point_in_closed_curve( - point, curve; - in::T = 1, on::T = -1, out::T = 0, -) where {T} - x, y = GI.x(point), GI.y(point) - n = GI.npoint(curve) - n -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) ? 1 : 0 - k = 0 # counter for ray crossings - p_start = GI.getpoint(curve, n) - @inbounds for i in 1:n - p_end = GI.getpoint(curve, i) - v1 = GI.y(p_start) - y - v2 = GI.y(p_end) - y - if !((v1 < 0 && v2 < 0) || (v1 > 0 && v2 > 0)) # if not cases 11 or 26 - u1 = GI.x(p_start) - x - u2 = GI.x(p_end) - x - f = u1 * v2 - u2 * v1 - if v2 > 0 && v1 ≤ 0 # Case 3, 9, 16, 21, 13, or 24 - f == 0 && return on # Case 16 or 21 - f > 0 && (k += 1) # Case 3 or 9 - elseif v1 > 0 && v2 ≤ 0 # Case 4, 10, 19, 20, 12, or 25 - f == 0 && return on # Case 19 or 20 - f < 0 && (k += 1) # Case 4 or 10 - elseif v2 == 0 && v1 < 0 # Case 7, 14, or 17 - f == 0 && return on # Case 17 - elseif v1 == 0 && v2 < 0 # Case 8, 15, or 18 - f == 0 && return on # Case 18 - elseif v1 == 0 && v2 == 0 # Case 1, 2, 5, 6, 22, or 23 - u2 ≤ 0 && u1 ≥ 0 && return on # Case 1 - u1 ≤ 0 && u2 ≥ 0 && return on # Case 2 - end - end - p_start = p_end - end - return iseven(k) ? out : in -end - -""" - line_in_closed_curve( - line, curve; - in::T = 1, on::T = -1, out::T = 0, - close = false, - ) - -Determine if line is in, on, or out of a closed curve. Both the line and curve -should be an object of linestring or linearring trait. The curve is assumed to -be closed, regardless of repeated last point. - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means line is within the curve (no segments on edges, vertices, or holes). -`On` means line has at least one segment on a curve edge or vertex. -`Out` means the line has at least one segment outside of the curve. - -This algorithm functions by checking if the first point of the line is within -the curve. If not, then the line is not within the curve, if so, we check for -intersections between the line and curve, as this would mean a part of the line -is outside of the curve. We take special care of intersections through vertices -as it isn't clearcut if those neccesitate a segment of the line being outside -of the curve. -""" -_line_in_closed_curve(line, curve; - exclude_boundaries = false, - close = false, -) = _line_in_out_closed_curve( - line, curve; - disjoint = false, - exclude_boundaries = exclude_boundaries, - close = close, -) - -# function _line_in_closed_curve( +# function _line_in_out_closed_curve( # line, curve; -# in::T = 1, on::T = -1, out::T = 0, +# disjoint = false, +# exclude_boundaries = false, # close = false, -# ) where {T} +# ) +# #= +# Set variables based off if we are determining within or disjoint. +# If `_point_in_closed_curve` returns `true_orientation` it is on the right +# side of the curve for the check. If it returns `false_orientation`, it is +# on the wrong side of the curve for the check. +# =# +# false_orientation = disjoint ? 1 : 0 # if checking within, want points in +# on = -1 # as used for point in closed curve + # # Determine number of points in curve and line # nc = GI.npoint(curve) # nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 # nl = GI.npoint(line) # nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 + # # Check to see if first point in line is within curve -# point_val = _point_in_closed_curve( -# GI.getpoint(line, 1), curve; -# in = in, on = on, out = out, -# ) -# # point is outside curve, line can't be within curve -# point_val == out && return out +# point_val = _point_in_closed_curve(GI.getpoint(line, 1), curve) +# # point is out (for within) or in curve (for disjoint) -> wrong orientation +# point_val == false_orientation && return false +# # point is on boundary and don't want boundary points -> wrong orientation +# exclude_boundaries && point_val == on && return false + # # Check for any intersections between line and curve -# line_on_curve = point_val == on # record if line is "on" part of curve # l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) # for i in (close ? 1 : 2):nl # l_end = _tuple_point(GI.getpoint(line, i)) # c_start = _tuple_point(GI.getpoint(curve, nc)) # for j in 1:nc # c_end = _tuple_point(GI.getpoint(curve, j)) -# # Check if edges intersect --> line is not within curve +# # Check if edges intersect --> line crosses --> wrong orientation # meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) # # open line segments meet in a single point -# meet_type == 1 && return out +# meet_type == 1 && return false # #= # closed line segments meet in one or several points -> meet at a # vertex or on the edge itself (parallel) # =# # if meet_type == 0 -# line_on_curve = true # # See if segment is parallel and within curve edge # p1_on_seg = point_on_segment(l_start, c_start, c_end) +# exclude_boundaries && p1_on_seg && return false # p2_on_seg = point_on_segment(l_end, c_start, c_end) +# exclude_boundaries && p2_on_seg && return false # # if segment isn't contained within curve edge # if !p1_on_seg || !p2_on_seg -# # Make sure l_start is in or on the segment -# p1_in_curve = -# p1_on_seg || -# _point_in_closed_curve( -# l_start, curve; -# in = in, on = on, out = out, -# ) != out -# !p1_in_curve && return out -# # Make sure l_end is in or on the segment -# p2_in_curve = -# p2_on_seg || -# _point_in_closed_curve( -# l_end, curve; -# in = in, on = on, out = out, -# ) != out -# !p2_in_curve && return out +# # Make sure l_start is in corrent orientation +# p1_val = p1_on_seg ? +# on : +# _point_in_closed_curve(l_start, curve) +# p1_val == false_orientation && return false +# exclude_boundaries && p1_val == on && return false +# # Make sure l_end is in is in corrent orientation +# p2_val = p2_on_seg ? +# on : +# _point_in_closed_curve(l_end, curve) +# p2_val == false_orientation && return false +# exclude_boundaries && p2_val == on && return false # #= -# If both endpoints are within or on the curve, but not +# If both endpoints are in the correct orientation, but not # parallel to the edge, make sure that midpoints between the -# intersections along the segment are within curve +# intersections along the segment are also in the correct +# orientation # =# -# !_segment_mids_in_curve( +# !_segment_mids_in_out_curve( # l_start, l_end, curve; -# in = in, on = on, out = out, -# ) && return out # point of segment is outside of curve +# disjoint = disjoint, +# exclude_boundaries = exclude_boundaries, +# ) && return false # midpoint on the wrong side of the curve # # line segment is fully within or on curve # break # end @@ -526,212 +694,44 @@ _line_in_closed_curve(line, curve; # l_start = l_end # end # # check if line is on any curve edges or vertcies -# return line_on_curve ? on : in +# return true # end -""" - _segment_mids_in_curve( - l_start, l_end, curve; - in::T = 1, on::T = -1, out::T = 0, - ) - - Given two points defining a line segment (both with point traits) and a - curve (with a linestring or linearring trait), find the intersection points - between them and sort them along the segment. Then, make sure that the - midpoint between pairs of points along the line is within the curve. Returns - true if all of the midpoints are within or on the curve and false otherwise. - - Note: This function assumes that both of the endpoints of the line segment - are on the curve! -""" -function _segment_mids_in_curve( - l_start, l_end, curve; - in::T = 1, on::T = -1, out::T = 0, -) where {T} - # Find intersection points - ipoints = intersection_points( - GI.Line([l_start, l_end]), - curve - ) - npoints = length(ipoints) - if npoints < 3 # only intersection points are the endpoints - mid_val = _point_in_closed_curve( - (l_start .+ l_end) ./ 2, curve; - in = in, on = on, out = out, - ) - mid_val == out && return false - else # more intersection points than the endpoints - # sort intersection points along the line - sort!(ipoints, by = p -> euclid_distance(p, l_start)) - p_start = ipoints[1] - for i in 2:npoints - p_end = ipoints[i] - # check if midpoint of intersection points is within the curve - mid_val = _point_in_closed_curve( - (p_start .+ p_end) ./ 2, curve; - in = in, on = on, out = out, - ) - # if it is out, return false - mid_val == out && return false - p_start = p_end - end - end - return true # all intersection point midpoints were in or on the curve -end - -function _geom_in_polygon(geom, poly; close = false) - # Cheaply check that the geom extent is inside the polygon extent - Extents.intersects(GI.extent(geom), GI.extent(poly)) || return (false, false) - # Check if geom is inside or on the exterior ring - in_ext, on_ext = _line_in_closed_curve( - geom, - GI.getexterior(poly); - close = close, - ) - (in_ext || on_ext) || return (false, false) # geom isn't in external ring - # Check if the geom is in any of the holes - for hole in GI.gethole(poly) - # out_of_hole, some_on_hole = _line_in_closed_curve( - # geom, hole; - # close = close, in = false, - # ) - # geom is in a hole -> not in polygon - !(out_of_hole || some_on_hole) && return (false, false) - end - return (in_ext, some_on_hole) # geom is inside of polygon -end - -""" - _point_in_extent(p, extent::Extents.Extent)::Bool - -Returns true if the point is the bounding box of the extent and false otherwise. -""" -function _point_in_extent(p, extent::Extents.Extent) - (x1, x2), (y1, y2) = extent.X, extent.Y - return x1 ≤ GI.x(p) ≤ x2 && y1 ≤ GI.y(p) ≤ y2 -end - -function _line_in_out_closed_curve( - line, curve; - disjoint = false, - exclude_boundaries = false, - close = false, -) - #= - Set variables based off if we are determining within or disjoint. - If `_point_in_closed_curve` returns `true_orientation` it is on the right - side of the curve for the check. If it returns `false_orientation`, it is - on the wrong side of the curve for the check. - =# - false_orientation = disjoint ? 1 : 0 # if checking within, want points in - on = -1 # as used for point in closed curve - - # Determine number of points in curve and line - nc = GI.npoint(curve) - nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 - nl = GI.npoint(line) - nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 - - # Check to see if first point in line is within curve - point_val = _point_in_closed_curve(GI.getpoint(line, 1), curve) - # point is out (for within) or in curve (for disjoint) -> wrong orientation - point_val == false_orientation && return false - # point is on boundary and don't want boundary points -> wrong orientation - exclude_boundaries && point_val == on && return false - - # Check for any intersections between line and curve - l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) - for i in (close ? 1 : 2):nl - l_end = _tuple_point(GI.getpoint(line, i)) - c_start = _tuple_point(GI.getpoint(curve, nc)) - for j in 1:nc - c_end = _tuple_point(GI.getpoint(curve, j)) - # Check if edges intersect --> line crosses --> wrong orientation - meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) - # open line segments meet in a single point - meet_type == 1 && return false - #= - closed line segments meet in one or several points -> meet at a - vertex or on the edge itself (parallel) - =# - if meet_type == 0 - # See if segment is parallel and within curve edge - p1_on_seg = point_on_segment(l_start, c_start, c_end) - exclude_boundaries && p1_on_seg && return false - p2_on_seg = point_on_segment(l_end, c_start, c_end) - exclude_boundaries && p2_on_seg && return false - # if segment isn't contained within curve edge - if !p1_on_seg || !p2_on_seg - # Make sure l_start is in corrent orientation - p1_val = p1_on_seg ? - on : - _point_in_closed_curve(l_start, curve) - p1_val == false_orientation && return false - exclude_boundaries && p1_val == on && return false - # Make sure l_end is in is in corrent orientation - p2_val = p2_on_seg ? - on : - _point_in_closed_curve(l_end, curve) - p2_val == false_orientation && return false - exclude_boundaries && p2_val == on && return false - #= - If both endpoints are in the correct orientation, but not - parallel to the edge, make sure that midpoints between the - intersections along the segment are also in the correct - orientation - =# - !_segment_mids_in_out_curve( - l_start, l_end, curve; - disjoint = disjoint, - exclude_boundaries = exclude_boundaries, - ) && return false # midpoint on the wrong side of the curve - # line segment is fully within or on curve - break - end - end - c_start = c_end - end - l_start = l_end - end - # check if line is on any curve edges or vertcies - return true -end - -function _segment_mids_in_out_curve( - l_start, l_end, curve; - disjoint = false, - exclude_boundaries = false, -) - false_orientation = disjoint ? 1 : 0 # if checking within, want points in - on = -1 # as used for point in closed curve - # Find intersection points - ipoints = intersection_points( - GI.Line([l_start, l_end]), - curve - ) - npoints = length(ipoints) - if npoints < 3 # only intersection points are the endpoints - mid_val = _point_in_closed_curve( - (l_start .+ l_end) ./ 2, curve; - in = in, on = on, out = out, - ) - mid_val == false_orientation && return false - exclude_boundaries && mid_val == on && return false - else # more intersection points than the endpoints - # sort intersection points along the line - sort!(ipoints, by = p -> euclid_distance(p, l_start)) - p_start = ipoints[1] - for i in 2:npoints - p_end = ipoints[i] - # check if midpoint of intersection points is within the curve - mid_val = _point_in_closed_curve( - (p_start .+ p_end) ./ 2, curve; - in = in, on = on, out = out, - ) - # if it is out, return false - mid_val == false_orientation && return false - exclude_boundaries && mid_val == on && return false - end - end - return true # all intersection point midpoints were in or on the curve -end +# function _segment_mids_in_out_curve( +# l_start, l_end, curve; +# disjoint = false, +# exclude_boundaries = false, +# ) +# false_orientation = disjoint ? 1 : 0 # if checking within, want points in +# on = -1 # as used for point in closed curve +# # Find intersection points +# ipoints = intersection_points( +# GI.Line([l_start, l_end]), +# curve +# ) +# npoints = length(ipoints) +# if npoints < 3 # only intersection points are the endpoints +# mid_val = _point_in_closed_curve( +# (l_start .+ l_end) ./ 2, curve; +# in = in, on = on, out = out, +# ) +# mid_val == false_orientation && return false +# exclude_boundaries && mid_val == on && return false +# else # more intersection points than the endpoints +# # sort intersection points along the line +# sort!(ipoints, by = p -> euclid_distance(p, l_start)) +# p_start = ipoints[1] +# for i in 2:npoints +# p_end = ipoints[i] +# # check if midpoint of intersection points is within the curve +# mid_val = _point_in_closed_curve( +# (p_start .+ p_end) ./ 2, curve; +# in = in, on = on, out = out, +# ) +# # if it is out, return false +# mid_val == false_orientation && return false +# exclude_boundaries && mid_val == on && return false +# end +# end +# return true # all intersection point midpoints were in or on the curve +# end diff --git a/src/methods/geom_on_geom.jl b/src/methods/geom_on_geom.jl deleted file mode 100644 index 110ed0cea..000000000 --- a/src/methods/geom_on_geom.jl +++ /dev/null @@ -1,71 +0,0 @@ -export point_on_line, line_on_line - -""" - point_on_line(point::Point, line::LineString; ignore_end_vertices::Bool=false)::Bool - -Return true if a point is on a line. Accept a optional parameter to ignore the -start and end vertices of the linestring. - -## Examples - -```jldoctest -import GeoInterface as GI, GeometryOps as GO - -point = (1, 1) -line = GI.LineString([(0, 0), (3, 3), (4, 4)]) -GO.point_on_line(point, line) - -# output -true -``` -""" -function point_on_line(point, line; ignore_end_vertices::Bool=false)::Bool - line_points = tuple_points(line) - n = length(line_points) - - exclude_boundary = :none - for i in 1:n - 1 - if ignore_end_vertices - if i === 1 - exclude_boundary = :start - elseif i === n - 2 - exclude_boundary = :end - elseif (i === 1 && i + 1 === n - 1) - exclude_boundary = :both - end - end - if point_on_segment(point, line_points[i], line_points[i + 1]) - return true - end - end - return false -end - -function point_on_segment(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) - Δx = x2 - x1 - Δy = y2 - y1 - #= - Determine if the point is on the segment -> see if cross product of line and - vector from line start to point is zero -> vectors are parallel. Then, check - point is between segment endpoints. - =# - on_line = _isparallel(Δx, Δy, (x - x1), (y - y1)) - between_endpoints = (x2 > x1 ? x1 <= x <= x2 : x2 <= x <= x1) && - (y2 > y1 ? y1 <= y <= y2 : y2 <= y <= y1) - return on_line && between_endpoints -end - - -line_on_line(line1, line2) = line_on_line(trait(line1), line1, trait(line2), line2) -function line_on_line(t1::GI.AbstractCurveTrait, line1, t2::AbstractCurveTrait, line2) - for p in GI.getpoint(line1) - # FIXME: all points being on the line doesn't - # actually mean the whole line is on the line... - point_on_line(p, line2) || return false - end - return true -end diff --git a/src/methods/within.jl b/src/methods/within.jl index d48bd6540..d2333bfc0 100644 --- a/src/methods/within.jl +++ b/src/methods/within.jl @@ -64,7 +64,9 @@ within( process = within_process, exclude_boundaries = true, ) -# Lines in geometries +# Lines in/on geometries + +# Lines in/on lines within( ::GI.LineStringTrait, line, ::GI.LineStringTrait, curve, @@ -76,6 +78,7 @@ within( closed_curve = false, ) +# Lines in/on rings within( ::GI.LineStringTrait, line, ::GI.LinearRingTrait, ring, @@ -87,6 +90,7 @@ within( closed_curve = true, ) +# Lines in/on polygons within( ::GI.LineStringTrait, line, ::GI.PolygonTrait, polygon, @@ -97,7 +101,9 @@ within( close = false, ) -# Rings in geometries +# Rings in/on geometries + +# Rings in/on lines within( ::GI.LinearRingTrait, line, ::GI.LineStringTrait, curve, @@ -109,6 +115,7 @@ within( closed_curve = false, ) +# Rings in/on rings within( ::GI.LinearRingTrait, line, ::GI.LinearRingTrait, ring, @@ -120,18 +127,45 @@ within( closed_curve = true, ) +# Rings in/on polygons within( ::GI.LinearRingTrait, line, ::GI.PolygonTrait, polygon, ) = _line_polygon_process( line, polygon; process = within_process, - exclude_boundaries = true, + exclude_boundaries = false, close = true, ) -# Polygons within geometries -within(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool = polygon_in_polygon(g1, g2) +# Polygons within polygons +function within( + ::GI.PolygonTrait, poly1, + ::GI.PolygonTrait, poly2; +) + if _line_polygon_process( + GI.getexterior(poly1), poly2; + process = within_process, + exclude_boundaries = false, + close = true, + line_is_poly_ring = true + ) + for hole in GI.gethole(poly2) + if _line_polygon_process( + hole, poly1; + process = within_process, + exclude_boundaries = false, + close = true, + line_is_poly_ring = true + ) + return false + end + end + return true + end + return false +end + # Everything not specified # TODO: Add multipolygons diff --git a/src/try.jl b/src/try.jl index ba8338ab9..e3250f559 100644 --- a/src/try.jl +++ b/src/try.jl @@ -2,9 +2,7 @@ import GeometryOps as GO import GeoInterface as GI import LibGEOS as LG +p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) -r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) -l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) - -GO.within(l2, r3) +GO.within(p1, p1) \ No newline at end of file diff --git a/test/methods/within.jl b/test/methods/within.jl index 244184c10..23ffd0c48 100644 --- a/test/methods/within.jl +++ b/test/methods/within.jl @@ -22,6 +22,9 @@ l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0 r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) +r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) +r5 = LG.LinearRing([[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]) +r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) p2 = LG.Polygon([ @@ -29,6 +32,32 @@ p2 = LG.Polygon([ [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] ]) +p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) +p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) +p5 = LG.Polygon([[[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]]) +p6 = LG.Polygon([[[0.1, 0.4], [0.1, 0.8], [0.3, 0.8], [0.3, 0.4], [0.1, 0.4]]]) +p7 = LG.Polygon([ + [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], + [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]], +]) +p8 = LG.Polygon([ + [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], + [[0.3, 0.3], [0.3, 0.7], [0.7, 0.7], [0.7, 0.3], [0.3, 0.3]] +]) +p9 = LG.Polygon([ + [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], + [[0.45, 0.45], [0.45, 0.55], [0.55, 0.55], [0.55, 0.45], [0.45, 0.45]] +]) +p10 = LG.Polygon([ + [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], + [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] +]) +p11 = LG.Polygon([ + [[0.45, 0.5], [0.45, 0.75], [0.55, 0.75], [0.55, 0.5], [0.45, 0.5]] +]) +p12 = LG.Polygon([ + [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] +]) # Point and point @test GO.within(pt1, pt1) == LG.within(pt1, pt1) @@ -101,5 +130,46 @@ p2 = LG.Polygon([ @test GO.within(r1, r3) == LG.within(r1, r3) # Ring and polygon +# Ring is equal to polygon's external ring, no holes +@test GO.within(r1, p1) == LG.within(r1, p1) +# Ring goes outside of polygon's external ring +@test GO.within(r1, p2) == LG.within(r1, p1) +# Ring is within polygon, but also on edges +@test GO.within(r2, p2) == LG.within(r2, p2) +# Ring is within polygon, but also on edges +@test GO.within(r3, p2) == LG.within(r3, p2) +# Ring is one of polygon's holes +@test GO.within(r4, p2) == LG.within(r4, p2) +# Ring is fully within polygon that has holes +@test GO.within(r5, p2) == LG.within(r5, p2) +# Ring is fully within polygon's hole +@test GO.within(r6, p2) == LG.within(r6, p2) # Polygon in polygon + +# Same polygon +@test GO.within(p1, p1) == LG.within(p1, p1) +@test GO.within(p2, p2) == LG.within(p2, p2) +# Polygon not in polygon +@test GO.within(p1, p2) == LG.within(p1, p2) +@test GO.within(p2, p1) == LG.within(p2, p1) +# Ring is within polygon, but also on edges +@test GO.within(p3, p2) == LG.within(p3, p2) +# Polygon within polygon with holes +@test GO.within(p4, p2) == LG.within(p4, p2) +# Polygon within polygon hole --> not within +@test GO.within(p5, p2) == LG.within(p5, p2) +# Polygon overlapping with other polygon's hole +@test GO.within(p6, p2) == LG.within(p6, p2) +# Polygon with hole nested with other polygon's hole --> within +@test GO.within(p8, p7) == LG.within(p8, p7) +# Nested holes but not within +@test GO.within(p9, p7) == LG.within(p9, p7) +# Nested with same hole +@test GO.within(p10, p7) == LG.within(p10, p7) +# within external ring but intersects with hole +@test GO.within(p11, p7) == LG.within(p11, p7) + +@test GO.within(p12, p7) == LG.within(p12, p7) + + From c8d71ad7731c189c5e3f9a3265d454bd548cc2f3 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Thu, 30 Nov 2023 21:08:33 -0800 Subject: [PATCH 17/33] Add multipolygon tests --- src/methods/within.jl | 25 +++++++- test/methods/within.jl | 128 ++++++++++++++++++++++++++++++++++------- 2 files changed, 130 insertions(+), 23 deletions(-) diff --git a/src/methods/within.jl b/src/methods/within.jl index d2333bfc0..64c9a54c1 100644 --- a/src/methods/within.jl +++ b/src/methods/within.jl @@ -166,9 +166,30 @@ function within( return false end +function within(::GI.AbstractTrait, g1, ::GI.MultiPolygonTrait, g2) + for poly in GI.getpolygon(g2) + if within(g1, poly) + return true + end + end + return false +end + +function within(::GI.MultiPolygonTrait, g1, ::GI.MultiPolygonTrait, g2) + for poly1 in GI.getpolygon(g1) + poly1_within = false + for poly2 in GI.getpolygon(g2) + if within(poly1, poly2) + poly1_within = true + break + end + end + !poly1_within && return false + end + return true +end # Everything not specified -# TODO: Add multipolygons -within(::GI.AbstractTrait, g1, ::GI.AbstractCurveTrait, g2)::Bool = false +within(::GI.AbstractTrait, g1, ::GI.AbstractTrait, g2)::Bool = false diff --git a/test/methods/within.jl b/test/methods/within.jl index 23ffd0c48..641b9319f 100644 --- a/test/methods/within.jl +++ b/test/methods/within.jl @@ -1,3 +1,4 @@ +# Test points pt1 = LG.Point([0.0, 0.0]) pt2 = LG.Point([0.0, 0.1]) pt3 = LG.Point([1.0, 0.0]) @@ -5,7 +6,9 @@ pt4 = LG.Point([0.5, 1.0]) pt5 = LG.Point([0.2, 0.5]) pt6 = LG.Point([0.3, 0.55]) pt7 = LG.Point([0.6, 0.49]) - +pt8 = LG.Point([0.25, 0.75]) +pt9 = LG.Point([0.5, 0.1]) +# Test lines l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) @@ -18,13 +21,16 @@ l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) - +l13 = LG.LineString([[0.5, 0.01], [0.5, 0.09]]) +# Test rings r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) r5 = LG.LinearRing([[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]) r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) +r7 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.5, 0.3], [0.0, 0.3],[0.0, 0.0]]) +# Test polygons p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) p2 = LG.Polygon([ @@ -58,78 +64,123 @@ p11 = LG.Polygon([ p12 = LG.Polygon([ [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] ]) +# Test multipolygons +m1 = LG.MultiPolygon([p3, p6]) +m2 = LG.MultiPolygon([p3, p4]) -# Point and point +# # Point and point +# Equal points @test GO.within(pt1, pt1) == LG.within(pt1, pt1) +# Different points @test GO.within(pt1, pt2) == LG.within(pt1, pt2) -# Point and line +# # Point and line +# Line endpoint (1 segment) @test GO.within(pt1, l1) == LG.within(pt1, l1) +# Line endpoint (2 segments) @test GO.within(pt2, l2) == LG.within(pt2, l2) +# Middle of line (1 segment) @test GO.within(pt2, l1) == LG.within(pt2, l1) +# Not on line (1 segment) @test GO.within(pt3, l1) == LG.within(pt3, l1) -@test GO.within(pt1, l2) == LG.within(pt1, l2) -@test GO.within(pt2, l2) == LG.within(pt2, l2) +# Middle of line on joint (2 segments) @test GO.within(pt3, l2) == LG.within(pt3, l2) +# Endpoint on closed line @test GO.within(pt1, l6) == GO.within(pt1, l6) -# Point and Ring +# # Point and Ring +# On ring corner @test GO.within(pt1, r1) == LG.within(pt1, r1) +# Outside of ring @test GO.within(pt2, r1) == LG.within(pt2, r1) +# Inside of ring center (not on line, so not within) @test GO.within(pt3, r1) == LG.within(pt3, r1) -@test GO.within(pt4, r1) == LG.within(pt4, r1) +# On ring edge +@test GO.within(pt7, r1) == LG.within(pt7, r1) -# Point and polygon +# # Point and polygon +# On polygon vertex @test GO.within(pt1, p1) == LG.within(pt1, p1) +# Outside of polygon @test GO.within(pt2, p1) == LG.within(pt2, p1) +# Inside of polygon @test GO.within(pt3, p1) == LG.within(pt3, p1) -@test GO.within(pt4, p1) == LG.within(pt4, p1) - +# On polygon vertex (with holes) @test GO.within(pt1, p2) == LG.within(pt1, p2) +# On polygon edge (with holes) @test GO.within(pt2, p2) == LG.within(pt2, p2) -@test GO.within(pt3, p2) == LG.within(pt3, p2) -@test GO.within(pt4, p2) == LG.within(pt4, p2) +# On hole vertex @test GO.within(pt5, p2) == LG.within(pt5, p2) +# Within hole @test GO.within(pt6, p2) == LG.within(pt6, p2) +# Inside of polygon (with holes) @test GO.within(pt7, p2) == LG.within(pt7, p2) -# Line and line +# # Line and line +# Equal lines @test GO.within(l1, l1) == LG.within(l1, l1) +# Lines share endpoints, but don't overlap @test GO.within(l1, l2) == LG.within(l1, l2) +# Lines overlap, but neither is within other @test GO.within(l1, l3) == LG.within(l1, l3) +# Within line (no shared endpoints) @test GO.within(l1, l4) == LG.within(l1, l4) +# Within line (shares endpoints) @test GO.within(l1, l5) == LG.within(l1, l5) +# Not within line (flipped previous test) @test GO.within(l5, l1) == LG.within(l5, l1) -# Line and ring +# # Line and ring +# Shares all endpoints (within) @test GO.within(l6, r1) == LG.within(l6, r1) +# Shares all endpoints (within) @test GO.within(l2, r2) == LG.within(l2, r2) +# Doesn't share all edges @test GO.within(l2, r3) == LG.within(l2, r3) +# Shares all endpoints, but adds one extra segment (not within) @test GO.within(l12, r1) == LG.within(l12, r1) # Line and polygon +# Line traces entire outline of polygon edges @test GO.within(l6, p1) == LG.within(l6, p1) +# Line is edge of polygon @test GO.within(l1, p2) == LG.within(l1, p2) +# Line is on edge + inside of polygon @test GO.within(l2, p2) == LG.within(l2, p2) +# Line goes outside of polygon @test GO.within(l3, p2) == LG.within(l3, p2) +# Line is fully within polygon @test GO.within(l7, p2) == LG.within(l7, p2) +# Line is fully within hole @test GO.within(l8, p2) == LG.within(l8, p2) +# Line is on hole edge @test GO.within(l9, p2) == LG.within(l9, p2) +# Line is on polygon edge and then cuts into polygon ending on hole vertex @test GO.within(l10, p2) == LG.within(l10, p2) +# Line is on polygon edge and then cuts through hole @test GO.within(l11, p2) == LG.within(l11, p2) -# Ring and line +# # Ring and line +# Shares all endpoints (within) @test GO.within(r1, l6) == LG.within(r1, l6) +# Shares all endpoints (within) @test GO.within(r2, l2) == LG.within(r2, l2) +# Doesn't share all edges @test GO.within(r3, l2) == LG.within(r3, l2) +# Shares all endpoints, but line has one extra segment (within) @test GO.within(r1, l12) == LG.within(r1, l12) -# Ring and Ring +# # Ring and ring +# Equal ring @test GO.within(r1, r1) == LG.within(r1, r1) +# Not equal ring @test GO.within(r1, r2) == LG.within(r1, r2) +# Not equal ring @test GO.within(r1, r3) == LG.within(r1, r3) +# Rings share all edges, but second ring has extra edges +@test GO.within(r2, r7) == LG.within(r2, r7) -# Ring and polygon +# # Ring and polygon # Ring is equal to polygon's external ring, no holes @test GO.within(r1, p1) == LG.within(r1, p1) # Ring goes outside of polygon's external ring @@ -145,8 +196,7 @@ p12 = LG.Polygon([ # Ring is fully within polygon's hole @test GO.within(r6, p2) == LG.within(r6, p2) -# Polygon in polygon - +# # Polygon in polygon # Same polygon @test GO.within(p1, p1) == LG.within(p1, p1) @test GO.within(p2, p2) == LG.within(p2, p2) @@ -169,7 +219,43 @@ p12 = LG.Polygon([ @test GO.within(p10, p7) == LG.within(p10, p7) # within external ring but intersects with hole @test GO.within(p11, p7) == LG.within(p11, p7) - +# polygon extactly overlaps with other polygon's hole @test GO.within(p12, p7) == LG.within(p12, p7) +# # Multipolygon tests +# Point in multipolygon +@test GO.within(pt5, m1) == LG.within(pt5, m1) +@test GO.within(pt9, m1) == LG.within(pt9, m1) + +# Point outside of multipolygon +@test GO.within(pt4, m1) == LG.within(pt4, m1) + +# Line in multipolygon +@test GO.within(l13, m1) == LG.within(l13, m1) +@test GO.within(l9, m1) == LG.within(l9, m1) + +# Line outside of multipolygon +@test GO.within(l1, m1) == LG.within(l1, m1) + +# Ring in multipolygon +@test GO.within(r1, m2) == LG.within(r1, m2) + +# Ring outside of multipolygon +@test GO.within(r1, m1) == LG.within(r1, m1) + +# Polygon in multipolygon +@test GO.within(p3, m1) == LG.within(p3, m1) +@test GO.within(p6, m1) == LG.within(p6, m1) + +# Polygon outside of multipolygon +@test GO.within(p1, m1) == LG.within(p1, m1) + +# Multipolygon in multipolygon +@test GO.within(m1, m1) == LG.within(m1, m1) + +# Multipolygon outside of multipolygon +@test GO.within(m2, m1) == LG.within(m2, m1) + + + From 57a31d4aa57c6d2522c437119b4348925572b662 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Thu, 30 Nov 2023 23:41:43 -0800 Subject: [PATCH 18/33] Add within comments --- src/methods/within.jl | 233 ++++++++++++++++++++++++++++++++--------- test/methods/within.jl | 2 +- 2 files changed, 185 insertions(+), 50 deletions(-) diff --git a/src/methods/within.jl b/src/methods/within.jl index 64c9a54c1..b0ae42725 100644 --- a/src/methods/within.jl +++ b/src/methods/within.jl @@ -1,15 +1,66 @@ -# # Containment/withinness +# # Within export within +#= +## What is within? + +The within function checks if one geometry is inside another geometry. + +To provide an example, consider these two polygons: +```@example cshape +using GeometryOps +using GeometryOps.GeometryBasics +using Makie +using CairoMakie + +l1 = GI.LineString([(0.0, 0.0), (1.0, 0.0), (0.0, 0.1)]) +l2 = GI.LineString([(0.25, 0.0), (0.75, 0.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) +``` +We can see that all of the points and edges of l2 are within l1, so l2 is +within l1, but l1 is not within l2 +```@example cshape +within(l1, l2) # returns false +within(l2, l1) # returns 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. + +The methodology for each geometry pairing is a little different. For a point, +other points can only be inside of it if they are the same point. Nothing other +than a point can be within a point. For line string and linear rings, a point is +within if it is on a vertex or a line. For a line/ring inside of another +line/ring, we need all vertices and edges to be within the other line/ring's +edges. Polygons cannot be within a line/ring. Then for polygons, we need +lines/rings to be either on the edges (but with at least one point within the +polygon) or within the polygon, but not in any holes. Then for polygons within +polygons, they must be inside of the interior, including edges, but again not in +any holes. + +The code for the specific implementations is in the geom_geom_processors file, +which has generalized code for the within and disjoint functions with a keyword +argument `process`, which is specified to be the `within_process` for the below +functions. +=# """ - within(geom1, geom)::Bool + within(geom1, geom2)::Bool Return `true` if the first geometry is completely within the second geometry. -The interiors of both geometries must intersect and, the interior and boundary of the primary (geometry a) -must not intersect the exterior of the secondary (geometry b). -`within` returns the exact opposite result of `contains`. +The interiors of both geometries must intersect and the interior and boundary of +the primary geometry (geom1) must not intersect the exterior of the secondary +geometry (geom2). + +Furthermore, `within` returns the exact opposite result of `contains`. ## Examples ```jldoctest setup=:(using GeometryOps, GeometryBasics) @@ -23,18 +74,36 @@ GO.within(point, line) true ``` """ -# Syntactic sugar within(g1, g2)::Bool = within(trait(g1), g1, trait(g2), g2)::Bool within(::GI.FeatureTrait, g1, ::Any, g2)::Bool = within(GI.geometry(g1), g2) within(::Any, g1, t2::GI.FeatureTrait, g2)::Bool = within(g1, GI.geometry(g2)) +""" +For any non-specified pair, g1 cannot be within g2 as g2 is of a higher +dimension than g1. Return false. +""" +within(::GI.AbstractTrait, g1, ::GI.AbstractTrait, g2)::Bool = false + # Points within geometries -# Point in point +""" + within(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool + +If a point is within another point, then those points must be equal. If they are +not equal then they are not within and return false. +""" within( ::GI.PointTrait, g1, ::GI.PointTrait, g2, ) = equals(g1, g2) -# Point in (on) line string + + +""" + within(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool + +A point is within a line string if it is on a vertex or an edge of that +linestring, excluding the start and end vertex if the linestring is not closed. +Return true if those conditions are met, else false. +""" within( ::GI.PointTrait, g1, ::GI.LineStringTrait, g2, @@ -44,115 +113,172 @@ within( exclude_boundaries = true, repeated_last_coord = false, ) -# Point in (on) curve + +""" + within(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +A point is within a linear ring if it is on a vertex or an edge of that +linear ring. Return true if those conditions are met, else false. +""" within( ::GI.PointTrait, g1, ::GI.LinearRingTrait, g2, ) = _point_curve_process( g1, g2; process = within_process, - exclude_boundaries = true, + exclude_boundaries = false, repeated_last_coord = true, ) -# Point in polygon +""" + within(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A point is within a polygon if it is inside of that polygon, excluding edges, +vertices, and holes. Return true if those conditions are met, else false. +""" within( ::GI.PointTrait, g1, ::GI.PolygonTrait, g2, ) = _point_polygon_process( g1, g2; - process = within_process, exclude_boundaries = true, + process = within_process, + exclude_boundaries = true, ) -# Lines in/on geometries +# Lines within geometries +""" + within(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool -# Lines in/on lines +A line string is within another linestring if the vertices and edges of the +first linestring are within the second linestring, including the first and last +vertex. Return true if those conditions are met, else false. +""" within( - ::GI.LineStringTrait, line, - ::GI.LineStringTrait, curve, + ::GI.LineStringTrait, g1, + ::GI.LineStringTrait, g2, ) = _line_curve_process( - line, curve; + g1, g2; process = within_process, exclude_boundaries = false, closed_line = false, closed_curve = false, ) -# Lines in/on rings +""" + within(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +A line string is within a linear ring if the vertices and edges of the +linestring are within the linear ring. Return true if those conditions are met, +else false. +""" within( - ::GI.LineStringTrait, line, - ::GI.LinearRingTrait, ring, + ::GI.LineStringTrait, g1, + ::GI.LinearRingTrait, g2, ) = _line_curve_process( - line, ring; + g1, g2; process = within_process, exclude_boundaries = false, closed_line = false, closed_curve = true, ) -# Lines in/on polygons +""" + within(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A line string is within a polygon if the vertices and edges of the +linestring are within the polygon. Points of the linestring can be on the +polygon edges, but at least one point must be in the polygon interior. The +linestring also cannot cross through a hole. Return true if those conditions are +met, else false. +""" within( - ::GI.LineStringTrait, line, - ::GI.PolygonTrait, polygon, + ::GI.LineStringTrait, g1, + ::GI.PolygonTrait, g2, ) = _line_polygon_process( - line, polygon; + g1, g2; process = within_process, exclude_boundaries = false, close = false, ) -# Rings in/on geometries +# Rings within geometries +""" + within(::GI.LinearRingTrait, g1, ::GI.LineStringTrait, g2)::Bool -# Rings in/on lines +A linear ring is within a linestring if the vertices and edges of the +linear ring are within the edges/vertices of the linear ring. Return true if +those conditions are met, else false. +""" within( - ::GI.LinearRingTrait, line, - ::GI.LineStringTrait, curve, + ::GI.LinearRingTrait, g1, + ::GI.LineStringTrait, g2, ) = _line_curve_process( - line, curve; + g1, g2; process = within_process, exclude_boundaries = false, closed_line = true, closed_curve = false, ) -# Rings in/on rings +""" + within(::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +A linear ring is within another linear ring if the vertices and edges of the +first linear ring are within the edges/vertices of the second linear ring. +Return true if those conditions are met, else false. +""" within( - ::GI.LinearRingTrait, line, - ::GI.LinearRingTrait, ring, + ::GI.LinearRingTrait, g1, + ::GI.LinearRingTrait, g2, ) = _line_curve_process( - line, ring; + g1, g2; process = within_process, exclude_boundaries = false, closed_line = true, closed_curve = true, ) -# Rings in/on polygons +""" + within(::GI.LinearRingTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A linear ring is within a polygon if the vertices and edges of the linear ring +are within the polygon. Points of the linestring can be on the polygon edges, +but at least one point must be in the polygon interior. The linear ring also +cannot cross through a hole. Return true if those conditions are met, else +false. +""" within( - ::GI.LinearRingTrait, line, - ::GI.PolygonTrait, polygon, + ::GI.LinearRingTrait, g1, + ::GI.PolygonTrait, g2, ) = _line_polygon_process( - line, polygon; + g1, g2; process = within_process, exclude_boundaries = false, close = true, ) # Polygons within polygons +""" + within(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A polygon is within another polygon if the interior of the first polygon is +inside of the second, including edges, and does not intersect with any holes of +the second polygon. If these conditions are met, return true, else false. +""" function within( - ::GI.PolygonTrait, poly1, - ::GI.PolygonTrait, poly2; + ::GI.PolygonTrait, g1, + ::GI.PolygonTrait, g2; ) if _line_polygon_process( - GI.getexterior(poly1), poly2; + GI.getexterior(g1), g2; process = within_process, exclude_boundaries = false, close = true, line_is_poly_ring = true ) - for hole in GI.gethole(poly2) + for hole in GI.gethole(g2) if _line_polygon_process( - hole, poly1; + hole, g1; process = within_process, exclude_boundaries = false, close = true, @@ -166,6 +292,13 @@ function within( return false end +# Geometries within multipolygons +""" + within(::GI.AbstractTrait, g1, ::GI.MultiPolygonTrait, g2)::Bool + +A geometry is within a multipolygon if it is within one of the polygons that +make up the multipolygon. Return true if these conditions are met, else false. +""" function within(::GI.AbstractTrait, g1, ::GI.MultiPolygonTrait, g2) for poly in GI.getpolygon(g2) if within(g1, poly) @@ -175,6 +308,13 @@ function within(::GI.AbstractTrait, g1, ::GI.MultiPolygonTrait, g2) return false end +""" + within(::GI.MultiPolygonTrait, g1, ::GI.MultiPolygonTrait, g2)::Bool + +A multipolygon is within a multipolygon if every polygon in the first +multipolygon is within one of the polygons in the second multipolygon. Return +true if these conditions are met, else false. +""" function within(::GI.MultiPolygonTrait, g1, ::GI.MultiPolygonTrait, g2) for poly1 in GI.getpolygon(g1) poly1_within = false @@ -187,9 +327,4 @@ function within(::GI.MultiPolygonTrait, g1, ::GI.MultiPolygonTrait, g2) !poly1_within && return false end return true -end - -# Everything not specified -within(::GI.AbstractTrait, g1, ::GI.AbstractTrait, g2)::Bool = false - - +end \ No newline at end of file diff --git a/test/methods/within.jl b/test/methods/within.jl index 641b9319f..547a715fb 100644 --- a/test/methods/within.jl +++ b/test/methods/within.jl @@ -203,7 +203,7 @@ m2 = LG.MultiPolygon([p3, p4]) # Polygon not in polygon @test GO.within(p1, p2) == LG.within(p1, p2) @test GO.within(p2, p1) == LG.within(p2, p1) -# Ring is within polygon, but also on edges +# Polygon is within polygon, but also on edges @test GO.within(p3, p2) == LG.within(p3, p2) # Polygon within polygon with holes @test GO.within(p4, p2) == LG.within(p4, p2) From 3ddeb789bb9e012c00a5d1566761918794b7f238 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Fri, 1 Dec 2023 10:22:20 -0800 Subject: [PATCH 19/33] Add disjoint comments and multipolygon code --- src/methods/disjoint.jl | 256 +++++++++++++++++++++++++++++++++------- src/methods/within.jl | 23 +++- 2 files changed, 230 insertions(+), 49 deletions(-) diff --git a/src/methods/disjoint.jl b/src/methods/disjoint.jl index fbec91eed..6651f136f 100644 --- a/src/methods/disjoint.jl +++ b/src/methods/disjoint.jl @@ -1,5 +1,69 @@ -# # Disjointness checks +# # Disjoint + +export disjoint +#= +## What is disjoint? + +The disjoint function checks if one geometry is outside of another geometry, +without sharing any boundaries or interiors. + +To provide an example, consider these two lines: +```@example cshape +using GeometryOps +using GeometryOps.GeometryBasics +using Makie +using CairoMakie + +l1 = GI.LineString([(0.0, 0.0), (1.0, 0.0), (0.0, 0.1)]) +l2 = GI.LineString([(2.0, 0.0), (2.75, 0.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) +``` +We can see that none of the edges or vertices of l1 interact with l2 so they are +disjoint. +```@example cshape +disjoint(l1, l2) # returns 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. + +For a point, other points are disjoint if they are not equal to the first point. +than a point can be within a point. For all other geometries, we identify that +the first point of the geometry is outside of the geometry and then make sure +that the two geometries do not intersect. If these conditions are met, the two +geometries are disjoint. + +The code for the specific implementations is in the geom_geom_processors file, +which has generalized code for the within and disjoint functions with a keyword +argument `process`, which is specified to be the `disjoint_process` for the +below functions. +=# +""" + disjoint(geom1, geom2)::Bool + +Return `true` if the first geometry is disjoint from the second geometry. +The interiors and boundaries of both geometries must not intersect. + +## Examples +```jldoctest setup=:(using GeometryOps, GeometryBasics) +import GeometryOps as GO, GeoInterface as GI + +line = GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)]) +point = (2, 2) +GO.disjoint(point, line) + +# output +true +``` +""" """ disjoint(geom1, geom2)::Bool @@ -22,15 +86,26 @@ true disjoint(g1, g2)::Bool = disjoint(trait(g1), g1, trait(g2), g2) disjoint(::FeatureTrait, g1, ::Any, g2)::Bool = disjoint(GI.geometry(g1), g2) disjoint(::Any, g1, t2::FeatureTrait, g2)::Bool = disjoint(g1, geometry(g2)) + # Point disjoint geometries +""" + disjoint(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool -# Point disjoint from point +If a point is disjoint from another point, those points must not be equal. If +they are equal then they are not disjoint and return false. +""" disjoint( ::GI.PointTrait, g1, ::GI.PointTrait, g2, ) = !equals(g1, g2) -# Point in from line string +""" + disjoint(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool + +If a point is disjoint from a linestring then it is not on any of the +linestring's edges or vertices. If these conditions are met, return true, else +false. +""" disjoint( ::GI.PointTrait, g1, ::GI.LineStringTrait, g2, @@ -41,7 +116,12 @@ disjoint( repeated_last_coord = false, ) -# Point disjoint from curve +""" + disjoint(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +If a point is disjoint from a linear ring then it is not on any of the +ring's edges or vertices. If these conditions are met, return true, else false. +""" disjoint( ::GI.PointTrait, g1, ::GI.LinearRingTrait, g2, @@ -52,53 +132,82 @@ disjoint( repeated_last_coord = true, ) -# Point disjoint from polygon +""" + disjoint(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A point is disjoint from a polygon if it is outside of that polygon. This means +it is not on any edges, vertices, or within the interior. The point can be +within a hole. Return true if those conditions are met, else false. +""" disjoint( ::GI.PointTrait, g1, ::GI.PolygonTrait, g2, ) = _point_polygon_process( g1, g2; - process = disjoint_process, exclude_boundaries = false, + process = disjoint_process, + exclude_boundaries = false, ) -# Geometry with point +""" + disjoint(trait1::GI.AbstractTrait, g1, trait2::GI.PointTrait, g2)::Bool + +To check if a geometry is disjoint from a point, switch the order of the +arguments to take advantage of point-geometry disjoint methods. +""" disjoint( trait1::GI.AbstractTrait, g1, trait2::GI.PointTrait, g2, ) = disjoint(trait2, g2, trait1, g1) # Lines disjoint from geometries +""" + disjoint(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool -# Lines disjoint from lines +Two linestrings are disjoint if they do not share any edges or vertices and if +they do not intersect. If these conditions are met, return true, else false. +""" disjoint( - ::GI.LineStringTrait, line, - ::GI.LineStringTrait, curve, + ::GI.LineStringTrait, g1, + ::GI.LineStringTrait, g2, ) = _line_curve_process( - line, curve; + g1, g2; process = disjoint_process, exclude_boundaries = false, closed_line = false, closed_curve = false, ) -# Lines disjoint from rings +""" + disjoint(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +A linestring and a linear ring are disjoint if they do not share any edges or +vertices and if they do not intersect. If these conditions are met, return true, +else false. +""" disjoint( - ::GI.LineStringTrait, line, - ::GI.LinearRingTrait, ring, + ::GI.LineStringTrait, g1, + ::GI.LinearRingTrait, g2, ) = _line_curve_process( - line, ring; + g1, g2; process = disjoint_process, exclude_boundaries = false, closed_line = false, closed_curve = true, ) -# Lines disjoint from polygons +""" + disjoint(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A linestring and a polygon are disjoint if they do not share any edges or +vertices and if the linestring does not pass through the interior of the +polygon, excluding any holes. If these conditions are met, return true, else +false. +""" disjoint( - ::GI.LineStringTrait, line, - ::GI.PolygonTrait, polygon, + ::GI.LineStringTrait, g1, + ::GI.PolygonTrait, g2, ) = _line_polygon_process( - line, polygon; + g1, g2; process = disjoint_process, exclude_boundaries = false, close = false, @@ -106,51 +215,77 @@ disjoint( # Rings disjoint from geometries -# Rings disjoint from lines +""" + disjoint(::GI.LinearRingTrait, g1, ::GI.LineStringTrait, g2)::Bool + +A linear ring and a linestring are disjoint if they do not share any edges or +vertices and if they do not intersect. If these conditions are met, return true, +else false. +""" disjoint( - ::GI.LinearRingTrait, line, - ::GI.LineStringTrait, curve, -) = _line_curve_process( - line, curve; - process = disjoint_process, - exclude_boundaries = false, - closed_line = true, - closed_curve = false, -) + trait1::GI.LinearRingTrait, g1, + trait2::GI.LineStringTrait, g2, +) = within(trait2, g2, trait1, g1) -# Rings disjoint from rings +""" + disjoint(::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +Two linear rings are disjoint if they do not share any edges or vertices and if +they do not intersect. If these conditions are met, return true, else false. +""" disjoint( - ::GI.LinearRingTrait, line, - ::GI.LinearRingTrait, ring, + ::GI.LinearRingTrait, g1, + ::GI.LinearRingTrait, g2, ) = _line_curve_process( - line, ring; + g1, g2; process = disjoint_process, exclude_boundaries = false, closed_line = true, closed_curve = true, ) -# Rings disjoint from polygons +""" + disjoint(::GI.LinearRingTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A linear ring and a polygon are disjoint if they do not share any edges or +vertices and if the linear ring does not pass through the interior of the +polygon, excluding any holes. If these conditions are met, return true, else +false. +""" disjoint( - ::GI.LinearRingTrait, line, - ::GI.PolygonTrait, polygon, + ::GI.LinearRingTrait, g1, + ::GI.PolygonTrait, g2, ) = _line_polygon_process( - line, polygon; + g1, g2; process = disjoint_process, exclude_boundaries = false, close = true, ) -# Polygons disjoint from polygons +""" + disjoint(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool + +Two polygons are disjoint if they do not share any edges or vertices and if +their interiors do not intersect, excluding any holes. If these conditions are +met, return true, else false. +""" function disjoint( - ::GI.PolygonTrait, poly1, - ::GI.PolygonTrait, poly2; + ::GI.PolygonTrait, g1, + ::GI.PolygonTrait, g2; ) - if disjoint(GI.getexterior(poly1), poly2) + #= + if the exterior of g1 is disjoint from g2 (could be in a g2 hole), the + polygons are disjoint + =# + if disjoint(GI.getexterior(g1), g2) return true else - for hole in GI.gethole(poly1) - if within(poly2, hole) + #= + if the exterior of g1 is not disjoint, the only way for the polygons to + be disjoint is if g2 is in a hole of g1 + =# + for hole in GI.gethole(g1) + if within(g2, hole) return true end end @@ -158,3 +293,38 @@ function disjoint( return false end +# Geometries within multipolygons +""" + disjoint(::GI.AbstractTrait, g1, ::GI.MultiPolygonTrait, g2)::Bool + +A geometry is disjoint from a multipolygon if it is disjoint from all of the +polygons that make up the multipolygon. Return true if these conditions are met, +else false. +""" +function disjoint(::GI.AbstractTrait, g1, ::GI.MultiPolygonTrait, g2) + for poly in GI.getpolygon(g2) + if !disjoint(g1, poly) + return false + end + end + return true +end + +""" + disjoint(::GI.MultiPolygonTrait, g1, ::GI.MultiPolygonTrait, g2)::Bool + +A multipolygon is disjoint from a multipolygon if every polygon in the first +multipolygon is disjoint from all of the polygons in the second multipolygon. +Return true if these conditions are met, else false. +""" +function disjoint(::GI.MultiPolygonTrait, g1, ::GI.MultiPolygonTrait, g2) + for poly1 in GI.getpolygon(g1) + for poly2 in GI.getpolygon(g2) + if !disjoint(poly1, poly2) + return false + end + end + end + return true +end + diff --git a/src/methods/within.jl b/src/methods/within.jl index b0ae42725..ffcd7ae28 100644 --- a/src/methods/within.jl +++ b/src/methods/within.jl @@ -7,7 +7,7 @@ export within The within function checks if one geometry is inside another geometry. -To provide an example, consider these two polygons: +To provide an example, consider these two lines: ```@example cshape using GeometryOps using GeometryOps.GeometryBasics @@ -74,22 +74,22 @@ GO.within(point, line) true ``` """ -within(g1, g2)::Bool = within(trait(g1), g1, trait(g2), g2)::Bool -within(::GI.FeatureTrait, g1, ::Any, g2)::Bool = within(GI.geometry(g1), g2) -within(::Any, g1, t2::GI.FeatureTrait, g2)::Bool = within(g1, GI.geometry(g2)) +within(g1, g2) = within(trait(g1), g1, trait(g2), g2) +within(::GI.FeatureTrait, g1, ::Any, g2) = within(GI.geometry(g1), g2) +within(::Any, g1, t2::GI.FeatureTrait, g2) = within(g1, GI.geometry(g2)) """ For any non-specified pair, g1 cannot be within g2 as g2 is of a higher dimension than g1. Return false. """ -within(::GI.AbstractTrait, g1, ::GI.AbstractTrait, g2)::Bool = false +within(::GI.AbstractTrait, g1, ::GI.AbstractTrait, g2) = false # Points within geometries """ within(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool If a point is within another point, then those points must be equal. If they are -not equal then they are not within and return false. +not equal, then they are not within and return false. """ within( ::GI.PointTrait, g1, @@ -269,6 +269,13 @@ function within( ::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2; ) + #= + For g1 to be within g2, the exterior of g1 must be within g2, including not + being in any of g2's holes. + Note: line_is_poly_ring is needed as ring can be exclusivly on g2 edge's and + still be "within" due to the interior being filled in in contrast to a + linestring or linear ring + =# if _line_polygon_process( GI.getexterior(g1), g2; process = within_process, @@ -276,6 +283,10 @@ function within( close = true, line_is_poly_ring = true ) + #= + now need to check that none of g2's holes are within g1 as this would + make the part of g1 within the hole outside of g2 + =# for hole in GI.gethole(g2) if _line_polygon_process( hole, g1; From da0cc2b95896f599f59dd7f6bf11774b9257a5b6 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Wed, 13 Dec 2023 13:54:08 -0800 Subject: [PATCH 20/33] Add processor comments and clean --- src/methods/contains.jl | 46 ++++++- src/methods/disjoint.jl | 45 +++---- src/methods/geom_geom_processors.jl | 155 +++++++++++++++++++---- src/methods/touches.jl | 44 +++++++ src/try.jl | 5 +- test/methods/disjoint.jl | 188 +++++++++++++++++++++++++--- test/methods/within.jl | 146 ++++++++++----------- 7 files changed, 477 insertions(+), 152 deletions(-) diff --git a/src/methods/contains.jl b/src/methods/contains.jl index a58e2a48b..ef15d17c0 100644 --- a/src/methods/contains.jl +++ b/src/methods/contains.jl @@ -1,13 +1,49 @@ -# # Containment +# # Contains export contains +#= +## What is contains? + +The contains function checks if completly contains another geometry, or in other +words, that the second geometry is completly within the first. + +To provide an example, consider these two lines: +```@example cshape +using GeometryOps +using GeometryOps.GeometryBasics +using Makie +using CairoMakie + +l1 = GI.LineString([(0.0, 0.0), (1.0, 0.0), (0.0, 0.1)]) +l2 = GI.LineString([(0.25, 0.0), (0.75, 0.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) +``` +We can see that all of the points and edges of l2 are within l1, so l1 contains +l2. +```@example cshape +contains(l1, l2) # returns true +``` + +## Implementation + +This is the GeoInterface-compatible implementation. + +Given that contains is the exact opposite of within, we simply pass the two +inputs variables, swapped in order, to within. +=# + """ - contains(ft1::AbstractGeometry, ft2::AbstractGeometry)::Bool + contains(g1::AbstractGeometry, g2::AbstractGeometry)::Bool + +Return true if the second geometry is completely contained by the first +geometry. The interiors of both geometries must intersect and, the interior and +boundary of the secondary (g2) must not intersect the exterior of the primary +(g1). -Return true if the second geometry is completely contained by the first geometry. -The interiors of both geometries must intersect and, the interior and boundary of the secondary (geometry b) -must not intersect the exterior of the primary (geometry a). `contains` returns the exact opposite result of `within`. ## Examples diff --git a/src/methods/disjoint.jl b/src/methods/disjoint.jl index 6651f136f..7448b3e5f 100644 --- a/src/methods/disjoint.jl +++ b/src/methods/disjoint.jl @@ -64,25 +64,6 @@ GO.disjoint(point, line) true ``` """ -""" - disjoint(geom1, geom2)::Bool - -Return `true` if the intersection of the two geometries is an empty set. - -# Examples - -```jldoctest -import GeometryOps as GO, GeoInterface as GI - -poly = GI.Polygon([[(-1, 2), (3, 2), (3, 3), (-1, 3), (-1, 2)]]) -point = (1, 1) -GO.disjoint(poly, point) - -# output -true -``` -""" -# Syntactic sugar disjoint(g1, g2)::Bool = disjoint(trait(g1), g1, trait(g2), g2) disjoint(::FeatureTrait, g1, ::Any, g2)::Bool = disjoint(GI.geometry(g1), g2) disjoint(::Any, g1, t2::FeatureTrait, g2)::Bool = disjoint(g1, geometry(g2)) @@ -213,8 +194,18 @@ disjoint( close = false, ) -# Rings disjoint from geometries +""" + disjoint(trait1::GI.AbstractTrait, g1, trait2::GI.LineStringTrait, g2)::Bool +To check if a geometry is disjoint from a linestring, switch the order of the +arguments to take advantage of linestring-geometry disjoint methods. +""" +disjoint( + trait1::GI.AbstractTrait, g1, + trait2::GI.LineStringTrait, g2, +) = disjoint(trait2, g2, trait1, g1) + +# Rings disjoint from geometries """ disjoint(::GI.LinearRingTrait, g1, ::GI.LineStringTrait, g2)::Bool @@ -225,7 +216,7 @@ else false. disjoint( trait1::GI.LinearRingTrait, g1, trait2::GI.LineStringTrait, g2, -) = within(trait2, g2, trait1, g1) +) = disjoint(trait2, g2, trait1, g1) """ disjoint(::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2)::Bool @@ -262,6 +253,18 @@ disjoint( close = true, ) +""" + disjoint(trait1::GI.AbstractTrait, g1, trait2::GI.LinearRingTrait, g2)::Bool + +To check if a geometry is disjoint from a linear ring, switch the order of the +arguments to take advantage of linear ring-geometry disjoint methods. +""" +disjoint( + trait1::GI.AbstractTrait, g1, + trait2::GI.LinearRingTrait, g2, +) = disjoint(trait2, g2, trait1, g1) + +# Polygon disjoint from geometries """ disjoint(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool diff --git a/src/methods/geom_geom_processors.jl b/src/methods/geom_geom_processors.jl index 312a12fad..2a5b9b22c 100644 --- a/src/methods/geom_geom_processors.jl +++ b/src/methods/geom_geom_processors.jl @@ -1,15 +1,64 @@ -@enum ProcessType within_process=1 disjoint_process=2 +@enum ProcessType within_process=1 disjoint_process=2 touch_process=3 coverby_process=4 +@enum PointOrientation point_in=1 point_on=2 point_off=3 -get_process_type_vals(process::ProcessType, exclude_boundaries = false) = +""" + get_process_return_vals( + process::ProcessType, + exclude_boundaries = false, + )::(Bool, Bool, Bool) + +Returns a tuple of booleans which represent the boolean return value for it a +point is in, out, or on a given geometry. This is determined by the process +type as well as by the exclude_boundaries. + +For within_process: + if a point is in a geometry, we should return true + if a point is out of a geomertry, we should return false + if a point is on the boundary of a geometry, we should return false if we + want to exclude boundaries, else we should return true +For disjoint_process: + if a point is in a geometry, we should return false + if a point is out of a geometry, we should return true + if a point is on the boundary of a geometry, we should return true if we + want to exclude boundaries, else we should return false +For touch_process: + ??? +""" +get_process_return_vals(process::ProcessType, exclude_boundaries = false) = ( - process == within_process, - process == disjoint_process, - process == within_process ? - !exclude_boundaries : exclude_boundaries, + process == within_process, # in value + process == disjoint_process, # out value + ( # on value + (process == touch_process) || + (process == within_process ? + !exclude_boundaries : exclude_boundaries + ) + ) ) +""" + _point_curve_process( + point, curve; + process::ProcessType = within_process, + exclude_boundaries = false, + repeated_last_coord = false, + )::Bool +Determines if a point meets the given process checks with respect to a curve. +This curve includes just the line segments that make up the curve. Even if the +curve has a repeated last point that "closes" the curve, this function does not +include the space within the closed curve as a part of the geometry. Point +should be an object of Point trait and curve should be an object with a line +string or a linear ring trait. + +If checking within, then the point must be on a segment of the line and if +checking disjoint the point must not be on any segment of the line. + +Beyond specifying the process type, user can also specify if the geometry +boundaries should be included in the checks and if the curve should be closed +with repeated a repeated last coordinate matching the first coordinate. +""" function _point_curve_process( point, curve; process::ProcessType = within_process, @@ -21,8 +70,11 @@ function _point_curve_process( repeated_last_coord |= first_last_equal exclude_boundaries |= first_last_equal n -= first_last_equal ? 1 : 0 - in_val, out_val, on_val = get_process_type_vals(process, exclude_boundaries) - + in_val, out_val, on_val = get_process_return_vals( + process, + exclude_boundaries, + ) + # Loop through all curve segments p_start = GI.getpoint(curve, repeated_last_coord ? n : 1) @inbounds for i in (repeated_last_coord ? 1 : 2):n p_end = GI.getpoint(curve, i) @@ -38,6 +90,17 @@ function _point_curve_process( return out_val end +""" + _point_in_on_out_segment( + point::Point, start::Point, stop::Point; + in::T = 1, on::T = -1, out::T = 0, + )::T where {T} + +Determines if a point is in, on, or out of a segment. If the point is 'on' the +segment it is on one of the segments endpoints. If it is 'in', it is on any +other point of the segment. If the point is not on any part of the segment, it +is 'out' of the segment. +""" function _point_in_on_out_segment( point, start, stop; in::T = 1, on::T = -1, out::T = 0, @@ -69,6 +132,7 @@ function _point_in_on_out_segment( return in end + point_return_val(point_val, in_val, out_val, on_val) = point_val == 1 ? in_val : # point is inside of polygon @@ -82,10 +146,44 @@ point_return_val(point_val, in_val, out_val, on_val) = point, curve; process::ProcessType = within_process, exclude_boundary = false, + )::Bool + +Determines if a point meets the given process checks with respect to a closed +curve, which includes the space enclosed by the closed curve. Point should be an +object of Point trait and curve should be an object with a line string or linear +ring trait, that is assumed to be closed, regardless of repeated last point. + +If checking within, then the point must be within the space enclosed by the +curve and if checking disjoint the point must not be outside of the curve. + +Beyond specifying the process type, user can also specify if the geometry +boundaries should be included in the checks and if the curve should be closed +with repeated a repeated last coordinate matching the first coordinate. +""" +function _point_closed_curve_process( + point, curve; + process::ProcessType = within_process, + exclude_boundaries = false, +) + in_val, out_val, on_val = get_process_return_vals( + process, + exclude_boundaries, ) + return _point_in_on_out_closed_curve( + point, curve; + in = in_val, out = out_val, on = on_val + ) +end -Determine if point is in, on, or out of a closed curve. Point should be an -object of Point trait and curve should be a linearstring or ring, that is +""" + _point_in_on_out_closed_curve( + point, curve; + in::T = 1, on::T = -1, out::T = 0, + )::T where {T} + +Determine if point is in, on, or out of a closed curve, which includes the space +enclosed by the closed curve. Point should be an object of Point trait and +curve should be an object with a line string or linear ring trait, that is assumed to be closed, regardless of repeated last point. Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. @@ -103,16 +201,6 @@ passes through an odd number of edges, it is within the curve, else outside of of the curve if it didn't return 'on'. See paper for more information on cases denoted in comments. """ -function _point_closed_curve_process( - point, curve; - process::ProcessType = within_process, - exclude_boundaries = false, -) - in_val, out_val, on_val = get_process_type_vals(process, exclude_boundaries) - point_val = _point_in_on_out_closed_curve(point, curve) - return point_return_val(point_val, in_val, out_val, on_val) -end - function _point_in_on_out_closed_curve( point, curve; in::T = 1, on::T = -1, out::T = 0, @@ -150,6 +238,18 @@ function _point_in_on_out_closed_curve( return iseven(k) ? out : in end +""" + _point_polygon_process( + point, polygon; + process::ProcessType = within_process, + exclude_boundaries = false, + )::Bool + +Determines if a point meets the given process checks with respect to a polygon, +which excludes any holes specified by the polygon. Point should be an +object of Point trait and polygon should an object with a Polygon trait. + +""" _point_polygon_process( point, polygon; process::ProcessType = within_process, @@ -162,6 +262,7 @@ _point_polygon_process( hole_exclude_boundaries = !exclude_boundaries ) + function _line_curve_process( line, curve; process::ProcessType = within_process, @@ -369,7 +470,7 @@ function _line_closed_curve_process( closed curve =# point_in = line_is_poly_ring - in_val, out_val, on_val = get_process_type_vals(process, exclude_boundaries) + in_val, out_val, on_val = get_process_return_vals(process, exclude_boundaries) # Determine number of points in curve and line nc = GI.npoint(curve) nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 @@ -461,7 +562,7 @@ function _segment_mids_closed_curve_process( exclude_boundaries = false, ) point_in = false - in_val, out_val, on_val = get_process_type_vals(process, exclude_boundaries) + in_val, out_val, on_val = get_process_return_vals(process, exclude_boundaries) # Find intersection points ipoints = intersection_points( GI.Line([l_start, l_end]), @@ -472,7 +573,7 @@ function _segment_mids_closed_curve_process( mid_val = _point_in_on_out_closed_curve((l_start .+ l_end) ./ 2, curve) point_in |= mid_val == 1 mid_return = point_return_val(mid_val, in_val, out_val, on_val) - !mid_return && return mid_return + !mid_return && return (false, point_in) else # more intersection points than the endpoints # sort intersection points along the line sort!(ipoints, by = p -> euclid_distance(p, l_start)) @@ -486,7 +587,7 @@ function _segment_mids_closed_curve_process( ) point_in |= mid_val == 1 mid_return = point_return_val(mid_val, in_val, out_val, on_val) - !mid_return && return mid_val + !mid_return && return (false, point_in) end end # all intersection point midpoints were in or on the curve @@ -509,11 +610,13 @@ _line_polygon_process( ); process = process, ext_exclude_boundaries = exclude_boundaries, - hole_exclude_boundaries = exclude_boundaries, + hole_exclude_boundaries = + process == within_process ? + exclude_boundaries : + !exclude_boundaries, ) - function _geom_polygon_process( geom, polygon, geom_closed_curve_func; process::ProcessType = within_process, diff --git a/src/methods/touches.jl b/src/methods/touches.jl index e69de29bb..7dbe956f9 100644 --- a/src/methods/touches.jl +++ b/src/methods/touches.jl @@ -0,0 +1,44 @@ +""" +they have at least one point in common, but their interiors do not intersect. +""" +touches(g1, g2)::Bool = touches(trait(g1), g1, trait(g2), g2) + +""" + touches(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool + +Two points cannot touch. If they are the same point then their interiors +intersect and if they are different points then they don't share any points. +""" +touches( + ::GI.PointTrait, g1, + ::GI.PointTrait, g2, +) = false + +""" + touches(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool + +If a point touches a linestring if it equal to +""" +touches( + ::GI.PointTrait, g1, + ::GI.LineStringTrait, g2, +) = _point_curve_process( + g1, g2; + process = touch_process, + repeated_last_coord = false, +) + +""" + touches(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +If a point is disjoint from a linear ring then it is not on any of the +ring's edges or vertices. If these conditions are met, return true, else false. +""" +touches( + ::GI.PointTrait, g1, + ::GI.LinearRingTrait, g2, +) = _point_curve_process( + g1, g2; + process = touch_process, + repeated_last_coord = true, +) \ No newline at end of file diff --git a/src/try.jl b/src/try.jl index e3250f559..d647f37f5 100644 --- a/src/try.jl +++ b/src/try.jl @@ -2,7 +2,8 @@ import GeometryOps as GO import GeoInterface as GI import LibGEOS as LG -p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) +r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) +p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) -GO.within(p1, p1) +GO.disjoint(r1, p3) \ No newline at end of file diff --git a/test/methods/disjoint.jl b/test/methods/disjoint.jl index 96c5f1425..7fe590488 100644 --- a/test/methods/disjoint.jl +++ b/test/methods/disjoint.jl @@ -1,3 +1,4 @@ +# Test points pt1 = LG.Point([0.0, 0.0]) pt2 = LG.Point([0.0, 0.1]) pt3 = LG.Point([1.0, 0.0]) @@ -5,52 +6,203 @@ pt4 = LG.Point([0.5, 1.0]) pt5 = LG.Point([0.2, 0.5]) pt6 = LG.Point([0.3, 0.55]) pt7 = LG.Point([0.6, 0.49]) - +pt8 = LG.Point([0.25, 0.75]) +pt9 = LG.Point([-1.0, 0.0]) +# Test lines l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) - +l7 = LG.LineString([[0.6, 0.6], [0.8, 0.6]]) +l8 = LG.LineString([[0.3, 0.55], [0.3, 0.65]]) +l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) +l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) +l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) +l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) +l13 = LG.LineString([[0.5, 0.01], [0.5, 0.09]]) +# Test rings r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) - +r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) +r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) +r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) +r5 = LG.LinearRing([[5.0, 5.0], [6.0, 6.0], [7.0, 5.0], [5.0, 5.0]]) +r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) +# Test polygons p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) p2 = LG.Polygon([ [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] ]) +p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) +p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) +p5 = LG.Polygon([[[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]]) +p6 = LG.Polygon([[[0.1, 0.4], [0.1, 0.8], [0.3, 0.8], [0.3, 0.4], [0.1, 0.4]]]) +p7 = LG.Polygon([[[-2.0, 0.0], [-1.0, 0.0], [-1.5, 1.5], [-2.0, 0.0]]]) +p8 = LG.Polygon([ + [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] +]) +# Test multipolygons +m1 = LG.MultiPolygon([p3, p6]) +m2 = LG.MultiPolygon([p3, p4]) +m3 = LG.MultiPolygon([p2, p7]) +m4 = LG.MultiPolygon([p7]) -# Point and point +# # Point and point +# Equal points -> not disjoint @test GO.disjoint(pt1, pt1) == LG.disjoint(pt1, pt1) +# Non-equal points -> disjoint @test GO.disjoint(pt1, pt2) == LG.disjoint(pt1, pt2) -# Point and line +# # Point and line +# Line endpoint (1 segment) -> not disjoint @test GO.disjoint(pt1, l1) == LG.disjoint(pt1, l1) +# Middle of line (1 segment) -> not disjoint @test GO.disjoint(pt2, l1) == LG.disjoint(pt2, l1) +# Not on line (1 segment) -> disjoint @test GO.disjoint(pt3, l1) == LG.disjoint(pt3, l1) -@test GO.disjoint(pt1, l2) == LG.disjoint(pt1, l2) +# Line endpoint (2 segments) -> not disjoing @test GO.disjoint(pt2, l2) == LG.disjoint(pt2, l2) +# Middle of line on joint (2 segments) -> not disjoint @test GO.disjoint(pt3, l2) == LG.disjoint(pt3, l2) -@test GO.within(pt1, l6) == GO.within(pt1, l6) +# Endpoint on closed line -> not disjoint +@test GO.disjoint(pt1, l6) == LG.disjoint(pt1, l6) -# Point and Ring +# # Point and ring +# On ring corner -> not disjoint @test GO.disjoint(pt1, r1) == LG.disjoint(pt1, r1) +# Outside of ring -> disjoint @test GO.disjoint(pt2, r1) == LG.disjoint(pt2, r1) +# Inside of ring center (not on line) -> disjoint @test GO.disjoint(pt3, r1) == LG.disjoint(pt3, r1) -@test GO.disjoint(pt4, r1) == LG.disjoint(pt4, r1) - -# Point and polygon -@test GO.disjoint(pt1, p1) == LG.disjoint(pt1, p1) -@test GO.disjoint(pt2, p1) == LG.disjoint(pt2, p1) -@test GO.disjoint(pt3, p1) == LG.disjoint(pt3, p1) -@test GO.disjoint(pt4, p1) == LG.disjoint(pt4, p1) +# On ring edge -> not disjoint +@test GO.disjoint(pt8, r1) == LG.disjoint(pt8, r1) +# # Point and polygon +# Point on polygon vertex -> not disjoint @test GO.disjoint(pt1, p2) == LG.disjoint(pt1, p2) +# Point on polygon edge -> not disjoint @test GO.disjoint(pt2, p2) == LG.disjoint(pt2, p2) -@test GO.disjoint(pt3, p2) == LG.disjoint(pt3, p2) -@test GO.disjoint(pt4, p2) == LG.disjoint(pt4, p2) +# Point on edge of hold --> not disjoint @test GO.disjoint(pt5, p2) == LG.disjoint(pt5, p2) +# Point in hole -> disjoint @test GO.disjoint(pt6, p2) == LG.disjoint(pt6, p2) -@test GO.disjoint(pt7, p2) == LG.disjoint(pt7, p2) \ No newline at end of file +# Point inside of polygon -> not disjoint +@test GO.disjoint(pt7, p2) == LG.disjoint(pt7, p2) +# Point outside of polygon -> disjoint +@test GO.disjoint(pt9, p2) == LG.disjoint(pt9, p2) + +# # Geometry and point (switched direction) +@test GO.disjoint(pt1, l1) == GO.disjoint(l1, pt1) +@test GO.disjoint(pt1, r1) == GO.disjoint(r1, pt1) +@test GO.disjoint(pt1, p2) == GO.disjoint(p2, pt1) + +# # Line and line +# Equal lines -> not disjoint +@test GO.disjoint(l1, l1) == LG.disjoint(l1, l1) +# Lines share 2 endpoints, but don't overlap -> not disjoint +@test GO.disjoint(l1, l2) == LG.disjoint(l1, l2) +# Lines overlap, but neither is within other -> not disjoint +@test GO.disjoint(l1, l3) == LG.disjoint(l1, l3) +# Within line (no shared endpoints) -> not disjoint +@test GO.disjoint(l1, l4) == LG.disjoint(l1, l4) +# Line shares just 1 endpoint -> not disjoint +@test GO.disjoint(l1, l5) == LG.disjoint(l1, l5) +# Lines don't touch at all -> disjoint +@test GO.disjoint(l7, l1) == LG.disjoint(l7, l1) + +# # Line and ring +# Shares all endpoints -> not disjoint +@test GO.disjoint(l6, r1) == LG.disjoint(l6, r1) +# Shares only some edges -> not disjoint +@test GO.disjoint(l2, r3) == LG.disjoint(l2, r3) +# line inside of ring -> disjoint +@test GO.disjoint(l7, r1) == LG.disjoint(l7, r1) +# line outside of ring -> disjoint +@test GO.disjoint(l7, r2) == LG.disjoint(l7, r2) + +# # Line and polygon +# Line traces entire outline of polygon edges -> not disjoint +@test GO.disjoint(l6, p1) == LG.disjoint(l6, p1) +# Line is on edge + inside of polygon -> not disjoint +@test GO.disjoint(l2, p2) == LG.disjoint(l2, p2) +# Line goes outside of polygon -> not disjoint +@test GO.disjoint(l3, p2) == LG.disjoint(l3, p2) +# Line is fully within hole -> disjoint +@test GO.disjoint(l8, p2) == LG.disjoint(l8, p2) +# Line is on polygon edge and then cuts through hole -> not disjoint +@test GO.disjoint(l11, p2) == LG.disjoint(l11, p2) + +# # Geometry and line (switched direction) +@test GO.disjoint(l7, r1) == GO.disjoint(r1, l7) +@test GO.disjoint(l8, p2) == GO.disjoint(p2, l8) + +# # Ring and line +# Shares all endpoints -> not disjoint +@test GO.disjoint(r1, l6) == LG.disjoint(r1, l6) +# Shares some edges -> not disjoint +@test GO.disjoint(r3, l2) == LG.disjoint(r3, l2) +# Doesn't share any edges -> disjoint +@test GO.disjoint(r4, l2) == LG.disjoint(r4, l2) + +# # Ring and ring +# Equal ring -> not disjoint +@test GO.disjoint(r1, r1) == LG.disjoint(r1, r1) +# Not equal ring but share a vertex -> not disjoint +@test GO.disjoint(r1, r2) == LG.disjoint(r1, r2) +# Rings not touching -> not disjoint +@test GO.disjoint(r3, r4) == LG.disjoint(r3, r4) +# Ring inside of ring -> disjoint +@test GO.disjoint(r4, r2) == LG.disjoint(r4, r2) +# Ring outside of other ring -> disjoing +@test GO.disjoint(r2, r4) == LG.disjoint(r2, r4) + +# # Ring and polygon +# Ring goes outside of polygon's external ring -> not disjoint +@test GO.within(r1, p2) == LG.within(r1, p1) +# Ring is one of polygon's holes -> not disjoint +@test GO.disjoint(r4, p2) == LG.disjoint(r4, p2) +# Ring is fully within polygon -> not disjoint +@test GO.disjoint(r5, p2) == LG.disjoint(r5, p2) +# Ring is fully within polygon's hole -> disjoint +@test GO.disjoint(r6, p2) == LG.disjoint(r6, p2) +# Ring is fully outside of the polygon -> disjoint +@test GO.disjoint(r5, p2) == LG.disjoint(r5, p2) + +# # Geometry and ring (switched direction) +@test GO.disjoint(r4, r2) == GO.disjoint(r2, r4) +@test GO.disjoint(p2, r6) == GO.disjoint(r6, p2) + +# # Polygon and polygon +# Overlapping polygons -> not disjoint +@test GO.disjoint(p1, p2) == LG.disjoint(p1, p2) +# Polygon is within polygon, but also on edges -> not disjoint +@test GO.disjoint(p3, p2) == LG.disjoint(p3, p2) +# Polygon within polygon hole --> disjoint +@test GO.disjoint(p5, p2) == LG.disjoint(p5, p2) +# polygon extactly overlaps with other polygon's hole -> not disjoint +@test GO.disjoint(p8, p7) == LG.disjoint(p8, p7) + +# # Multipolygon tests +# Point in multipolygon -> not disjoint +@test GO.disjoint(pt5, m1) == LG.disjoint(pt5, m1) +# Point outside of multipolygon -> disjoint +@test GO.disjoint(pt4, m1) == LG.disjoint(pt4, m1) +# Line in multipolygon -> not disjoint +@test GO.disjoint(l13, m1) == LG.disjoint(l13, m1) +# Line outside of multipolygon -> disjoint +@test GO.disjoint(l8, m3) == LG.disjoint(l8, m3) +# Ring in multipolygon -> not disjoint +@test GO.disjoint(r1, m2) == LG.disjoint(r1, m2) +# Ring outside of multipolygon +@test GO.disjoint(r6, m3) == LG.disjoint(r6, m3) +# Polygon in multipolygon -> not disjoint +@test GO.disjoint(p3, m1) == LG.disjoint(p3, m1) +# Polygon outside of multipolygon -> disjoint +@test GO.disjoint(p5, m3) == LG.disjoint(p5, m3) +# Multipolygon in multipolygon -> not disjoint +@test GO.disjoint(m1, m1) == LG.disjoint(m1, m1) +# Multipolygon outside of multipolygon -> disjoint +@test GO.disjoint(m1, m4) == LG.disjoint(m1, m4) \ No newline at end of file diff --git a/test/methods/within.jl b/test/methods/within.jl index 547a715fb..84c50cee2 100644 --- a/test/methods/within.jl +++ b/test/methods/within.jl @@ -31,7 +31,6 @@ r5 = LG.LinearRing([[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]) r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) r7 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.5, 0.3], [0.0, 0.3],[0.0, 0.0]]) # Test polygons - p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) p2 = LG.Polygon([ [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], @@ -69,193 +68,180 @@ m1 = LG.MultiPolygon([p3, p6]) m2 = LG.MultiPolygon([p3, p4]) # # Point and point -# Equal points +# Equal points -> within @test GO.within(pt1, pt1) == LG.within(pt1, pt1) -# Different points +# Different points -> not within @test GO.within(pt1, pt2) == LG.within(pt1, pt2) # # Point and line -# Line endpoint (1 segment) +# Line endpoint (1 segment) -> not within @test GO.within(pt1, l1) == LG.within(pt1, l1) -# Line endpoint (2 segments) +# Line endpoint (2 segments) -> not within @test GO.within(pt2, l2) == LG.within(pt2, l2) -# Middle of line (1 segment) +# Middle of line (1 segment) -> within @test GO.within(pt2, l1) == LG.within(pt2, l1) -# Not on line (1 segment) +# Not on line (1 segment) -> not within @test GO.within(pt3, l1) == LG.within(pt3, l1) -# Middle of line on joint (2 segments) +# Middle of line on joint (2 segments) -> within @test GO.within(pt3, l2) == LG.within(pt3, l2) -# Endpoint on closed line -@test GO.within(pt1, l6) == GO.within(pt1, l6) +# Endpoint on closed line -> within +@test GO.within(pt1, l6) == LG.within(pt1, l6) # # Point and Ring -# On ring corner +# On ring corner -> within @test GO.within(pt1, r1) == LG.within(pt1, r1) -# Outside of ring +# Outside of ring -> not within @test GO.within(pt2, r1) == LG.within(pt2, r1) -# Inside of ring center (not on line, so not within) +# Inside of ring center (not on line) -> not within @test GO.within(pt3, r1) == LG.within(pt3, r1) -# On ring edge -@test GO.within(pt7, r1) == LG.within(pt7, r1) +# On ring edge -> within +@test GO.within(pt8, r1) == LG.within(pt8, r1) # # Point and polygon -# On polygon vertex +# On polygon vertex -> not within @test GO.within(pt1, p1) == LG.within(pt1, p1) -# Outside of polygon +# Outside of polygon -> not within @test GO.within(pt2, p1) == LG.within(pt2, p1) -# Inside of polygon +# Inside of polygon -> within @test GO.within(pt3, p1) == LG.within(pt3, p1) -# On polygon vertex (with holes) +# On polygon vertex (with holes) -> not within @test GO.within(pt1, p2) == LG.within(pt1, p2) -# On polygon edge (with holes) +# On polygon edge (with holes) -> not within @test GO.within(pt2, p2) == LG.within(pt2, p2) -# On hole vertex +# On hole vertex -> not within @test GO.within(pt5, p2) == LG.within(pt5, p2) -# Within hole +# Within hole -> not within @test GO.within(pt6, p2) == LG.within(pt6, p2) -# Inside of polygon (with holes) +# Inside of polygon (with holes) -> within @test GO.within(pt7, p2) == LG.within(pt7, p2) +# # Geometry and point + # # Line and line -# Equal lines +# Equal lines -> within @test GO.within(l1, l1) == LG.within(l1, l1) -# Lines share endpoints, but don't overlap +# Lines share 2 endpoints, but don't overlap -> not within @test GO.within(l1, l2) == LG.within(l1, l2) -# Lines overlap, but neither is within other +# Lines overlap, but neither is within other -> not within @test GO.within(l1, l3) == LG.within(l1, l3) -# Within line (no shared endpoints) +# Within line (no shared endpoints) -> within @test GO.within(l1, l4) == LG.within(l1, l4) -# Within line (shares endpoints) +# Line shares just one endpoint -> not within @test GO.within(l1, l5) == LG.within(l1, l5) -# Not within line (flipped previous test) -@test GO.within(l5, l1) == LG.within(l5, l1) # # Line and ring -# Shares all endpoints (within) +# Shares all endpoints -> within @test GO.within(l6, r1) == LG.within(l6, r1) -# Shares all endpoints (within) +# Shares all endpoints, but ring has extra edge -> within @test GO.within(l2, r2) == LG.within(l2, r2) -# Doesn't share all edges +# Doesn't share all edges -> not within @test GO.within(l2, r3) == LG.within(l2, r3) -# Shares all endpoints, but adds one extra segment (not within) +# Shares all endpoints, but adds one extra segment -> not within @test GO.within(l12, r1) == LG.within(l12, r1) # Line and polygon -# Line traces entire outline of polygon edges +# Line traces entire outline of polygon edges -> not within @test GO.within(l6, p1) == LG.within(l6, p1) -# Line is edge of polygon +# Line is edge of polygon -> not within @test GO.within(l1, p2) == LG.within(l1, p2) -# Line is on edge + inside of polygon +# Line is on edge + inside of polygon -> within @test GO.within(l2, p2) == LG.within(l2, p2) -# Line goes outside of polygon +# Line goes outside of polygon -> not within @test GO.within(l3, p2) == LG.within(l3, p2) -# Line is fully within polygon +# Line is fully within polygon -> within @test GO.within(l7, p2) == LG.within(l7, p2) -# Line is fully within hole +# Line is fully within hole -> not within @test GO.within(l8, p2) == LG.within(l8, p2) -# Line is on hole edge +# Line is on hole edge -> not within @test GO.within(l9, p2) == LG.within(l9, p2) -# Line is on polygon edge and then cuts into polygon ending on hole vertex +# Line on polygon edge and then enters polygon to end on hole vertex -> within @test GO.within(l10, p2) == LG.within(l10, p2) -# Line is on polygon edge and then cuts through hole +# Line is on polygon edge and then cuts through hole -> not within @test GO.within(l11, p2) == LG.within(l11, p2) # # Ring and line -# Shares all endpoints (within) +# Shares all endpoints -> within @test GO.within(r1, l6) == LG.within(r1, l6) -# Shares all endpoints (within) +# Shares all endpoints but ring has closing edge -> not within @test GO.within(r2, l2) == LG.within(r2, l2) -# Doesn't share all edges +# Doesn't share all edges -> not within @test GO.within(r3, l2) == LG.within(r3, l2) -# Shares all endpoints, but line has one extra segment (within) +# Shares all endpoints, but line has one extra segment -> within @test GO.within(r1, l12) == LG.within(r1, l12) # # Ring and ring -# Equal ring +# Equal ring -> within @test GO.within(r1, r1) == LG.within(r1, r1) -# Not equal ring +# Not equal ring -> not within @test GO.within(r1, r2) == LG.within(r1, r2) -# Not equal ring +# Not equal ring -> not within @test GO.within(r1, r3) == LG.within(r1, r3) -# Rings share all edges, but second ring has extra edges +# Rings share all edges, but second ring has extra edges -> within @test GO.within(r2, r7) == LG.within(r2, r7) # # Ring and polygon -# Ring is equal to polygon's external ring, no holes +# Ring is equal to polygon's external ring, no holes -> not within @test GO.within(r1, p1) == LG.within(r1, p1) -# Ring goes outside of polygon's external ring +# Ring goes outside of polygon's external ring -> not within @test GO.within(r1, p2) == LG.within(r1, p1) -# Ring is within polygon, but also on edges +# Ring is within polygon, but also on edges -> within @test GO.within(r2, p2) == LG.within(r2, p2) -# Ring is within polygon, but also on edges +# Ring is within polygon, but also on edges -> within @test GO.within(r3, p2) == LG.within(r3, p2) -# Ring is one of polygon's holes +# Ring is one of polygon's holes -> not within @test GO.within(r4, p2) == LG.within(r4, p2) -# Ring is fully within polygon that has holes +# Ring is fully within polygon that has holes -> within @test GO.within(r5, p2) == LG.within(r5, p2) -# Ring is fully within polygon's hole +# Ring is fully within polygon's hole -> not within @test GO.within(r6, p2) == LG.within(r6, p2) # # Polygon in polygon -# Same polygon +# Same polygon -> within @test GO.within(p1, p1) == LG.within(p1, p1) @test GO.within(p2, p2) == LG.within(p2, p2) -# Polygon not in polygon +# Polygon not in polygon -> not within @test GO.within(p1, p2) == LG.within(p1, p2) @test GO.within(p2, p1) == LG.within(p2, p1) -# Polygon is within polygon, but also on edges +# Polygon is within polygon, but also on edges -> within @test GO.within(p3, p2) == LG.within(p3, p2) -# Polygon within polygon with holes +# Polygon within polygon with holes -> within @test GO.within(p4, p2) == LG.within(p4, p2) # Polygon within polygon hole --> not within @test GO.within(p5, p2) == LG.within(p5, p2) -# Polygon overlapping with other polygon's hole +# Polygon overlapping with other polygon's hole -> not within @test GO.within(p6, p2) == LG.within(p6, p2) # Polygon with hole nested with other polygon's hole --> within @test GO.within(p8, p7) == LG.within(p8, p7) -# Nested holes but not within +# Nested holes but not within -> not within @test GO.within(p9, p7) == LG.within(p9, p7) -# Nested with same hole +# Nested with same hole -> within @test GO.within(p10, p7) == LG.within(p10, p7) -# within external ring but intersects with hole +# within external ring but intersects with hole -> not within @test GO.within(p11, p7) == LG.within(p11, p7) -# polygon extactly overlaps with other polygon's hole +# polygon extactly overlaps with other polygon's hole -> not within @test GO.within(p12, p7) == LG.within(p12, p7) # # Multipolygon tests # Point in multipolygon @test GO.within(pt5, m1) == LG.within(pt5, m1) @test GO.within(pt9, m1) == LG.within(pt9, m1) - # Point outside of multipolygon @test GO.within(pt4, m1) == LG.within(pt4, m1) - # Line in multipolygon @test GO.within(l13, m1) == LG.within(l13, m1) @test GO.within(l9, m1) == LG.within(l9, m1) - # Line outside of multipolygon @test GO.within(l1, m1) == LG.within(l1, m1) - # Ring in multipolygon @test GO.within(r1, m2) == LG.within(r1, m2) - # Ring outside of multipolygon @test GO.within(r1, m1) == LG.within(r1, m1) - # Polygon in multipolygon @test GO.within(p3, m1) == LG.within(p3, m1) @test GO.within(p6, m1) == LG.within(p6, m1) - # Polygon outside of multipolygon @test GO.within(p1, m1) == LG.within(p1, m1) - # Multipolygon in multipolygon @test GO.within(m1, m1) == LG.within(m1, m1) - # Multipolygon outside of multipolygon @test GO.within(m2, m1) == LG.within(m2, m1) - - - - From be5c24f47d726442a6eb3e67a2f6b1e437b6aae5 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 19 Dec 2023 12:13:10 -0800 Subject: [PATCH 21/33] Generalize processor --- src/GeometryOps.jl | 2 +- src/methods/disjoint.jl | 66 +-- src/methods/geom_geom_processors.jl | 741 +++++++++++----------------- src/methods/overlaps.jl | 8 +- src/methods/touches.jl | 74 ++- src/methods/within.jl | 97 ++-- src/try.jl | 10 +- test/methods/disjoint.jl | 28 +- test/methods/touches.jl | 35 +- test/methods/within.jl | 2 - test/runtests.jl | 1 + 11 files changed, 468 insertions(+), 596 deletions(-) diff --git a/src/GeometryOps.jl b/src/GeometryOps.jl index ee865e4f5..17b665e58 100644 --- a/src/GeometryOps.jl +++ b/src/GeometryOps.jl @@ -34,7 +34,7 @@ include("methods/barycentric.jl") include("methods/equals.jl") include("methods/geom_geom_processors.jl") include("methods/orientation.jl") - +include("methods/touches.jl") include("transformations/flip.jl") include("transformations/simplify.jl") include("transformations/reproject.jl") diff --git a/src/methods/disjoint.jl b/src/methods/disjoint.jl index 7448b3e5f..1e680ee35 100644 --- a/src/methods/disjoint.jl +++ b/src/methods/disjoint.jl @@ -92,8 +92,7 @@ disjoint( ::GI.LineStringTrait, g2, ) = _point_curve_process( g1, g2; - process = disjoint_process, - exclude_boundaries = false, + in_allow = false, on_allow = false, out_allow = true, repeated_last_coord = false, ) @@ -108,8 +107,7 @@ disjoint( ::GI.LinearRingTrait, g2, ) = _point_curve_process( g1, g2; - process = disjoint_process, - exclude_boundaries = false, + in_allow = false, on_allow = false, out_allow = true, repeated_last_coord = true, ) @@ -125,8 +123,7 @@ disjoint( ::GI.PolygonTrait, g2, ) = _point_polygon_process( g1, g2; - process = disjoint_process, - exclude_boundaries = false, + in_allow = false, on_allow = false, out_allow = true, ) """ @@ -152,8 +149,8 @@ disjoint( ::GI.LineStringTrait, g2, ) = _line_curve_process( g1, g2; - process = disjoint_process, - exclude_boundaries = false, + in_allow = false, on_allow = false, out_allow = true, + in_require = false, on_require = false, out_require = false, closed_line = false, closed_curve = false, ) @@ -170,8 +167,8 @@ disjoint( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - process = disjoint_process, - exclude_boundaries = false, + in_allow = false, on_allow = false, out_allow = true, + in_require = false, on_require = false, out_require = false, closed_line = false, closed_curve = true, ) @@ -189,9 +186,9 @@ disjoint( ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - process = disjoint_process, - exclude_boundaries = false, - close = false, + in_allow = false, on_allow = false, out_allow = true, + in_require = false, on_require = false, out_require = true, + closed_line = false, ) """ @@ -229,8 +226,8 @@ disjoint( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - process = disjoint_process, - exclude_boundaries = false, + in_allow = false, on_allow = false, out_allow = true, + in_require = false, on_require = false, out_require = false, closed_line = true, closed_curve = true, ) @@ -248,9 +245,9 @@ disjoint( ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - process = disjoint_process, - exclude_boundaries = false, - close = true, + in_allow = false, on_allow = false, out_allow = true, + in_require = false, on_require = false, out_require = true, + closed_line = true, ) """ @@ -276,20 +273,25 @@ function disjoint( ::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2; ) - #= - if the exterior of g1 is disjoint from g2 (could be in a g2 hole), the - polygons are disjoint - =# - if disjoint(GI.getexterior(g1), g2) - return true - else - #= - if the exterior of g1 is not disjoint, the only way for the polygons to - be disjoint is if g2 is in a hole of g1 - =# - for hole in GI.gethole(g1) - if within(g2, hole) - return true + ext1 = GI.getexterior(g1) + e1_in_e2, e1_on_e2, e1_out_e2 = _line_filled_curve_interactions( + ext1, GI.getexterior(g2); + closed_line = true, + ) + e1_on_e2 && return false + !e1_in_e2 && return true + + for h2 in GI.gethole(g2) + if e1_in_e2 # h2 could be outside of e1, but inside of e2 + h2_in_e1, h2_on_e1, h2_out_e1 = _line_filled_curve_interactions( + h2, ext1; + closed_line = true, + ) + (h2_in_e1 || h2_on_e1) && return false + + if h2_out_e1 + c1_val = point_filled_curve_orientation(centroid(ext1), h2) + c1_val == point_in && return true # e1 is within h2 end end end diff --git a/src/methods/geom_geom_processors.jl b/src/methods/geom_geom_processors.jl index 2a5b9b22c..ad56b6060 100644 --- a/src/methods/geom_geom_processors.jl +++ b/src/methods/geom_geom_processors.jl @@ -1,41 +1,6 @@ -@enum ProcessType within_process=1 disjoint_process=2 touch_process=3 coverby_process=4 +@enum PointOrientation point_in=1 point_on=2 point_out=3 -@enum PointOrientation point_in=1 point_on=2 point_off=3 - -""" - get_process_return_vals( - process::ProcessType, - exclude_boundaries = false, - )::(Bool, Bool, Bool) - -Returns a tuple of booleans which represent the boolean return value for it a -point is in, out, or on a given geometry. This is determined by the process -type as well as by the exclude_boundaries. - -For within_process: - if a point is in a geometry, we should return true - if a point is out of a geomertry, we should return false - if a point is on the boundary of a geometry, we should return false if we - want to exclude boundaries, else we should return true -For disjoint_process: - if a point is in a geometry, we should return false - if a point is out of a geometry, we should return true - if a point is on the boundary of a geometry, we should return true if we - want to exclude boundaries, else we should return false -For touch_process: - ??? -""" -get_process_return_vals(process::ProcessType, exclude_boundaries = false) = - ( - process == within_process, # in value - process == disjoint_process, # out value - ( # on value - (process == touch_process) || - (process == within_process ? - !exclude_boundaries : exclude_boundaries - ) - ) - ) +@enum LineOrientation line_cross=1 line_hinge=2 line_over=3 line_out=4 """ _point_curve_process( @@ -61,37 +26,33 @@ with repeated a repeated last coordinate matching the first coordinate. """ function _point_curve_process( point, curve; - process::ProcessType = within_process, - exclude_boundaries = false, + in_allow, on_allow, out_allow, repeated_last_coord = false, ) n = GI.npoint(curve) first_last_equal = equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) repeated_last_coord |= first_last_equal - exclude_boundaries |= first_last_equal n -= first_last_equal ? 1 : 0 - in_val, out_val, on_val = get_process_return_vals( - process, - exclude_boundaries, - ) # Loop through all curve segments p_start = GI.getpoint(curve, repeated_last_coord ? n : 1) @inbounds for i in (repeated_last_coord ? 1 : 2):n p_end = GI.getpoint(curve, i) - seg_val = _point_in_on_out_segment(point, p_start, p_end) - seg_val == 1 && return in_val - if seg_val == -1 - i == 2 && equals(point, p_start) && return on_val - i == n && equals(point, p_end) && return on_val - return in_val + seg_val = point_segment_orientation(point, p_start, p_end) + seg_val == point_in && return in_allow + if seg_val == point_on + if !repeated_last_coord + i == 2 && equals(point, p_start) && return on_allow + i == n && equals(point, p_end) && return on_allow + end + return in_allow end p_start = p_end end - return out_val + return out_allow end """ - _point_in_on_out_segment( + point_segment_orientation( point::Point, start::Point, stop::Point; in::T = 1, on::T = -1, out::T = 0, )::T where {T} @@ -101,9 +62,9 @@ segment it is on one of the segments endpoints. If it is 'in', it is on any other point of the segment. If the point is not on any part of the segment, it is 'out' of the segment. """ -function _point_in_on_out_segment( +function point_segment_orientation( point, start, stop; - in::T = 1, on::T = -1, out::T = 0, + in::T = point_in, on::T = point_on, out::T = point_out, ) where {T} # Parse out points x, y = GI.x(point), GI.y(point) @@ -132,51 +93,8 @@ function _point_in_on_out_segment( return in end - -point_return_val(point_val, in_val, out_val, on_val) = - point_val == 1 ? - in_val : # point is inside of polygon - (point_val == 0 ? - out_val : # point is outside of polygon - on_val # point is on the edge of polygon - ) - """ - _point_closed_curve_process( - point, curve; - process::ProcessType = within_process, - exclude_boundary = false, - )::Bool - -Determines if a point meets the given process checks with respect to a closed -curve, which includes the space enclosed by the closed curve. Point should be an -object of Point trait and curve should be an object with a line string or linear -ring trait, that is assumed to be closed, regardless of repeated last point. - -If checking within, then the point must be within the space enclosed by the -curve and if checking disjoint the point must not be outside of the curve. - -Beyond specifying the process type, user can also specify if the geometry -boundaries should be included in the checks and if the curve should be closed -with repeated a repeated last coordinate matching the first coordinate. -""" -function _point_closed_curve_process( - point, curve; - process::ProcessType = within_process, - exclude_boundaries = false, -) - in_val, out_val, on_val = get_process_return_vals( - process, - exclude_boundaries, - ) - return _point_in_on_out_closed_curve( - point, curve; - in = in_val, out = out_val, on = on_val - ) -end - -""" - _point_in_on_out_closed_curve( + point_filled_curve_orientation( point, curve; in::T = 1, on::T = -1, out::T = 0, )::T where {T} @@ -201,9 +119,9 @@ passes through an odd number of edges, it is within the curve, else outside of of the curve if it didn't return 'on'. See paper for more information on cases denoted in comments. """ -function _point_in_on_out_closed_curve( +function point_filled_curve_orientation( point, curve; - in::T = 1, on::T = -1, out::T = 0, + in::T = point_in, on::T = point_on, out::T = point_out, ) where {T} x, y = GI.x(point), GI.y(point) n = GI.npoint(curve) @@ -217,17 +135,19 @@ function _point_in_on_out_closed_curve( if !((v1 < 0 && v2 < 0) || (v1 > 0 && v2 > 0)) # if not cases 11 or 26 u1 = GI.x(p_start) - x u2 = GI.x(p_end) - x - f = u1 * v2 - u2 * v1 + c1 = u1 * v2 # first element of cross product summation + c2 = u2 * v1 # second element of cross product summation + f = c1 - c2 if v2 > 0 && v1 ≤ 0 # Case 3, 9, 16, 21, 13, or 24 - f == 0 && return on # Case 16 or 21 + (c1 ≈ c2) && return on # Case 16 or 21 f > 0 && (k += 1) # Case 3 or 9 elseif v1 > 0 && v2 ≤ 0 # Case 4, 10, 19, 20, 12, or 25 - f == 0 && return on # Case 19 or 20 + (c1 ≈ c2) && return on # Case 19 or 20 f < 0 && (k += 1) # Case 4 or 10 elseif v2 == 0 && v1 < 0 # Case 7, 14, or 17 - f == 0 && return on # Case 17 + (c1 ≈ c2) && return on # Case 17 elseif v1 == 0 && v2 < 0 # Case 8, 15, or 18 - f == 0 && return on # Case 18 + (c1 ≈ c2) && return on # Case 18 elseif v1 == 0 && v2 == 0 # Case 1, 2, 5, 6, 22, or 23 u2 ≤ 0 && u1 ≥ 0 && return on # Case 1 u1 ≤ 0 && u2 ≥ 0 && return on # Case 2 @@ -248,414 +168,305 @@ end Determines if a point meets the given process checks with respect to a polygon, which excludes any holes specified by the polygon. Point should be an object of Point trait and polygon should an object with a Polygon trait. - """ -_point_polygon_process( +function _point_polygon_process( point, polygon; - process::ProcessType = within_process, - exclude_boundaries = false, -) = _geom_polygon_process( - point, polygon, - _point_closed_curve_process; - process = process, - ext_exclude_boundaries = exclude_boundaries, - hole_exclude_boundaries = !exclude_boundaries + in_allow, on_allow, out_allow, ) + # Check interaction of geom with polygon's exterior boundary + ext_val = point_filled_curve_orientation(point, GI.getexterior(polygon)) + # If a point is outside, it isn't interacting with any holes + ext_val == point_out && return out_allow + # if a point is on an external boundary, it isn't interacting with any holes + ext_val == point_on && return on_allow + + # If geom is within the polygon, need to check interactions with holes + for hole in GI.gethole(polygon) + hole_val = point_filled_curve_orientation(point, hole) + # If a point in in a hole, it is outside of the polygon + hole_val == point_in && return out_allow + # If a point in on a hole edge, it is on the edge of the polygon + hole_val == point_on && return on_allow + end + + # Point is within external boundary and on in/on any holes + return in_allow +end + +function _segment_segment_orientation( + (a_point, b_point), (c_point, d_point); + cross::T = line_cross, hinge::T = line_hinge, + over::T = line_over, out::T = line_out, +) where T + (ax, ay) = _tuple_point(a_point) + (bx, by) = _tuple_point(b_point) + (cx, cy) = _tuple_point(c_point) + (dx, dy) = _tuple_point(d_point) + meet_type = ExactPredicates.meet((ax, ay), (bx, by), (cx, cy), (dx, dy)) + # Lines meet at one point within open segments + meet_type == 1 && return cross + # Lines don't meet at any points + meet_type == -1 && return out + # Lines meet at one or more points within closed segments + if _isparallel(((ax, ay), (bx, by)), ((cx, cy), (dx, dy))) + min_x, max_x = cx < dx ? (cx, dx) : (dx, cx) + min_y, max_y = cy < dy ? (cy, dy) : (dy, cy) + if ( + ((ax ≤ min_x && bx ≤ min_x) || (ax ≥ max_x && bx ≥ max_x)) && + ((ay ≤ min_y && by ≤ min_y) || (ay ≥ max_y && by ≥ max_y)) + ) + # a_point and b_point are on the same side of segment, don't overlap + return hinge + else + return over + end + end + # if lines aren't parallel then they must hinge + return hinge +end function _line_curve_process( line, curve; - process::ProcessType = within_process, - exclude_boundaries = false, + in_allow, on_allow, out_allow, + in_require, on_require, out_require, closed_line = false, closed_curve = false, ) + in_req_met = !in_require + on_req_met = !on_require + out_req_met = !out_require + # Determine curve endpoints nl = GI.npoint(line) nc = GI.npoint(curve) - explicit_closed_line = equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) - explicit_closed_curve = equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) - nl -= explicit_closed_line ? 1 : 0 - nc -= explicit_closed_curve ? 1 : 0 - closed_line |= explicit_closed_line - closed_curve |= explicit_closed_curve - exclude_boundaries |= explicit_closed_curve - - check_func = - if process == within_process - (args...) -> _line_curve_within_checks( - args...; - nc = nc, closed_curve = closed_curve, exclude_boundaries, - ) - else - (args...) -> _line_curve_disjoint_checks( - args...; - nc = nc, closed_curve = closed_curve, exclude_boundaries, - ) - end - - l_start = _tuple_point(GI.getpoint(line, closed_line ? nl : 1)) + first_last_equal_line = equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) + first_last_equal_curve = equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) + nl -= first_last_equal_line ? 1 : 0 + nc -= first_last_equal_curve ? 1 : 0 + closed_line |= first_last_equal_line + closed_curve |= first_last_equal_curve + + # Loop over each line segment + l_start = GI.getpoint(line, closed_line ? nl : 1) i = closed_line ? 1 : 2 while i ≤ nl - l_end = _tuple_point(GI.getpoint(line, i)) - c_start = _tuple_point(GI.getpoint(curve, closed_curve ? nc : 1)) + l_end = GI.getpoint(line, i) + c_start = GI.getpoint(curve, closed_curve ? nc : 1) + # Loop over each curve segment for j in (closed_curve ? 1 : 2):nc - c_end = _tuple_point(GI.getpoint(curve, j)) - - meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) - passes_checks, break_loop, i, l_start = check_func( - meet_type, - l_start, l_end, - c_start, c_end, - i, j, - ) - break_loop && break - !passes_checks && return false - c_start = c_end - end - end - return true -end - -function _line_curve_within_checks( - meet_type, - l_start, l_end, - c_start, c_end, - i, j; - nc, - closed_curve, - exclude_boundaries, -) - is_within = true - break_loop = false - #= - if l_start is in/on curve and curve and line meet either at - endpoints or are parallel and meet in multiple points - =# - if ( - meet_type == 0 && - _point_in_on_out_segment(l_start, c_start, c_end) != 0 - ) - #= - if excluding first and last point of curve, make sure those points - aren't within the line segment - =# - if exclude_boundaries && !closed_curve && (( - j == 2 && _point_in_on_out_segment(c_start, l_start, l_end) != 0 - ) || ( - j == nc && _point_in_on_out_segment(c_end, l_start, l_end) != 0 - )) - is_within = false - else # if end points aren't being excluded - # if l_end is within curve, whole line is contained in curve - if _point_in_on_out_segment(l_end, c_start, c_end) != 0 - i += 1 - l_start = l_end - break_loop = true - #= - if c_start is in line, then need to find overlap for c_start - to l_end as l_start to c_start is overlapping with curve - =# - elseif _point_in_on_out_segment( - c_start, - l_start, l_end, - ) == 1 - l_start = c_start - break_loop = true - #= - if c_end is in line, then need to find overlap for c_end to - l_end as l_start to c_end is overlapping with curve - =# - elseif _point_in_on_out_segment( - c_end, - l_start, l_end, - ) == 1 - l_start = c_end - break_loop = true - end - end - end - #= - if line segment has been checked against all curve segments and it isn't - within any of them, line isn't within curve - =# - if j == nc - is_within = false - end - return is_within, break_loop, i, l_start -end - -function _line_curve_disjoint_checks( - meet_type, - l_start, l_end, - c_start, c_end, - i, j; - nc, - closed_curve, - exclude_boundaries, -) - is_disjoint = true - break_loop = false - #= - if excluding first and last point of curve, line can still cross those - points and be disjoint - =# - if ( - exclude_boundaries && meet_type == 0 && - !closed_curve && (j == 2 || j == nc) - ) - # if line and curve are parallel, they cannot overlap and be disjoint - if _isparallel(l_start, l_end, c_start, c_end) - (p1, p2) = - if j == 2 && equals(c_start, l_start) - (l_end, c_end) - elseif j == 2 && equals(c_start, l_end) - (l_start, c_end) - elseif j == nc && equals(c_end, l_start) - (l_end, c_start) - elseif j == nc &&equals(c_end, l_end) - (l_start, c_start) - else - is_disjoint = false - end - if is_disjoint && ( - _point_in_on_out_segment(p1, c_start, c_end) || - _point_in_on_out_segment(p2, l_start, l_end) - ) - is_disjoint = false - end - #= - if line and curve aren't parallel, they intersection must be either the - start or end point of the curve to be disjoint - =# - else - _, (_, c_frac) = _intersection_point( + c_end = GI.getpoint(curve, j) + # Check if line and curve segments meet + seg_val = _segment_segment_orientation( (l_start, l_end), (c_start, c_end), ) - if ( - j == 2 && c_frac != 0 || - j == nc && c_frac != 1 - ) - is_disjoint = false + # if segments are touching + if seg_val == line_over + !in_allow && return false + # at least one point in, meets requirments + in_req_met = true + if seg_val == line_over + point_val = point_segment_orientation( + l_start, + c_start, c_end, + ) + if point_val != point_out + if point_segment_orientation( + l_end, + c_start, c_end, + ) != point_out + l_start = l_end + i += 1 + break + elseif point_segment_orientation( + c_start, + l_start, l_end, + ) != point_out + l_start = c_start + break + elseif point_segment_orientation( + c_end, + l_start, l_end, + ) != point_out + l_start = c_end + break + end + end + end + else + if seg_val == line_hinge + !on_allow && return false + # at least one point on, meets requirments + on_req_met = true + elseif seg_val == line_cross + !in_allow && return false + # at least one point in, meets requirments + in_req_met = true + end + # no overlap for a give segment + if j == nc + !out_allow && return false + out_req_met = true + end end + c_start = c_end + j == nc && (i += 1) end - #= - if not excluding first and last point of curve, line cannot intersect with - any points of the curve - =# - elseif meet_type != -1 - is_disjoint = false - end - #= - if line segment has been checked against all curve segments and is disjoint - from all of them, we can now check the next line segment - =# - if j == nc - i += 1 - l_start = l_end end - return is_disjoint, break_loop, i, l_start + return in_req_met && on_req_met && out_req_met end -function _line_closed_curve_process( +function _line_filled_curve_interactions( line, curve; - process::ProcessType = within_process, - exclude_boundaries = false, - close = false, - line_is_poly_ring = false, + closed_line = false, ) - #= - if line isn't the external ring of a polygon, see if at least one point is - within the closed curve - else, ring is "filled in" and has points within - closed curve - =# - point_in = line_is_poly_ring - in_val, out_val, on_val = get_process_return_vals(process, exclude_boundaries) + in_curve = false + on_curve = false + out_curve = false + # Determine number of points in curve and line - nc = GI.npoint(curve) - nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 nl = GI.npoint(line) - nl -= equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) ? 1 : 0 + nc = GI.npoint(curve) + first_last_equal_line = equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) + first_last_equal_curve = equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) + nl -= first_last_equal_line ? 1 : 0 + nc -= first_last_equal_curve ? 1 : 0 + closed_line |= first_last_equal_line + + # See if first point is in an acceptable orientation + l_start = GI.getpoint(line, closed_line ? nl : 1) + point_val = point_filled_curve_orientation(l_start, curve) + if point_val == point_in + in_curve = true + elseif point_val == point_on + on_curve = true + else # point_val == point_out + out_curve = true + end - # Check to see if first point in line is within curve - l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) - point_val = _point_in_on_out_closed_curve(l_start, curve) - point_in |= point_val == 1 # check if point is within closed curve - point_return = point_return_val(point_val, in_val, out_val, on_val) + # Check for any intersections between line and curve + for i in (closed_line ? 1 : 2):nl + l_end = GI.getpoint(line, i) + c_start = GI.getpoint(curve, nc) - # point is not in correct orientation to curve given process and boundary - !point_return && return point_return + # If already interacted with all regions of curve, can stop + in_curve && on_curve && out_curve && break - # Check for any intersections between line and curve - for i in (close ? 1 : 2):nl - l_end = _tuple_point(GI.getpoint(line, i)) - c_start = _tuple_point(GI.getpoint(curve, nc)) for j in 1:nc - c_end = _tuple_point(GI.getpoint(curve, j)) - # Check if edges intersect --> line crosses --> wrong orientation - meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) - # open line segments meet in a single point - meet_type == 1 && return false - #= - closed line segments meet in one or several points -> meet at a - vertex or on the edge itself (parallel) - =# - if meet_type == 0 - # See if segment is parallel and within curve edge - p1_on_seg = _point_in_on_out_segment( - l_start, - c_start, c_end, - ) != 0 - exclude_boundaries && p1_on_seg && return false - p2_on_seg = _point_in_on_out_segment( - l_end, - c_start, c_end, - ) != 0 - exclude_boundaries && p2_on_seg && return false - # if segment isn't contained within curve edge - if !p1_on_seg || !p2_on_seg - # Make sure l_start is in corrent orientation - if !p1_on_seg - p1_val = _point_in_on_out_closed_curve(l_start, curve) - # check if point is within closed curve - point_in |= p1_on_seg == 1 - !point_return_val(p1_val, in_val, out_val, on_val) && - return false + c_end = GI.getpoint(curve, j) + # Check if two line and curve segments meet + seg_val = _segment_segment_orientation( + (l_start, l_end), + (c_start, c_end), + ) + if seg_val != line_out + # If line and curve meet, then at least one point is on boundary + on_curve = true + if seg_val == line_cross + # When crossing boundary, line is both in and out of curve + in_curve = true + out_curve = true + else + if seg_val == line_over + sp = point_segment_orientation(l_start, c_start, c_end) + lp = point_segment_orientation(l_end, c_start, c_end) + if sp != point_in || lp != point_in + #= + Line crosses over segment endpoint, creating a hinge + with another segment. + =# + seg_val = line_hinge + end end - # Make sure l_end is in is in corrent orientation - if !p2_on_seg - p2_val = _point_in_on_out_closed_curve(l_end, curve) - # check if point is within closed curve - point_in |= p2_val == 1 - !point_return_val(p2_val, in_val, out_val, on_val) && - return false + if seg_val == line_hinge + #= + Can't determine all types of interactions (in, out) with + hinge as it could pass through multiple other segments + so calculate if segment endpoints and intersections are + in/out of filled curve + =# + ipoints = intersection_points( + GI.Line([l_start, l_end]), + curve + ) + npoints = length(ipoints) # since hinge, at least one + sort!(ipoints, by = p -> euclid_distance(p, l_start)) + p_start = _tuple_point(l_start) + for i in 1:(npoints + 1) + p_end = i ≤ npoints ? + ipoints[i] : + _tuple_point(l_end) + mid_val = point_filled_curve_orientation( + (p_start .+ p_end) ./ 2, + curve, + ) + if mid_val == point_in + in_curve = true + elseif mid_val == point_out + out_curve = true + end + end + # already checked segment against whole filled curve + l_start = l_end + break end - #= - If both endpoints are in the correct orientation, but not - parallel to the edge, make sure that midpoints between the - intersections along the segment are also in the correct - orientation - =# - mid_vals, mid_in = _segment_mids_closed_curve_process( - l_start, l_end, curve; - process = process, - exclude_boundaries = exclude_boundaries, - ) - point_in |= mid_in - # midpoint on the wrong side of the curve - !mid_vals && return false - # line segment is fully within or on curve - break end end c_start = c_end end l_start = l_end end - # check if line is on any curve edges or vertcies - return process == within_process ? point_in : true + return in_curve, on_curve, out_curve end -function _segment_mids_closed_curve_process( - l_start, l_end, curve; - process::ProcessType = within_process, - exclude_boundaries = false, +function _line_polygon_process( + line, polygon; + in_allow, on_allow, out_allow, + in_require, on_require, out_require, + closed_line = false, ) - point_in = false - in_val, out_val, on_val = get_process_return_vals(process, exclude_boundaries) - # Find intersection points - ipoints = intersection_points( - GI.Line([l_start, l_end]), - curve + in_req_met = !in_require + on_req_met = !on_require + out_req_met = !out_require + # Check interaction of line with polygon's exterior boundary + in_curve, on_curve, out_curve = _line_filled_curve_interactions( + line, GI.getexterior(polygon); + closed_line = closed_line, ) - npoints = length(ipoints) - if npoints < 3 # only intersection points are the endpoints - mid_val = _point_in_on_out_closed_curve((l_start .+ l_end) ./ 2, curve) - point_in |= mid_val == 1 - mid_return = point_return_val(mid_val, in_val, out_val, on_val) - !mid_return && return (false, point_in) - else # more intersection points than the endpoints - # sort intersection points along the line - sort!(ipoints, by = p -> euclid_distance(p, l_start)) - p_start = ipoints[1] - for i in 2:npoints - p_end = ipoints[i] - # check if midpoint of intersection points is within the curve - mid_val = _point_in_on_out_closed_curve( - (p_start .+ p_end) ./ 2, - curve, - ) - point_in |= mid_val == 1 - mid_return = point_return_val(mid_val, in_val, out_val, on_val) - !mid_return && return (false, point_in) - end + if on_curve + !on_allow && return false + on_req_met = true end - # all intersection point midpoints were in or on the curve - return true, point_in -end - -_line_polygon_process( - line, polygon; - process::ProcessType = within_process, - exclude_boundaries = true, - close = false, - line_is_poly_ring = false, -) = _geom_polygon_process( - line, polygon, - (args...; kwargs...) -> _line_closed_curve_process( - args...; - kwargs..., - close = close, - line_is_poly_ring = line_is_poly_ring, - ); - process = process, - ext_exclude_boundaries = exclude_boundaries, - hole_exclude_boundaries = - process == within_process ? - exclude_boundaries : - !exclude_boundaries, -) - + if out_curve + !out_allow && return false + out_req_met = true + end + !in_curve && return in_req_met && on_req_met && out_req_met -function _geom_polygon_process( - geom, polygon, geom_closed_curve_func; - process::ProcessType = within_process, - ext_exclude_boundaries = true, - hole_exclude_boundaries = false -) - # Check interaction of geom with polygon's exterior boundary - ext_val = geom_closed_curve_func( - geom, GI.getexterior(polygon); - process = process, exclude_boundaries = ext_exclude_boundaries, - ) - - #= - If checking within and geom is outside of exterior ring, return false or - if checking disjoint and geom is outside of exterior ring, return true. - =# - ((process == within_process && !ext_val) || - (process == disjoint_process && ext_val) - ) && return ext_val - - # If geom is within the polygon, need to check interactions with holes + # Loop over polygon holes for hole in GI.gethole(polygon) - hole_val = geom_closed_curve_func( - geom, hole, - process = ( - process == within_process ? - disjoint_process : - within_process - ), - exclude_boundaries = hole_exclude_boundaries + in_hole, on_hole, out_hole =_line_filled_curve_interactions( + line, hole; + closed_line = closed_line, ) - #= - If checking within and geom is not disjoint from hole, return false or - if checking disjoint and geom is within hole, return true. - =# - process == within_process && !hole_val && return false - process == disjoint_process && hole_val && return true + if in_hole + !out_allow && return false + out_req_met = true + end + if on_hole + !on_allow && return false + on_req_met = true + end + if !out_hole # entire line is in/on hole, can't be in/on other holes + in_curve = false + break + end + end + if in_curve + !in_allow && return false + in_req_met = true end - return ext_val + return in_req_met && on_req_met && out_req_met end function _point_in_extent(p, extent::Extents.Extent) diff --git a/src/methods/overlaps.jl b/src/methods/overlaps.jl index de723fbd6..82f4f94c2 100644 --- a/src/methods/overlaps.jl +++ b/src/methods/overlaps.jl @@ -228,9 +228,9 @@ function _overlaps( on_top = ExactPredicates.meet(a1, a2, b1, b2) == 0 on_top || return false # check that one endpoint of each edge is within other edge - a1_in = _point_in_on_out_segment(a1, b1, b2) == 1 - a2_in = _point_in_on_out_segment(a2, b1, b2) == 1 - b1_in = _point_in_on_out_segment(b1, a1, a2) == 1 - b2_in = _point_in_on_out_segment(b2, a1, a2) == 1 + a1_in = point_segment_orientation(a1, b1, b2) == point_in + a2_in = point_segment_orientation(a2, b1, b2) == point_in + b1_in = point_segment_orientation(b1, a1, a2) == point_in + b2_in = point_segment_orientation(b2, a1, a2) == point_in return (a1_in ⊻ a2_in) && (b1_in ⊻ b2_in) end diff --git a/src/methods/touches.jl b/src/methods/touches.jl index 7dbe956f9..ed2672e6e 100644 --- a/src/methods/touches.jl +++ b/src/methods/touches.jl @@ -17,28 +17,78 @@ touches( """ touches(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool -If a point touches a linestring if it equal to +If a point touches a linestring if it equal to either the first of last point of +the linestring, which make up the linestrings boundaries. If the first and last +point are equal, closing the linestring, then no point can touch the linestring. """ -touches( +function touches( ::GI.PointTrait, g1, ::GI.LineStringTrait, g2, -) = _point_curve_process( - g1, g2; - process = touch_process, - repeated_last_coord = false, ) + n = GI.npoint(g2) + p1 = GI.getpoint(g2, 1) + pn = GI.getpoint(g2, n) + equals(p1, pn) && return false + return equals(g1, p1) || equals(g1, pn) +end """ touches(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool -If a point is disjoint from a linear ring then it is not on any of the -ring's edges or vertices. If these conditions are met, return true, else false. +If a point cannot 'touch' a linear ring given that the linear ring has no +boundary points. Since the whole ring is "interior", a point cannot touch it. """ touches( ::GI.PointTrait, g1, ::GI.LinearRingTrait, g2, -) = _point_curve_process( +) = false + +""" + touches(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A point touches a polygon if it is on the boundary of that polygon. +Return true if those conditions are met, else false. +""" +touches( + ::GI.PointTrait, g1, + ::GI.PolygonTrait, g2, +) = _point_polygon_process( + g1, g2; + in_allow = false, on_allow = true, out_allow = false, +) + +# Lines touching geometries +""" + touches(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool + +A line string touches another linestring only if at least one endpoints +(boundary point) of one of the linestrings intersects with the other linestring +""" +touches( + ::GI.LineStringTrait, g1, + ::GI.LineStringTrait, g2, +) = _line_curve_process( + g1, g2; + in_allow = false, on_allow = true, out_allow = true, + in_require = false, on_require = true, out_require = false, + closed_line = false, + closed_curve = false, +) + +""" + touches(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +A linestring touches a linear ring if the vertices and edges of the +linestring are touches the linear ring. Return true if those conditions are met, +else false. +""" +touches( + ::GI.LineStringTrait, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_process( g1, g2; - process = touch_process, - repeated_last_coord = true, -) \ No newline at end of file + in_allow = false, on_allow = true, out_allow = true, + in_require = false, on_require = true, out_require = false, + closed_line = false, + closed_curve = true, +) diff --git a/src/methods/within.jl b/src/methods/within.jl index ffcd7ae28..f3ce7cdbe 100644 --- a/src/methods/within.jl +++ b/src/methods/within.jl @@ -109,8 +109,7 @@ within( ::GI.LineStringTrait, g2, ) = _point_curve_process( g1, g2; - process = within_process, - exclude_boundaries = true, + in_allow = true, on_allow = false, out_allow = false, repeated_last_coord = false, ) @@ -125,8 +124,7 @@ within( ::GI.LinearRingTrait, g2, ) = _point_curve_process( g1, g2; - process = within_process, - exclude_boundaries = false, + in_allow = true, on_allow = false, out_allow = false, repeated_last_coord = true, ) @@ -141,8 +139,7 @@ within( ::GI.PolygonTrait, g2, ) = _point_polygon_process( g1, g2; - process = within_process, - exclude_boundaries = true, + in_allow = true, on_allow = false, out_allow = false, ) # Lines within geometries @@ -158,8 +155,8 @@ within( ::GI.LineStringTrait, g2, ) = _line_curve_process( g1, g2; - process = within_process, - exclude_boundaries = false, + in_allow = true, on_allow = true, out_allow = false, + in_require = true, on_require = false, out_require = false, closed_line = false, closed_curve = false, ) @@ -176,8 +173,8 @@ within( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - process = within_process, - exclude_boundaries = false, + in_allow = true, on_allow = true, out_allow = false, + in_require = true, on_require = false, out_require = false, closed_line = false, closed_curve = true, ) @@ -196,9 +193,9 @@ within( ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - process = within_process, - exclude_boundaries = false, - close = false, + in_allow = true, on_allow = true, out_allow = false, + in_require = true, on_require = false, out_require = false, + closed_line = false, ) # Rings within geometries @@ -214,8 +211,8 @@ within( ::GI.LineStringTrait, g2, ) = _line_curve_process( g1, g2; - process = within_process, - exclude_boundaries = false, + in_allow = true, on_allow = true, out_allow = false, + in_require = true, on_require = false, out_require = false, closed_line = true, closed_curve = false, ) @@ -232,8 +229,8 @@ within( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - process = within_process, - exclude_boundaries = false, + in_allow = true, on_allow = true, out_allow = false, + in_require = true, on_require = false, out_require = false, closed_line = true, closed_curve = true, ) @@ -252,9 +249,9 @@ within( ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - process = within_process, - exclude_boundaries = false, - close = true, + in_allow = true, on_allow = true, out_allow = false, + in_require = true, on_require = false, out_require = false, + closed_line = true, ) # Polygons within polygons @@ -269,38 +266,44 @@ function within( ::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2; ) - #= - For g1 to be within g2, the exterior of g1 must be within g2, including not - being in any of g2's holes. - Note: line_is_poly_ring is needed as ring can be exclusivly on g2 edge's and - still be "within" due to the interior being filled in in contrast to a - linestring or linear ring - =# - if _line_polygon_process( - GI.getexterior(g1), g2; - process = within_process, - exclude_boundaries = false, - close = true, - line_is_poly_ring = true + ext1 = GI.getexterior(g1) + e1_in_e2, _, e1_out_e2 = _line_filled_curve_interactions( + ext1, GI.getexterior(g2); + closed_line = true, ) - #= - now need to check that none of g2's holes are within g1 as this would - make the part of g1 within the hole outside of g2 - =# - for hole in GI.gethole(g2) - if _line_polygon_process( - hole, g1; - process = within_process, - exclude_boundaries = false, - close = true, - line_is_poly_ring = true + e1_out_e2 && return false + + for h2 in GI.gethole(g2) + if e1_in_e2 # h2 could be outside of e1, but inside of e2 + h2_in_e1, h2_on_e1, _ = _line_filled_curve_interactions( + h2, ext1; + closed_line = true, ) - return false + # h2 is inside of e1 and cannot be excluded by a hole since it touches the boundary + h2_on_e1 && h2_in_e1 && return false + if !h2_in_e1 # is h2 disjoint from e1, or is e1 within h2? + c1_val = point_filled_curve_orientation(centroid(ext1), h2) + c1_val == point_in && return false # e1 is within h2 + break # e1 is disjoint from h2 end end - return true + # h2 is within e1, but is it within a hole of g1? + h2_in_e1 = true + for h1 in GI.gethole(g1) + _, h2_on_h1, h2_out_h1 = _line_filled_curve_interactions( + h2, h1; + closed_line = true, + ) + # h2 is outside of h1 and cannot be excluded by another hole since it touches the boundary + h2_on_h1 && h2_out_h1 && return false + if !h2_out_h1 #h2 is within bounds of h1, so not in e1 + h2_in_e1 = false + break + end + end + h2_in_e1 && return false end - return false + return true end # Geometries within multipolygons diff --git a/src/try.jl b/src/try.jl index d647f37f5..f95ec399c 100644 --- a/src/try.jl +++ b/src/try.jl @@ -2,8 +2,12 @@ import GeometryOps as GO import GeoInterface as GI import LibGEOS as LG -r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) +p2 = LG.Polygon([ + [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], + [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], + [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] +]) p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) - -GO.disjoint(r1, p3) +p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) +a = GO.within(p3, p2) \ No newline at end of file diff --git a/test/methods/disjoint.jl b/test/methods/disjoint.jl index 7fe590488..18695c625 100644 --- a/test/methods/disjoint.jl +++ b/test/methods/disjoint.jl @@ -123,21 +123,21 @@ m4 = LG.MultiPolygon([p7]) # line outside of ring -> disjoint @test GO.disjoint(l7, r2) == LG.disjoint(l7, r2) -# # Line and polygon -# Line traces entire outline of polygon edges -> not disjoint -@test GO.disjoint(l6, p1) == LG.disjoint(l6, p1) -# Line is on edge + inside of polygon -> not disjoint -@test GO.disjoint(l2, p2) == LG.disjoint(l2, p2) -# Line goes outside of polygon -> not disjoint -@test GO.disjoint(l3, p2) == LG.disjoint(l3, p2) -# Line is fully within hole -> disjoint -@test GO.disjoint(l8, p2) == LG.disjoint(l8, p2) -# Line is on polygon edge and then cuts through hole -> not disjoint -@test GO.disjoint(l11, p2) == LG.disjoint(l11, p2) +# # # Line and polygon +# # Line traces entire outline of polygon edges -> not disjoint +# @test GO.disjoint(l6, p1) == LG.disjoint(l6, p1) +# # Line is on edge + inside of polygon -> not disjoint +# @test GO.disjoint(l2, p2) == LG.disjoint(l2, p2) +# # Line goes outside of polygon -> not disjoint +# @test GO.disjoint(l3, p2) == LG.disjoint(l3, p2) +# # Line is fully within hole -> disjoint +# @test GO.disjoint(l8, p2) == LG.disjoint(l8, p2) +# # Line is on polygon edge and then cuts through hole -> not disjoint +# @test GO.disjoint(l11, p2) == LG.disjoint(l11, p2) # # Geometry and line (switched direction) @test GO.disjoint(l7, r1) == GO.disjoint(r1, l7) -@test GO.disjoint(l8, p2) == GO.disjoint(p2, l8) +# @test GO.disjoint(l8, p2) == GO.disjoint(p2, l8) # # Ring and line # Shares all endpoints -> not disjoint @@ -156,10 +156,10 @@ m4 = LG.MultiPolygon([p7]) @test GO.disjoint(r3, r4) == LG.disjoint(r3, r4) # Ring inside of ring -> disjoint @test GO.disjoint(r4, r2) == LG.disjoint(r4, r2) -# Ring outside of other ring -> disjoing +# Ring outside of other ring -> disjoint @test GO.disjoint(r2, r4) == LG.disjoint(r2, r4) -# # Ring and polygon +# Ring and polygon # Ring goes outside of polygon's external ring -> not disjoint @test GO.within(r1, p2) == LG.within(r1, p1) # Ring is one of polygon's holes -> not disjoint diff --git a/test/methods/touches.jl b/test/methods/touches.jl index 16ec90b59..2f9ace932 100644 --- a/test/methods/touches.jl +++ b/test/methods/touches.jl @@ -1,25 +1,28 @@ -p1 = LG.Point([0.0, 0.0]) -p2 = LG.Point([0.0, 0.1]) -p3 = LG.Point([1.0, 0.0]) +pt1 = LG.Point([0.0, 0.0]) +pt2 = LG.Point([0.0, 0.1]) +pt3 = LG.Point([1.0, 0.0]) l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) +l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) +l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) +l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) # Point and point -@test GO.touches(p1, p1) == LG.touches(p1, p1) -@test GO.touches(p1, p2) == LG.touches(p1, p2) +@test GO.touches(pt1, pt1) == LG.touches(pt1, pt1) +@test GO.touches(pt1, pt2) == LG.touches(pt1, pt2) # Point and line -@test GO.touches(p1, l1) == LG.touches(p1, l1) -@test GO.touches(p2, l1) == LG.touches(p2, l1) -@test GO.touches(p3, l1) == LG.touches(p3, l1) -@test GO.touches(p1, l2) == LG.touches(p1, l2) -@test GO.touches(p2, l2) == LG.touches(p2, l2) -@test GO.touches(p3, l2) == LG.touches(p3, l2) +@test GO.touches(pt1, l1) == LG.touches(pt1, l1) +@test GO.touches(pt2, l1) == LG.touches(pt2, l1) +@test GO.touches(pt3, l1) == LG.touches(pt3, l1) +@test GO.touches(pt1, l2) == LG.touches(pt1, l2) +@test GO.touches(pt2, l2) == LG.touches(pt2, l2) +@test GO.touches(pt3, l2) == LG.touches(pt3, l2) # Line and line -LG.touches(l1, l1) -LG.touches(l1, l2) -LG.touches(l1, l3) -LG.touches(l1, l4) -LG.touches(l1, l5) \ No newline at end of file +@test GO.touches(l1, l1) == LG.touches(l1, l1) +@test GO.touches(l1, l2) == LG.touches(l1, l2) +@test GO.touches(l1, l3) == LG.touches(l1, l3) +@test GO.touches(l1, l4) == LG.touches(l1, l4) +@test GO.touches(l1, l5) == LG.touches(l1, l5) \ No newline at end of file diff --git a/test/methods/within.jl b/test/methods/within.jl index 84c50cee2..0a89e8a06 100644 --- a/test/methods/within.jl +++ b/test/methods/within.jl @@ -115,8 +115,6 @@ m2 = LG.MultiPolygon([p3, p4]) # Inside of polygon (with holes) -> within @test GO.within(pt7, p2) == LG.within(pt7, p2) -# # Geometry and point - # # Line and line # Equal lines -> within @test GO.within(l1, l1) == LG.within(l1, l1) diff --git a/test/runtests.jl b/test/runtests.jl index ffc257d07..c9c34572f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -24,6 +24,7 @@ const GO = GeometryOps # @testset "Geom on geom" begin include("methods/geom_on_geom.jl") end @testset "Intersect" begin include("methods/intersects.jl") end @testset "Signed Area" begin include("methods/signed_area.jl") end + @testset "Touches" begin include("methods/touches.jl") end @testset "Overlaps" begin include("methods/overlaps.jl") end @testset "Within" begin include("methods/within.jl") end From c8af05a88cf7a8643cc64f9f0a9a4409fa50c692 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 19 Dec 2023 17:31:54 -0800 Subject: [PATCH 22/33] Started poly-poly processor generalization --- src/methods/geom_geom_processors.jl | 126 +++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 3 deletions(-) diff --git a/src/methods/geom_geom_processors.jl b/src/methods/geom_geom_processors.jl index ad56b6060..0a45b00d4 100644 --- a/src/methods/geom_geom_processors.jl +++ b/src/methods/geom_geom_processors.jl @@ -175,7 +175,6 @@ function _point_polygon_process( ) # Check interaction of geom with polygon's exterior boundary ext_val = point_filled_curve_orientation(point, GI.getexterior(polygon)) - # If a point is outside, it isn't interacting with any holes ext_val == point_out && return out_allow # if a point is on an external boundary, it isn't interacting with any holes @@ -319,6 +318,7 @@ end function _line_filled_curve_interactions( line, curve; closed_line = false, + filled_line = false, ) in_curve = false on_curve = false @@ -332,6 +332,7 @@ function _line_filled_curve_interactions( nl -= first_last_equal_line ? 1 : 0 nc -= first_last_equal_curve ? 1 : 0 closed_line |= first_last_equal_line + filled_line &= closed_line # See if first point is in an acceptable orientation l_start = GI.getpoint(line, closed_line ? nl : 1) @@ -348,10 +349,9 @@ function _line_filled_curve_interactions( for i in (closed_line ? 1 : 2):nl l_end = GI.getpoint(line, i) c_start = GI.getpoint(curve, nc) - # If already interacted with all regions of curve, can stop in_curve && on_curve && out_curve && break - + # Check next segment of line against curve for j in 1:nc c_end = GI.getpoint(curve, j) # Check if two line and curve segments meet @@ -416,6 +416,18 @@ function _line_filled_curve_interactions( end l_start = l_end end + if filled_line && !in_curve + if !out_curve # line overlaps entire curve boundary + in_curve = true # line interior overlaps boundary filled interior + else + cent = centroid(line) + if within(cent, curve) + in_curve = true + on_curve = true + end + end + end + return in_curve, on_curve, out_curve end @@ -424,6 +436,7 @@ function _line_polygon_process( in_allow, on_allow, out_allow, in_require, on_require, out_require, closed_line = false, + filled_line = false, ) in_req_met = !in_require on_req_met = !on_require @@ -432,6 +445,7 @@ function _line_polygon_process( in_curve, on_curve, out_curve = _line_filled_curve_interactions( line, GI.getexterior(polygon); closed_line = closed_line, + filled_line = filled_line, ) if on_curve !on_allow && return false @@ -469,6 +483,112 @@ function _line_polygon_process( return in_req_met && on_req_met && out_req_met end +function _polygon_polygon_process( + poly1, poly2; + in_allow, on_allow, out_allow, + in_require, on_require, out_require, +) + in_req_met = !in_require + on_req_met = !on_require + out_req_met = !out_require + + ext1 = GI.getexterior(poly1) + e1_in_p2, e1_on_p2, e1_out_p2 = _line_polygon_process( + ext1, poly2; + in_allow = in_allow, in_require = in_require, + on_allow = on_allow, on_require = on_require, + out_allow = out_allow, out_require = out_require, + closed_line = true, + filled_line = true, + ) + if e1_on_p2 + !on_allow && return false + on_req_met = true + end + if e1_out_p2 + !out_allow && return false + out_req_met = true + end + !e1_in_p2 && return in_req_met && on_req_met && out_req_met + + # is the part if p1 that is in p2 actually in p2? + + # does p1 touch any other edges or exit p2 at any point? + + # for h2 in GI.gethole(g2) + # # check if h2 is inside of e1 + # e1_in_h2, e1_on_h2, e1_out_h2 = _line_filled_curve_interactions( + # ext1, h2; + # closed_line = true, + # filled_line = true, + # ) + # # skip if poly1 doesn't interact with the hole at all + # !e1_in_h2 && !e1_on_h2 && break + # # if hole interacts with an edge of poly1 + # if e1_on_h2 + # !on_allow && return false + # on_req_met = true + # #= + # we know that h2 touches edge of p1 so: + # (1) no hole of p1 can touch the edge of p1 and + # (2) no other hole of p2 can line up with current h2 + # This means there is at least a small border of p1 that is either + # inside of p2 (e1_out_h2) or outside of p2 (e1_in_h2) + # =# + # if e1_out_h2 + # !in_allow && return false + # in_req_met = true + # end + # if e1_in_h2 + # !out_allow && return false + # out_req_met = true + # # entirety of poly1 is within/on h2 + # !e1_out_h2 && return in_req_met && on_req_met && out_req_met + # end + # else # if hole is completly within poly1 + # !in_allow && return false + # in_req_met = true + # # Check to see if h2 is within a hole of poly1 + # for h1 in GI.gethole(poly1) + # h2_in_h1, h2_on_h1, h2_out_h1 = _line_filled_curve_interactions( + # h2, h1; + # closed_line = true, + # filled_line = true, + # ) + # if !h2_out_h1 + # !out_allow && return false + # out_req_met = true + # else + + # end + # # h2 is outside of h1 and cannot be excluded by another hole since it touches the boundary + # h2_on_h1 && h2_out_h1 && return false + # if !h2_out_h1 #h2 is within bounds of h1, so not in e1 + # h2_in_e1 = false + # break + # end + # end + + # end + + # for h1 in GI.gethole(g1) + # _, h2_on_h1, h2_out_h1 = _line_filled_curve_interactions( + # h2, h1; + # closed_line = true, + # ) + # # h2 is outside of h1 and cannot be excluded by another hole since it touches the boundary + # h2_on_h1 && h2_out_h1 && return false + # if !h2_out_h1 #h2 is within bounds of h1, so not in e1 + # h2_in_e1 = false + # break + # end + # end + # h2_in_e1 && return false + # end + # return true +end + + function _point_in_extent(p, extent::Extents.Extent) (x1, x2), (y1, y2) = extent.X, extent.Y return x1 ≤ GI.x(p) ≤ x2 && y1 ≤ GI.y(p) ≤ y2 From d491545975e5ad4157829df196b721ce06d50a26 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Thu, 21 Dec 2023 12:40:44 -0800 Subject: [PATCH 23/33] Add tests and reorganize --- src/GeometryOps.jl | 22 +- src/methods/crosses.jl | 92 ------ src/methods/{ => geom_relations}/bools.jl | 0 src/methods/{ => geom_relations}/contains.jl | 17 +- src/methods/geom_relations/coveredby.jl | 266 +++++++++++++++ src/methods/geom_relations/covers.jl | 53 +++ src/methods/geom_relations/crosses.jl | 219 +++++++++++++ src/methods/{ => geom_relations}/disjoint.jl | 0 src/methods/{ => geom_relations}/equals.jl | 0 .../geom_geom_processors.jl | 0 .../{ => geom_relations}/geom_in_out_geom.jl | 0 .../{ => geom_relations}/intersects.jl | 101 ++++-- .../{covers.jl => geom_relations/old_code.jl} | 0 src/methods/{ => geom_relations}/overlaps.jl | 63 ++-- src/methods/{ => geom_relations}/touches.jl | 11 + src/methods/{ => geom_relations}/within.jl | 0 test/methods/bools.jl | 259 ++++++++------- test/methods/contains.jl | 31 -- test/methods/covers.jl | 25 -- test/methods/disjoint.jl | 208 ------------ test/methods/equals.jl | 131 -------- test/methods/geom_relations/contains.jl | 130 ++++++++ test/methods/geom_relations/coveredby.jl | 61 ++++ test/methods/geom_relations/covers.jl | 92 ++++++ test/methods/geom_relations/crosses.jl | 39 +++ test/methods/geom_relations/disjoint.jl | 266 +++++++++++++++ test/methods/geom_relations/equals.jl | 160 +++++++++ test/methods/geom_relations/intersects.jl | 201 ++++++++++++ test/methods/geom_relations/overlaps.jl | 151 +++++++++ test/methods/geom_relations/touches.jl | 61 ++++ test/methods/geom_relations/within.jl | 306 ++++++++++++++++++ test/methods/intersects.jl | 145 --------- test/methods/overlaps.jl | 110 ------- test/methods/touches.jl | 28 -- test/methods/within.jl | 245 -------------- test/runtests.jl | 11 +- 36 files changed, 2308 insertions(+), 1196 deletions(-) delete mode 100644 src/methods/crosses.jl rename src/methods/{ => geom_relations}/bools.jl (100%) rename src/methods/{ => geom_relations}/contains.jl (72%) create mode 100644 src/methods/geom_relations/coveredby.jl create mode 100644 src/methods/geom_relations/covers.jl create mode 100644 src/methods/geom_relations/crosses.jl rename src/methods/{ => geom_relations}/disjoint.jl (100%) rename src/methods/{ => geom_relations}/equals.jl (100%) rename src/methods/{ => geom_relations}/geom_geom_processors.jl (100%) rename src/methods/{ => geom_relations}/geom_in_out_geom.jl (100%) rename src/methods/{ => geom_relations}/intersects.jl (80%) rename src/methods/{covers.jl => geom_relations/old_code.jl} (100%) rename src/methods/{ => geom_relations}/overlaps.jl (81%) rename src/methods/{ => geom_relations}/touches.jl (89%) rename src/methods/{ => geom_relations}/within.jl (100%) delete mode 100644 test/methods/contains.jl delete mode 100644 test/methods/covers.jl delete mode 100644 test/methods/disjoint.jl delete mode 100644 test/methods/equals.jl create mode 100644 test/methods/geom_relations/contains.jl create mode 100644 test/methods/geom_relations/coveredby.jl create mode 100644 test/methods/geom_relations/covers.jl create mode 100644 test/methods/geom_relations/crosses.jl create mode 100644 test/methods/geom_relations/disjoint.jl create mode 100644 test/methods/geom_relations/equals.jl create mode 100644 test/methods/geom_relations/intersects.jl create mode 100644 test/methods/geom_relations/overlaps.jl create mode 100644 test/methods/geom_relations/touches.jl create mode 100644 test/methods/geom_relations/within.jl delete mode 100644 test/methods/intersects.jl delete mode 100644 test/methods/overlaps.jl delete mode 100644 test/methods/touches.jl delete mode 100644 test/methods/within.jl diff --git a/src/GeometryOps.jl b/src/GeometryOps.jl index 17b665e58..821ee9f9d 100644 --- a/src/GeometryOps.jl +++ b/src/GeometryOps.jl @@ -19,22 +19,24 @@ const Edge = Tuple{TuplePoint,TuplePoint} include("primitives.jl") include("utils.jl") -include("methods/bools.jl") +include("methods/geom_relations/bools.jl") include("methods/signed_distance.jl") include("methods/signed_area.jl") include("methods/centroid.jl") -include("methods/intersects.jl") -include("methods/contains.jl") -include("methods/crosses.jl") -include("methods/disjoint.jl") -include("methods/overlaps.jl") -include("methods/within.jl") +include("methods/geom_relations/intersects.jl") +include("methods/geom_relations/contains.jl") +include("methods/geom_relations/covers.jl") +include("methods/geom_relations/coveredby.jl") +include("methods/geom_relations/crosses.jl") +include("methods/geom_relations/disjoint.jl") +include("methods/geom_relations/overlaps.jl") +include("methods/geom_relations/within.jl") include("methods/polygonize.jl") include("methods/barycentric.jl") -include("methods/equals.jl") -include("methods/geom_geom_processors.jl") +include("methods/geom_relations/equals.jl") +include("methods/geom_relations/geom_geom_processors.jl") include("methods/orientation.jl") -include("methods/touches.jl") +include("methods/geom_relations/touches.jl") include("transformations/flip.jl") include("transformations/simplify.jl") include("transformations/reproject.jl") diff --git a/src/methods/crosses.jl b/src/methods/crosses.jl deleted file mode 100644 index 6ae7e9f39..000000000 --- a/src/methods/crosses.jl +++ /dev/null @@ -1,92 +0,0 @@ -# # Crossing checks - -""" - crosses(geom1, geom2)::Bool - -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. - -TODO: broken - -## Examples -```julia -import GeoInterface as GI, GeometryOps as GO - -line1 = GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)]) -line2 = GI.LineString([(-2, 2), (4, 2)]) - -GO.crosses(line1, line2) -# output -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 - -# return int_point && ext_point -# end - -function line_crosses_line(line1, line2) - np2 = GI.npoint(line2) - if 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) - te(p, (pa, pb); exclude_boundary) && return true - end - end - end - return false -end - -function line_crosses_poly(line, poly) - for l in flatten(AbstractCurveTrait, poly) - intersects(line, l) && return true - end - return false -end - -function multipoint_crosses_poly(mp, poly) - int_point = false - ext_point = false - - for p in GI.getpoint(mp) - if point_in_polygon(p, poly) - int_point = true - else - ext_point = true - end - int_point && ext_point && return true - end - return false -end diff --git a/src/methods/bools.jl b/src/methods/geom_relations/bools.jl similarity index 100% rename from src/methods/bools.jl rename to src/methods/geom_relations/bools.jl diff --git a/src/methods/contains.jl b/src/methods/geom_relations/contains.jl similarity index 72% rename from src/methods/contains.jl rename to src/methods/geom_relations/contains.jl index ef15d17c0..e23317008 100644 --- a/src/methods/contains.jl +++ b/src/methods/geom_relations/contains.jl @@ -5,11 +5,13 @@ export contains #= ## What is contains? -The contains function checks if completly contains another geometry, or in other -words, that the second geometry is completly within the first. +The contains function checks if a given geometry completly contains another +geometry, or in other words, that the second geometry is completly within the +first. This requires that the two interiors intersect and that the interior and +boundary of the second geometry is not in the exterior of the first geometry. To provide an example, consider these two lines: -```@example cshape +```@example contains using GeometryOps using GeometryOps.GeometryBasics using Makie @@ -23,9 +25,10 @@ lines!(GI.getpoint(l2), color = :orange) scatter!(GI.getpoint(l2), color = :orange) ``` We can see that all of the points and edges of l2 are within l1, so l1 contains -l2. -```@example cshape +l2. However, l2 does not contain l1. +```@example contains contains(l1, l2) # returns true +contains(l2, l1) # returns false ``` ## Implementation @@ -40,8 +43,8 @@ inputs variables, swapped in order, to within. contains(g1::AbstractGeometry, g2::AbstractGeometry)::Bool Return true if the second geometry is completely contained by the first -geometry. The interiors of both geometries must intersect and, the interior and -boundary of the secondary (g2) must not intersect the exterior of the primary +geometry. The interiors of both geometries must intersect and the interior and +boundary of the secondary (g2) must not intersect the exterior of the first (g1). `contains` returns the exact opposite result of `within`. diff --git a/src/methods/geom_relations/coveredby.jl b/src/methods/geom_relations/coveredby.jl new file mode 100644 index 000000000..5e29b5d74 --- /dev/null +++ b/src/methods/geom_relations/coveredby.jl @@ -0,0 +1,266 @@ +# # CoveredBy + +export coveredby + +#= +## What is coveredby? + +The coveredby function checks if one geometry is covered by another geometry. +This is an extension of within that does not require the interiors of the two +geometries to intersect, but still does require that the interior and boundary +of the first geometry isn't outside of the second geometry. + +To provide an example, consider this point and line: +```@example coveredby +using GeometryOps +using GeometryOps.GeometryBasics +using Makie +using CairoMakie + +p1 = Point(0.0, 0.0) +p2 = Point(1.0, 1.0) +l1 = Line(p1, p2) + +f, a, p = lines([p1, p2]) +scatter!(p1, color = :red) +``` +As we can see, `p1` is on the endpoint of l1. This means it is not `within`, but +it does meet the definition of `coveredby`. +```@example coveredby +coveredby(p1, l1) # 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 +`coveredby` 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 not allowed to be in the exterior of g2 + - no points of g1 are required to be in the interior of g2 + - no points of g1 are required to be on the boundary of g2 + - no points of g1 are required to be in the exterior of g2 + +The code for the specific implementations is in the geom_geom_processors file. +=# + +""" + coveredby(g1, g2)::Bool + +Return `true` if the first geometry is completely covered by the second +geometry. The interior and boundary of the primary geometry (g1) must not +intersect the exterior of the secondary geometry (g2). + +Furthermore, `coveredby` returns the exact opposite result of `covers`. They are +equivalent with the order of the arguments swapped. + +## Examples +```jldoctest setup=:(using GeometryOps, GeometryBasics) +import GeometryOps as GO, GeoInterface as GI +p1 = GI.Point(0.0, 0.0) +p2 = GI.Point(1.0, 1.0) +l1 = GI.Line([p1, p2]) + +GO.coveredby(p1, l1) +# output +true +``` +""" +coveredby(g1, g2) = coveredby(trait(g1), g1, trait(g2), g2) +coveredby(::GI.FeatureTrait, g1, ::Any, g2) = coveredby(GI.geometry(g1), g2) +coveredby(::Any, g1, t2::GI.FeatureTrait, g2) = coveredby(g1, GI.geometry(g2)) + + +# Points coveredby geometries +""" + coveredby(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool + +If a point is coveredby another point, then those points must be equal. If they +are not equal, then they are not coveredby and return false. +""" +coveredby( + ::GI.PointTrait, g1, + ::GI.PointTrait, g2, +) = equals(g1, g2) + + +""" + coveredby( + ::GI.PointTrait, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, + )::Bool + +A point is coveredby a line or linestring if it is on a vertex or an edge of +that linestring. Return true if those conditions are met, else false. +""" +coveredby( + ::GI.PointTrait, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, +) = _point_curve_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, + repeated_last_coord = false, +) + +""" + coveredby(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +A point is coveredby a linear ring if it is on a vertex or an edge of that +linear ring. Return true if those conditions are met, else false. +""" +coveredby( + ::GI.PointTrait, g1, + ::GI.LinearRingTrait, g2, +) = _point_curve_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, + repeated_last_coord = true, +) + +""" + coveredby(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A point is coveredby a polygon if it is inside of that polygon, including edges +and vertices. Return true if those conditions are met, else false. +""" +coveredby( + ::GI.PointTrait, g1, + ::GI.PolygonTrait, g2, +) = _point_polygon_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, +) + +""" +coveredby(::GI.AbstractTrait, g1, ::GI.PointTrait, g2)::Bool + +Points cannot cover any geometry other than points. Return false if not +dispatched to more specific function. +""" +coveredby( + ::GI.AbstractTrait, g1, + ::GI.PointTrait, g2, +) = false + +# Lines coveredby geometries +""" + coveredby( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, + )::Bool + +A line or linestring is coveredby another line or linestring if all of the +interior and boundary points of the first line are on the interior and +boundary points of the second line. +""" +coveredby( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, +) = _line_curve_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, + in_require = false, on_require = false, out_require = false, + closed_line = false, + closed_curve = false, +) + +""" + coveredby( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::GI.LinearRingTrait, g2, + )::Bool + +A line or linestring is coveredby a linear ring if all of the interior and +boundary points of the line are on the edges of the ring. +""" +coveredby( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, + in_require = false, on_require = false, out_require = false, + closed_line = false, + closed_curve = true, +) + +""" + coveredby(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A line or linestring is coveredby a polygon if all of the interior and boundary +points of the line are in the polygon interior or on its edges. This includes +edges of holes. Return true if those conditions are met, else false. +""" +coveredby( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::GI.PolygonTrait, g2, +) = _line_polygon_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, + in_require = false, on_require = false, out_require = false, + closed_line = false, +) + +# Rings covered by geometries +""" + coveredby( + ::GI.LinearRingTrait, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, + )::Bool + +A linear ring is covered by a linestring if all the vertices and edges of the +linear ring are on the edges/vertices of the linear ring. Return true if +those conditions are met, else false. +""" +coveredby( + ::GI.LinearRingTrait, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, +) = _line_curve_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, + in_require = false, on_require = false, out_require = false, + closed_line = true, + closed_curve = false, +) + +""" + coveredby(::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +A linear ring is covered by another linear ring if the vertices and edges of the +first linear ring are on the edges/vertices of the second linear ring. Return +true if those conditions are met, else false. +""" +coveredby( + ::GI.LinearRingTrait, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, + in_require = false, on_require = false, out_require = false, + closed_line = true, + closed_curve = true, +) + +""" + coveredby(::GI.LinearRingTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A linear ring is coveredby a polygon if the vertices and edges of the linear +ring are either in the polygon interior or on the polygon edges. This includes +edges of holes. Return true if those conditions are met, else false. +""" +coveredby( + ::GI.LinearRingTrait, g1, + ::GI.PolygonTrait, g2, +) = _line_polygon_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, + in_require = true, on_require = false, out_require = false, + closed_line = true, +) + diff --git a/src/methods/geom_relations/covers.jl b/src/methods/geom_relations/covers.jl new file mode 100644 index 000000000..398c3817f --- /dev/null +++ b/src/methods/geom_relations/covers.jl @@ -0,0 +1,53 @@ +# # Covers + +export covers + +#= +## What is covers? + +The covers function checks if a given geometry completly covers another +geometry. For this to be true, the "contained" geometry's interior and +boundaries must be covered by the "covering" geometry's interior and boundaries. +The interiors do not need to overlap. + +To provide an example, consider these two lines: +```@example cshape +using GeometryOps +using GeometryOps.GeometryBasics +using Makie +using CairoMakie + + +``` + +```@example cshape +covers(l1, l2) # returns true +``` + +## Implementation + +This is the GeoInterface-compatible implementation. + +Given that covers is the exact opposite of coveredby, we simply pass the two +inputs variables, swapped in order, to coveredby. +=# + +""" + covers(g1::AbstractGeometry, g2::AbstractGeometry)::Bool + +Return true if the first geometry is completely covers the second geometry, +The exterior and boundary of the second geometry must not be outside of the +interior and boundary of the first geometry. However, the interiors need not +intersect. + +`covers` returns the exact opposite result of `coveredby`. + +## Examples + +```jldoctest +import GeometryOps as GO, GeoInterface as GI +line = GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)]) + +``` +""" +covers(g1, g2)::Bool = coveredby(g2, g1) diff --git a/src/methods/geom_relations/crosses.jl b/src/methods/geom_relations/crosses.jl new file mode 100644 index 000000000..8af4bc34d --- /dev/null +++ b/src/methods/geom_relations/crosses.jl @@ -0,0 +1,219 @@ +# # Crosses + +export crosses + +#= +## What is crosses? + +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 +one of the geometries has a smaller dimensionality than the other geometry. +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. + +Note that points can't cross any geometries, despite different dimension, due to +their inability to be both crosses and exterior to any other shape. + +To provide an example, consider these two lines: +```@example cshape +using GeometryOps +using GeometryOps.GeometryBasics +using Makie +using CairoMakie + + +``` + +```@example cshape + +``` + +## Implementation + +This is the GeoInterface-compatible implementation. + +First, we implement a wrapper method that dispatches to the correct +implementation based on the geometry trait. + +... + +The code for the specific implementations is in the geom_geom_processors file. + +=# + +""" + crosses(geom1, geom2)::Bool + +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. + +## Examples +```jldoctest setup=:(using GeometryOps, GeometryBasics) +import GeometryOps as GO, GeoInterface as GI + + + +# output + +``` +""" +crosses(g1, g2) = crosses(trait(g1), g1, trait(g2), g2) +crosses(::GI.FeatureTrait, g1, ::Any, g2) = crosses(GI.geometry(g1), g2) +crosses(::Any, g1, t2::GI.FeatureTrait, g2) = crosses(g1, GI.geometry(g2)) + +""" + +""" +crosses(::GI.AbstractTrait, g1, ::GI.AbstractTrait, g2) = false + +# Lines crosses geometries +""" + crosses(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool + +A line string is crosses another linestring if the vertices and edges of the +first linestring are crosses the second linestring, including the first and last +vertex. Return true if those conditions are met, else false. +""" +crosses( + ::GI.LineStringTrait, g1, + ::GI.LineStringTrait, g2, +) = _line_curve_process( + g1, g2; + in_allow = true, on_allow = false, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = false, +) + +""" + crosses(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +A line string is crosses a linear ring if the vertices and edges of the +linestring are crosses the linear ring. Return true if those conditions are met, +else false. +""" +crosses( + ::GI.LineStringTrait, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_process( + g1, g2; + in_allow = false, on_allow = true, out_allow = true, + in_require = false, on_require = true, out_require = true, + closed_line = false, + closed_curve = true, +) + +""" + crosses(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool + +A line string is crosses a polygon if the vertices and edges of the +linestring are crosses the polygon. Points of the linestring can be on the +polygon edges, but at least one point must be in the polygon interior. The +linestring also cannot cross through a hole. Return true if those conditions are +met, else false. +""" +crosses( + ::GI.LineStringTrait, g1, + ::GI.PolygonTrait, g2, +) = _line_polygon_process( + g1, g2; + in_allow = false, on_allow = true, out_allow = true, + in_require = false, on_require = true, out_require = true, + closed_line = false, +) + + + +""" + crosses(geom1, geom2)::Bool + +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. + +TODO: broken + +## Examples +```julia +import GeoInterface as GI, GeometryOps as GO + +line1 = GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)]) +line2 = GI.LineString([(-2, 2), (4, 2)]) + +GO.crosses(line1, line2) +# output +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 + +# # return int_point && ext_point +# # end + +# function line_crosses_line(line1, line2) +# np2 = GI.npoint(line2) +# if 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) +# te(p, (pa, pb); exclude_boundary) && return true +# end +# end +# end +# return false +# end + +# function line_crosses_poly(line, poly) +# for l in flatten(AbstractCurveTrait, poly) +# intersects(line, l) && return true +# end +# return false +# end + +# function multipoint_crosses_poly(mp, poly) +# int_point = false +# ext_point = false + +# for p in GI.getpoint(mp) +# if point_in_polygon(p, poly) +# int_point = true +# else +# ext_point = true +# end +# int_point && ext_point && return true +# end +# return false +# end diff --git a/src/methods/disjoint.jl b/src/methods/geom_relations/disjoint.jl similarity index 100% rename from src/methods/disjoint.jl rename to src/methods/geom_relations/disjoint.jl diff --git a/src/methods/equals.jl b/src/methods/geom_relations/equals.jl similarity index 100% rename from src/methods/equals.jl rename to src/methods/geom_relations/equals.jl diff --git a/src/methods/geom_geom_processors.jl b/src/methods/geom_relations/geom_geom_processors.jl similarity index 100% rename from src/methods/geom_geom_processors.jl rename to src/methods/geom_relations/geom_geom_processors.jl diff --git a/src/methods/geom_in_out_geom.jl b/src/methods/geom_relations/geom_in_out_geom.jl similarity index 100% rename from src/methods/geom_in_out_geom.jl rename to src/methods/geom_relations/geom_in_out_geom.jl diff --git a/src/methods/intersects.jl b/src/methods/geom_relations/intersects.jl similarity index 80% rename from src/methods/intersects.jl rename to src/methods/geom_relations/intersects.jl index 2efaf1b78..e9b74eb53 100644 --- a/src/methods/intersects.jl +++ b/src/methods/geom_relations/intersects.jl @@ -70,26 +70,83 @@ GO.intersects(line1, line2) true ``` """ -intersects(geom1, geom2) = intersects( - GI.trait(geom1), - geom1, - GI.trait(geom2), - geom2 -) +# intersects(geom1, geom2) = intersects( +# GI.trait(geom1), +# geom1, +# GI.trait(geom2), +# geom2 +# ) +intersects(geom1, geom2) = !disjoint(geom1, geom2) + + +# Points intersects geometries +""" + intersects(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool + +If a point is intersects another point, then those points must be equal. If they are +not equal, then they are not intersects and return false. +""" +# intersects( +# ::GI.PointTrait, g1, +# ::GI.PointTrait, g2, +# ) = equals(g1, g2) + + +""" + intersects(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool + +A point is intersects a line string if it is on a vertex or an edge of that +linestring, excluding the start and end vertex if the linestring is not closed. +Return true if those conditions are met, else false. +""" +# intersects( +# ::GI.PointTrait, g1, +# ::GI.LineStringTrait, g2, +# ) = _point_curve_process( +# g1, g2; +# in_allow = true, on_allow = true, out_allow = false, +# repeated_last_coord = false, +# ) + +""" + intersects(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool + +A point is intersects a linear ring if it is on a vertex or an edge of that +linear ring. Return true if those conditions are met, else false. +""" +# intersects( +# ::GI.PointTrait, g1, +# ::GI.LinearRingTrait, g2, +# ) = _point_curve_process( +# g1, g2; +# in_allow = true, on_allow = true, out_allow = false, +# repeated_last_coord = true, +# ) + +""" + intersects(trait1::GI.AbstractTrait, g1, trait2::GI.PointTrait, g2)::Bool + +To check if a geometry intersects with a point, switch the order of the +arguments to take advantage of point-geometry intersects methods. +""" +# intersects( +# trait1::GI.AbstractTrait, g1, +# trait2::GI.PointTrait, g2, +# ) = intersects(trait2, g2, trait1, g1) """ intersects(::GI.LineTrait, a, ::GI.LineTrait, b)::Bool Returns true if two line segments intersect and false otherwise. """ -function intersects(::GI.LineTrait, a, ::GI.LineTrait, b) - a1 = _tuple_point(GI.getpoint(a, 1)) - a2 = _tuple_point(GI.getpoint(a, 2)) - b1 = _tuple_point(GI.getpoint(b, 1)) - b2 = _tuple_point(GI.getpoint(b, 2)) - meet_type = ExactPredicates.meet(a1, a2, b1, b2) - return meet_type == 0 || meet_type == 1 -end +# function intersects(::GI.LineTrait, a, ::GI.LineTrait, b) +# a1 = _tuple_point(GI.getpoint(a, 1)) +# a2 = _tuple_point(GI.getpoint(a, 2)) +# b1 = _tuple_point(GI.getpoint(b, 1)) +# b2 = _tuple_point(GI.getpoint(b, 2)) +# meet_type = ExactPredicates.meet(a1, a2, b1, b2) +# return meet_type == 0 || meet_type == 1 +# end """ intersects(::GI.AbstractTrait, a, ::GI.AbstractTrait, b)::Bool @@ -98,14 +155,14 @@ Returns true if two geometries intersect with one another and false otherwise. For all geometries but lines, convert the geometry to a list of edges and cross compare the edges for intersections. """ -function intersects( - trait_a::GI.AbstractTrait, a_geom, - trait_b::GI.AbstractTrait, b_geom, -) edges_a, edges_b = map(sort! ∘ to_edges, (a_geom, b_geom)) - return _line_intersects(edges_a, edges_b) || - within(trait_a, a_geom, trait_b, b_geom) || - within(trait_b, b_geom, trait_a, a_geom) -end +# function intersects( +# trait_a::GI.AbstractTrait, a_geom, +# trait_b::GI.AbstractTrait, b_geom, +# ) edges_a, edges_b = map(sort! ∘ to_edges, (a_geom, b_geom)) +# return _line_intersects(edges_a, edges_b) || +# within(trait_a, a_geom, trait_b, b_geom) || +# within(trait_b, b_geom, trait_a, a_geom) +# end """ _line_intersects( diff --git a/src/methods/covers.jl b/src/methods/geom_relations/old_code.jl similarity index 100% rename from src/methods/covers.jl rename to src/methods/geom_relations/old_code.jl diff --git a/src/methods/overlaps.jl b/src/methods/geom_relations/overlaps.jl similarity index 81% rename from src/methods/overlaps.jl rename to src/methods/geom_relations/overlaps.jl index 82f4f94c2..66e5643ee 100644 --- a/src/methods/overlaps.jl +++ b/src/methods/geom_relations/overlaps.jl @@ -118,8 +118,8 @@ end 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(::GI.LineTrait, line1, ::GI.LineTrait, line) = +# _overlaps((a1, a2), (b1, b2)) """ overlaps( @@ -130,18 +130,39 @@ overlaps(::GI.LineTrait, line1, ::GI.LineTrait, line) = 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, +# 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( + ::GI.LineStringTrait, g1, + ::GI.LineStringTrait, g2, +) = _line_curve_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = false, +) + +overlaps( + ::GI.LineStringTrait, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = true, ) - 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( @@ -152,14 +173,14 @@ end 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 +# 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( diff --git a/src/methods/touches.jl b/src/methods/geom_relations/touches.jl similarity index 89% rename from src/methods/touches.jl rename to src/methods/geom_relations/touches.jl index ed2672e6e..248641e7b 100644 --- a/src/methods/touches.jl +++ b/src/methods/geom_relations/touches.jl @@ -57,6 +57,17 @@ touches( in_allow = false, on_allow = true, out_allow = false, ) +""" + touches(trait1::GI.AbstractTrait, g1, trait2::GI.PointTrait, g2)::Bool + +To check if a geometry is touches by a point, switch the order of the +arguments to take advantage of point-geometry touches methods. +""" +touches( + trait1::GI.AbstractTrait, g1, + trait2::GI.PointTrait, g2, +) = touches(trait2, g2, trait1, g1) + # Lines touching geometries """ touches(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool diff --git a/src/methods/within.jl b/src/methods/geom_relations/within.jl similarity index 100% rename from src/methods/within.jl rename to src/methods/geom_relations/within.jl diff --git a/test/methods/bools.jl b/test/methods/bools.jl index ab2afd0aa..b0d5522fd 100644 --- a/test/methods/bools.jl +++ b/test/methods/bools.jl @@ -1,112 +1,147 @@ -using Test, GeometryOps -import GeoInterface as GI -import GeometryOps as GO - -@testset "booleans" begin - line1 = GI.LineString([[9.170356, 45.477985], [9.164434, 45.482551], [9.166644, 45.484003]]) - line2 = GI.LineString([[9.169356, 45.477985], [9.163434, 45.482551], [9.165644, 45.484003]]) - line3 = GI.LineString([ - (-111.544189453125, 24.186847428521244), - (-110.687255859375, 24.966140159912975), - (-110.4510498046875, 24.467150664739002), - (-109.9951171875, 25.180087808990645) - ]) - line4 = GI.LineString([ - (-111.4617919921875, 24.05148034322011), - (-110.8795166015625, 24.681961205014595), - (-110.841064453125, 24.14174098050432), - (-109.97863769531249, 24.617057340809524) - ]) - - # @test isparallel(line1, line2) == true - # @test isparallel(line3, line4) == false - - poly1 = GI.Polygon([[[0, 0], [1, 0], [1, 1], [0.5, 0.5], [0, 1], [0, 0]]]) - poly2 = GI.Polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]) - - @test isconcave(poly1) == true - @test isconcave(poly2) == false - - l1 = GI.LineString([[0, 0], [1, 1], [1, 0], [0, 0]]) - l2 = GI.LineString([[0, 0], [1, 0], [1, 1], [0, 0]]) - - @test isclockwise(l1) == true - @test isclockwise(l2) == false - - l3 = GI.LineString([[0, 0], [3, 3], [4, 4]]) - p1 = GI.Point([1,1]) - - l4 = GI.LineString([[0, 0], [3, 3]]) - p2 = GI.Point([0, 0]) - - p3 = GI.Point([20, 20]) - l5 = GI.LineString([[0, 0], [3, 3], [38.32, 5.96]]) - - pt = (-77, 44) - poly = GI.Polygon([[[-81, 41], [-81, 47], [-72, 47], [-72, 41], [-81, 41]]]) - - - poly3 = GI.Polygon([[(1, 1), (1, 10), (10, 10), (10, 1), (1, 1)]]) - poly4 = GI.Polygon([[(1, 1), (2, 2), (3, 2), (1, 1)]]) - line5 = GI.LineString([(1.0, 1.0), (2.0, 3.0), (2.0, 3.5)]) - - line6 = GI.LineString([(1.0, 1.0), (1.0, 2.0), (1.0, 3.0), (1.0, 4.0)]) - poly5 = GI.Polygon([[(1.0, 1.0), (1.0, 20.0), (1.0, 3.0), (1.0, 4.0), (1.0, 1.0)]]) - line7 = GI.LineString([(1.0, 2.0), (1.0, 3.0), (1.0, 3.5)]) - - # @test GO.contains(poly3, poly4) == true - # @test GO.contains(poly3, line5) == true - # @test GO.contains(line6, (1, 2)) == true - # @test GO.contains(poly3, poly5) == false - # @test GO.contains(poly3 , line7) == false - - # @test GO.within(poly4, poly3) == true - # @test GO.within(line5, poly3) == true - # @test GO.within(poly5, poly3) == false - # @test GO.within((1, 2), line6) == true - # @test GO.within(line7, poly3) == false - - poly6 = GI.Polygon([[(-11, -12), (-13, -12), (-13, -13), (-11, -13), (-11, -12)]]) - poly7 = GI.Polygon([[(-1, 2), (3, 2), (3, 3), (-1, 3), (-1, 2)]]) - poly8 = GI.Polygon([[(-1, 2), (-13, -12), (-13, -13), (-11, -13), (-1, 2)]]) - - # @test GO.disjoint(poly7, poly6) == true - # @test GO.disjoint(poly7, (1, 1)) == true - # @test GO.disjoint(poly7, GI.LineString([(0, 0), (12, 2), (12, 3), (12, 4)])) == true - # @test GO.disjoint(poly8, poly7) == false - - line8 = GI.LineString([(124.584961, -12.768946), (126.738281, -17.224758)]) - line9 = GI.LineString([(123.354492, -15.961329), (127.22168, -14.008696)]) - - @test all(GO.intersection(line8, line9)[1] .≈ (125.583754, -14.835723)) - - line10 = GI.LineString([ - (142.03125, -11.695273), - (138.691406, -16.804541), - (136.40625, -14.604847), - (135.966797, -12.039321), - (131.308594, -11.436955), - (128.232422, -15.36895), - (125.947266, -13.581921), - (121.816406, -18.729502), - (117.421875, -20.632784), - (113.378906, -23.402765), - (114.169922, -26.667096), - ]) - line11 = GI.LineString([ - (117.861328, -15.029686), - (122.124023, -24.886436), - (132.583008, -22.309426), - (132.890625, -7.754537), - ]) - - points = GO.intersection(line10, line11) - @test all(points[1] .≈ (119.832884, -19.58857)) - @test all(points[2] .≈ (132.808697, -11.6309378)) - - # @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, 2), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == true - # @test GO.crosses(GI.MultiPoint([(1, 0), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == false - # @test GO.crosses(GI.LineString([(-2, 2), (-4, 2)]), poly7) == false -end + +pt1 = LG.Point([0.0, 0.0]) +pt2 = LG.Point([5.0, 5.0]) +pt3 = LG.Point([1.0, 0.0]) +pt4 = LG.Point([0.5, 0.0]) +pt5 = LG.Point([0.5, 0.25]) +pt6 = LG.Point([0.6, 0.4]) +pt7 = LG.Point([0.4, 0.8]) + +l1 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]) +l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0]]) +l3 = LG.LineString([[-1.0, 0.0], [0.0, 0.0], [1.0, 0.0]]) +l4 = LG.LineString([[0.5, 0.0], [1.0, 0.0], [1.0, 0.5]]) +l5 = LG.LineString([[0.0, 0.0], [-1.0, -1.0]]) +l6 = LG.LineString([[2.0, 2.0], [0.0, 1.0]]) +l7 = LG.LineString([[0.5, 1.0], [0.5, -1.0]]) +l8 = LG.LineString([[0.0, 0.0], [0.5, 0.0], [0.5, 0.5], [1.0, -0.5]]) +l9 = LG.LineString([[0.0, 1.0], [0.0, -1.0], [1.0, 1.0]]) +l10 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) +l11 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0], [-1.0, 0.0]]) + + +r1 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) +r2 = LG.LinearRing([[0.5, 0.2], [0.6, 0.4], [0.7, 0.2], [0.5, 0.2]]) +r3 = LG.LinearRing([[0.2, 0.7], [0.4, 0.9], [0.5, 0.6], [0.2, 0.7]]) + +p1 = LG.Polygon(r1, [r2, r3]) + +@testset "Contains" begin include("geom_relations/contains.jl") end +@testset "Covered By" begin include("geom_relations/coveredby.jl") end +@testset "Covers" begin include("geom_relations/covers.jl") end +@testset "Crosses" begin include("geom_relations/crosses.jl") end +@testset "Disjoint" begin include("geom_relations/disjoint.jl") end +@testset "Equals" begin include("geom_relations/equals.jl") end +@testset "Touches" begin include("geom_relations/touches.jl") end +@testset "Overlaps" begin include("geom_relations/overlaps.jl") end +@testset "Within" begin include("geom_relations/within.jl") end +@testset "Intersect" begin include("geom_relations/intersects.jl") end + +# @testset "booleans" begin +# line1 = GI.LineString([[9.170356, 45.477985], [9.164434, 45.482551], [9.166644, 45.484003]]) +# line2 = GI.LineString([[9.169356, 45.477985], [9.163434, 45.482551], [9.165644, 45.484003]]) +# line3 = GI.LineString([ +# (-111.544189453125, 24.186847428521244), +# (-110.687255859375, 24.966140159912975), +# (-110.4510498046875, 24.467150664739002), +# (-109.9951171875, 25.180087808990645) +# ]) +# line4 = GI.LineString([ +# (-111.4617919921875, 24.05148034322011), +# (-110.8795166015625, 24.681961205014595), +# (-110.841064453125, 24.14174098050432), +# (-109.97863769531249, 24.617057340809524) +# ]) + +# # @test isparallel(line1, line2) == true +# # @test isparallel(line3, line4) == false + +# poly1 = GI.Polygon([[[0, 0], [1, 0], [1, 1], [0.5, 0.5], [0, 1], [0, 0]]]) +# poly2 = GI.Polygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]) + +# @test isconcave(poly1) == true +# @test isconcave(poly2) == false + +# l1 = GI.LineString([[0, 0], [1, 1], [1, 0], [0, 0]]) +# l2 = GI.LineString([[0, 0], [1, 0], [1, 1], [0, 0]]) + +# @test isclockwise(l1) == true +# @test isclockwise(l2) == false + +# l3 = GI.LineString([[0, 0], [3, 3], [4, 4]]) +# p1 = GI.Point([1,1]) + +# l4 = GI.LineString([[0, 0], [3, 3]]) +# p2 = GI.Point([0, 0]) + +# p3 = GI.Point([20, 20]) +# l5 = GI.LineString([[0, 0], [3, 3], [38.32, 5.96]]) + +# pt = (-77, 44) +# poly = GI.Polygon([[[-81, 41], [-81, 47], [-72, 47], [-72, 41], [-81, 41]]]) + + +# poly3 = GI.Polygon([[(1, 1), (1, 10), (10, 10), (10, 1), (1, 1)]]) +# poly4 = GI.Polygon([[(1, 1), (2, 2), (3, 2), (1, 1)]]) +# line5 = GI.LineString([(1.0, 1.0), (2.0, 3.0), (2.0, 3.5)]) + +# line6 = GI.LineString([(1.0, 1.0), (1.0, 2.0), (1.0, 3.0), (1.0, 4.0)]) +# poly5 = GI.Polygon([[(1.0, 1.0), (1.0, 20.0), (1.0, 3.0), (1.0, 4.0), (1.0, 1.0)]]) +# line7 = GI.LineString([(1.0, 2.0), (1.0, 3.0), (1.0, 3.5)]) + +# # @test GO.contains(poly3, poly4) == true +# # @test GO.contains(poly3, line5) == true +# # @test GO.contains(line6, (1, 2)) == true +# # @test GO.contains(poly3, poly5) == false +# # @test GO.contains(poly3 , line7) == false + +# # @test GO.within(poly4, poly3) == true +# # @test GO.within(line5, poly3) == true +# # @test GO.within(poly5, poly3) == false +# # @test GO.within((1, 2), line6) == true +# # @test GO.within(line7, poly3) == false + +# poly6 = GI.Polygon([[(-11, -12), (-13, -12), (-13, -13), (-11, -13), (-11, -12)]]) +# poly7 = GI.Polygon([[(-1, 2), (3, 2), (3, 3), (-1, 3), (-1, 2)]]) +# poly8 = GI.Polygon([[(-1, 2), (-13, -12), (-13, -13), (-11, -13), (-1, 2)]]) + +# # @test GO.disjoint(poly7, poly6) == true +# # @test GO.disjoint(poly7, (1, 1)) == true +# # @test GO.disjoint(poly7, GI.LineString([(0, 0), (12, 2), (12, 3), (12, 4)])) == true +# # @test GO.disjoint(poly8, poly7) == false + +# line8 = GI.LineString([(124.584961, -12.768946), (126.738281, -17.224758)]) +# line9 = GI.LineString([(123.354492, -15.961329), (127.22168, -14.008696)]) + +# @test all(GO.intersection(line8, line9)[1] .≈ (125.583754, -14.835723)) + +# line10 = GI.LineString([ +# (142.03125, -11.695273), +# (138.691406, -16.804541), +# (136.40625, -14.604847), +# (135.966797, -12.039321), +# (131.308594, -11.436955), +# (128.232422, -15.36895), +# (125.947266, -13.581921), +# (121.816406, -18.729502), +# (117.421875, -20.632784), +# (113.378906, -23.402765), +# (114.169922, -26.667096), +# ]) +# line11 = GI.LineString([ +# (117.861328, -15.029686), +# (122.124023, -24.886436), +# (132.583008, -22.309426), +# (132.890625, -7.754537), +# ]) + +# points = GO.intersection(line10, line11) +# @test all(points[1] .≈ (119.832884, -19.58857)) +# @test all(points[2] .≈ (132.808697, -11.6309378)) + +# # @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, 2), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == true +# # @test GO.crosses(GI.MultiPoint([(1, 0), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == false +# # @test GO.crosses(GI.LineString([(-2, 2), (-4, 2)]), poly7) == false +# end diff --git a/test/methods/contains.jl b/test/methods/contains.jl deleted file mode 100644 index bcc669079..000000000 --- a/test/methods/contains.jl +++ /dev/null @@ -1,31 +0,0 @@ -p1 = LG.Point([0.0, 0.0]) -p2 = LG.Point([0.0, 0.1]) -p3 = LG.Point([1.0, 0.0]) - -l1 = LG.LineString([[0.0, 1.0]]) -l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) - -# Point and point -@test GO.contains(p1, p1) == LG.contains(p1, p1) -@test GO.contains(p1, p2) == LG.contains(p1, p2) - -# Point and line -@test GO.contains(l1, p1) == LG.contains(l1, p1) -@test GO.contains(l1, p2) == LG.contains(l1, p2) -@test GO.contains(l1, p3) == LG.contains(l1, p3) -@test GO.contains(l2, p1) == LG.contains(l2, p1) -@test GO.contains(l2, p2) == LG.contains(l2, p2) -@test GO.contains(l2, p3) == LG.contains(l2, p3) - -# Line and line -@test GO.contains(l1, l1) == LG.contains(l1, l1) -@test GO.contains(l1, l2) == LG.contains(l1, l2) -@test GO.contains(l1, l3) == LG.contains(l1, l3) -@test GO.contains(l1, l4) == LG.contains(l1, l4) -@test GO.contains(l1, l5) == LG.contains(l1, l5) - -@test GO.contais(l1, l1) == LG.contains(l1, l1) -@test GO.contais(l2, l1) == LG.contains(l2, l1) -@test GO.contais(l3, l1) == LG.contains(l3, l1) -@test GO.contais(l4, l1) == LG.contains(l4, l1) -@test GO.contais(l5, l1) == LG.contains(l5, l1) \ No newline at end of file diff --git a/test/methods/covers.jl b/test/methods/covers.jl deleted file mode 100644 index 57f1aa92a..000000000 --- a/test/methods/covers.jl +++ /dev/null @@ -1,25 +0,0 @@ -p1 = LG.Point([0.0, 0.0]) -p2 = LG.Point([0.0, 0.1]) -p3 = LG.Point([1.0, 0.0]) - -l1 = LG.LineString([[0.0, 1.0]]) -l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) - -# Point and point -@test GO.covers(p1, p1) == LG.covers(p1, p1) -@test GO.covers(p1, p2) == LG.covers(p1, p2) - -# Point and line -@test GO.covers(l1, p1) == LG.covers(l1, p1) -@test GO.covers(l1, p2) == LG.covers(l1, p2) -@test GO.covers(l1, p3) == LG.covers(l1, p3) -@test GO.covers(l2, p1) == LG.covers(l2, p1) -@test GO.covers(l2, p2) == LG.covers(l2, p2) -@test GO.covers(l2, p3) == LG.covers(l2, p3) - -# Line and line -@test GO.covers(l1, l1) == LG.covers(l1, l1) -@test GO.covers(l2, l1) == LG.covers(l2, l1) -@test GO.covers(l3, l1) == LG.covers(l3, l1) -@test GO.covers(l4, l1) == LG.covers(l4, l1) -@test GO.covers(l5, l1) == LG.covers(l5, l1) \ No newline at end of file diff --git a/test/methods/disjoint.jl b/test/methods/disjoint.jl deleted file mode 100644 index 18695c625..000000000 --- a/test/methods/disjoint.jl +++ /dev/null @@ -1,208 +0,0 @@ -# Test points -pt1 = LG.Point([0.0, 0.0]) -pt2 = LG.Point([0.0, 0.1]) -pt3 = LG.Point([1.0, 0.0]) -pt4 = LG.Point([0.5, 1.0]) -pt5 = LG.Point([0.2, 0.5]) -pt6 = LG.Point([0.3, 0.55]) -pt7 = LG.Point([0.6, 0.49]) -pt8 = LG.Point([0.25, 0.75]) -pt9 = LG.Point([-1.0, 0.0]) -# Test lines -l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) -l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) -l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) -l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) -l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) -l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) -l7 = LG.LineString([[0.6, 0.6], [0.8, 0.6]]) -l8 = LG.LineString([[0.3, 0.55], [0.3, 0.65]]) -l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) -l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) -l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) -l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) -l13 = LG.LineString([[0.5, 0.01], [0.5, 0.09]]) -# Test rings -r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) -r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) -r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) -r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) -r5 = LG.LinearRing([[5.0, 5.0], [6.0, 6.0], [7.0, 5.0], [5.0, 5.0]]) -r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) -# Test polygons -p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) -p2 = LG.Polygon([ - [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], - [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], - [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] -]) -p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) -p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) -p5 = LG.Polygon([[[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]]) -p6 = LG.Polygon([[[0.1, 0.4], [0.1, 0.8], [0.3, 0.8], [0.3, 0.4], [0.1, 0.4]]]) -p7 = LG.Polygon([[[-2.0, 0.0], [-1.0, 0.0], [-1.5, 1.5], [-2.0, 0.0]]]) -p8 = LG.Polygon([ - [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] -]) -# Test multipolygons -m1 = LG.MultiPolygon([p3, p6]) -m2 = LG.MultiPolygon([p3, p4]) -m3 = LG.MultiPolygon([p2, p7]) -m4 = LG.MultiPolygon([p7]) - -# # Point and point -# Equal points -> not disjoint -@test GO.disjoint(pt1, pt1) == LG.disjoint(pt1, pt1) -# Non-equal points -> disjoint -@test GO.disjoint(pt1, pt2) == LG.disjoint(pt1, pt2) - -# # Point and line -# Line endpoint (1 segment) -> not disjoint -@test GO.disjoint(pt1, l1) == LG.disjoint(pt1, l1) -# Middle of line (1 segment) -> not disjoint -@test GO.disjoint(pt2, l1) == LG.disjoint(pt2, l1) -# Not on line (1 segment) -> disjoint -@test GO.disjoint(pt3, l1) == LG.disjoint(pt3, l1) -# Line endpoint (2 segments) -> not disjoing -@test GO.disjoint(pt2, l2) == LG.disjoint(pt2, l2) -# Middle of line on joint (2 segments) -> not disjoint -@test GO.disjoint(pt3, l2) == LG.disjoint(pt3, l2) -# Endpoint on closed line -> not disjoint -@test GO.disjoint(pt1, l6) == LG.disjoint(pt1, l6) - -# # Point and ring -# On ring corner -> not disjoint -@test GO.disjoint(pt1, r1) == LG.disjoint(pt1, r1) -# Outside of ring -> disjoint -@test GO.disjoint(pt2, r1) == LG.disjoint(pt2, r1) -# Inside of ring center (not on line) -> disjoint -@test GO.disjoint(pt3, r1) == LG.disjoint(pt3, r1) -# On ring edge -> not disjoint -@test GO.disjoint(pt8, r1) == LG.disjoint(pt8, r1) - -# # Point and polygon -# Point on polygon vertex -> not disjoint -@test GO.disjoint(pt1, p2) == LG.disjoint(pt1, p2) -# Point on polygon edge -> not disjoint -@test GO.disjoint(pt2, p2) == LG.disjoint(pt2, p2) -# Point on edge of hold --> not disjoint -@test GO.disjoint(pt5, p2) == LG.disjoint(pt5, p2) -# Point in hole -> disjoint -@test GO.disjoint(pt6, p2) == LG.disjoint(pt6, p2) -# Point inside of polygon -> not disjoint -@test GO.disjoint(pt7, p2) == LG.disjoint(pt7, p2) -# Point outside of polygon -> disjoint -@test GO.disjoint(pt9, p2) == LG.disjoint(pt9, p2) - -# # Geometry and point (switched direction) -@test GO.disjoint(pt1, l1) == GO.disjoint(l1, pt1) -@test GO.disjoint(pt1, r1) == GO.disjoint(r1, pt1) -@test GO.disjoint(pt1, p2) == GO.disjoint(p2, pt1) - -# # Line and line -# Equal lines -> not disjoint -@test GO.disjoint(l1, l1) == LG.disjoint(l1, l1) -# Lines share 2 endpoints, but don't overlap -> not disjoint -@test GO.disjoint(l1, l2) == LG.disjoint(l1, l2) -# Lines overlap, but neither is within other -> not disjoint -@test GO.disjoint(l1, l3) == LG.disjoint(l1, l3) -# Within line (no shared endpoints) -> not disjoint -@test GO.disjoint(l1, l4) == LG.disjoint(l1, l4) -# Line shares just 1 endpoint -> not disjoint -@test GO.disjoint(l1, l5) == LG.disjoint(l1, l5) -# Lines don't touch at all -> disjoint -@test GO.disjoint(l7, l1) == LG.disjoint(l7, l1) - -# # Line and ring -# Shares all endpoints -> not disjoint -@test GO.disjoint(l6, r1) == LG.disjoint(l6, r1) -# Shares only some edges -> not disjoint -@test GO.disjoint(l2, r3) == LG.disjoint(l2, r3) -# line inside of ring -> disjoint -@test GO.disjoint(l7, r1) == LG.disjoint(l7, r1) -# line outside of ring -> disjoint -@test GO.disjoint(l7, r2) == LG.disjoint(l7, r2) - -# # # Line and polygon -# # Line traces entire outline of polygon edges -> not disjoint -# @test GO.disjoint(l6, p1) == LG.disjoint(l6, p1) -# # Line is on edge + inside of polygon -> not disjoint -# @test GO.disjoint(l2, p2) == LG.disjoint(l2, p2) -# # Line goes outside of polygon -> not disjoint -# @test GO.disjoint(l3, p2) == LG.disjoint(l3, p2) -# # Line is fully within hole -> disjoint -# @test GO.disjoint(l8, p2) == LG.disjoint(l8, p2) -# # Line is on polygon edge and then cuts through hole -> not disjoint -# @test GO.disjoint(l11, p2) == LG.disjoint(l11, p2) - -# # Geometry and line (switched direction) -@test GO.disjoint(l7, r1) == GO.disjoint(r1, l7) -# @test GO.disjoint(l8, p2) == GO.disjoint(p2, l8) - -# # Ring and line -# Shares all endpoints -> not disjoint -@test GO.disjoint(r1, l6) == LG.disjoint(r1, l6) -# Shares some edges -> not disjoint -@test GO.disjoint(r3, l2) == LG.disjoint(r3, l2) -# Doesn't share any edges -> disjoint -@test GO.disjoint(r4, l2) == LG.disjoint(r4, l2) - -# # Ring and ring -# Equal ring -> not disjoint -@test GO.disjoint(r1, r1) == LG.disjoint(r1, r1) -# Not equal ring but share a vertex -> not disjoint -@test GO.disjoint(r1, r2) == LG.disjoint(r1, r2) -# Rings not touching -> not disjoint -@test GO.disjoint(r3, r4) == LG.disjoint(r3, r4) -# Ring inside of ring -> disjoint -@test GO.disjoint(r4, r2) == LG.disjoint(r4, r2) -# Ring outside of other ring -> disjoint -@test GO.disjoint(r2, r4) == LG.disjoint(r2, r4) - -# Ring and polygon -# Ring goes outside of polygon's external ring -> not disjoint -@test GO.within(r1, p2) == LG.within(r1, p1) -# Ring is one of polygon's holes -> not disjoint -@test GO.disjoint(r4, p2) == LG.disjoint(r4, p2) -# Ring is fully within polygon -> not disjoint -@test GO.disjoint(r5, p2) == LG.disjoint(r5, p2) -# Ring is fully within polygon's hole -> disjoint -@test GO.disjoint(r6, p2) == LG.disjoint(r6, p2) -# Ring is fully outside of the polygon -> disjoint -@test GO.disjoint(r5, p2) == LG.disjoint(r5, p2) - -# # Geometry and ring (switched direction) -@test GO.disjoint(r4, r2) == GO.disjoint(r2, r4) -@test GO.disjoint(p2, r6) == GO.disjoint(r6, p2) - -# # Polygon and polygon -# Overlapping polygons -> not disjoint -@test GO.disjoint(p1, p2) == LG.disjoint(p1, p2) -# Polygon is within polygon, but also on edges -> not disjoint -@test GO.disjoint(p3, p2) == LG.disjoint(p3, p2) -# Polygon within polygon hole --> disjoint -@test GO.disjoint(p5, p2) == LG.disjoint(p5, p2) -# polygon extactly overlaps with other polygon's hole -> not disjoint -@test GO.disjoint(p8, p7) == LG.disjoint(p8, p7) - -# # Multipolygon tests -# Point in multipolygon -> not disjoint -@test GO.disjoint(pt5, m1) == LG.disjoint(pt5, m1) -# Point outside of multipolygon -> disjoint -@test GO.disjoint(pt4, m1) == LG.disjoint(pt4, m1) -# Line in multipolygon -> not disjoint -@test GO.disjoint(l13, m1) == LG.disjoint(l13, m1) -# Line outside of multipolygon -> disjoint -@test GO.disjoint(l8, m3) == LG.disjoint(l8, m3) -# Ring in multipolygon -> not disjoint -@test GO.disjoint(r1, m2) == LG.disjoint(r1, m2) -# Ring outside of multipolygon -@test GO.disjoint(r6, m3) == LG.disjoint(r6, m3) -# Polygon in multipolygon -> not disjoint -@test GO.disjoint(p3, m1) == LG.disjoint(p3, m1) -# Polygon outside of multipolygon -> disjoint -@test GO.disjoint(p5, m3) == LG.disjoint(p5, m3) -# Multipolygon in multipolygon -> not disjoint -@test GO.disjoint(m1, m1) == LG.disjoint(m1, m1) -# Multipolygon outside of multipolygon -> disjoint -@test GO.disjoint(m1, m4) == LG.disjoint(m1, m4) \ No newline at end of file diff --git a/test/methods/equals.jl b/test/methods/equals.jl deleted file mode 100644 index ea1eb73fb..000000000 --- a/test/methods/equals.jl +++ /dev/null @@ -1,131 +0,0 @@ -@testset "Points/MultiPoints" begin - p1 = LG.Point([0.0, 0.0]) - p2 = LG.Point([0.0, 1.0]) - # Same points - @test GO.equals(p1, p1) == LG.equals(p1, p1) - @test GO.equals(p2, p2) == LG.equals(p2, p2) - # Different points - @test GO.equals(p1, p2) == LG.equals(p1, p2) - - mp1 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0]]) - mp2 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0], [3.0, 3.0]]) - mp3 = LG.MultiPoint([p2]) - # Same points - @test GO.equals(mp1, mp1) == LG.equals(mp1, mp1) - @test GO.equals(mp2, mp2) == LG.equals(mp2, mp2) - # Different points - @test GO.equals(mp1, mp2) == LG.equals(mp1, mp2) - @test GO.equals(mp1, p1) == LG.equals(mp1, p1) - # Point and multipoint - @test GO.equals(p2, mp3) == LG.equals(p2, mp3) -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]]) - # Equal lines - @test GO.equals(l1, l1) == LG.equals(l1, l1) - @test GO.equals(l2, l2) == LG.equals(l2, l2) - # Different lines - @test GO.equals(l1, l2) == GO.equals(l2, l1) == LG.equals(l1, l2) - - r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) - r2 = LG.LinearRing([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) - r3 = GI.LinearRing([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0]]) - l3 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) - # Equal rings - @test GO.equals(r1, r1) == LG.equals(r1, r1) - @test GO.equals(r2, r2) == LG.equals(r2, r2) - # Test equal rings without closing point - @test GO.equals(r2, r3) - @test GO.equals(r3, l3) - # Different rings - @test GO.equals(r1, r2) == GO.equals(r2, r1) == LG.equals(r1, r2) - # Equal linear ring and line string - @test GO.equals(r2, l3) == LG.equals(r2, l3) - # Equal line string and line - @test GO.equals(l1, GI.Line([(0.0, 0.0), (0.0, 10.0)])) -end - -@testset "Polygons/MultiPolygons" begin - pt1 = LG.Point([0.0, 0.0]) - r1 = GI.LinearRing([(0, 0), (0, 5), (5, 5), (5, 0), (0, 0)]) - p1 = GI.Polygon([[(0, 0), (0, 5), (5, 5), (5, 0), (0, 0)]]) - p2 = GI.Polygon([[(1, 1), (1, 6), (6, 6), (6, 1), (1, 1)]]) - p3 = 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]] - ] - ) - p4 = LG.Polygon( - [ - [[10.0, 0.0], [10.0, 20.0], [30.0, 20.0], [30.0, 0.0], [10.0, 0.0]], - [[16.0, 1.0], [16.0, 11.0], [25.0, 11.0], [25.0, 1.0], [16.0, 1.0]] - ] - ) - p5 = 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]], - [[11.0, 1.0], [11.0, 2.0], [12.0, 2.0], [12.0, 1.0], [11.0, 1.0]] - ] - ) - p6 = GI.Polygon([[(6, 6), (6, 1), (1, 1), (1, 6), (6, 6)]]) - p7 = GI.Polygon([[(6, 6), (1, 6), (1, 1), (6, 1), (6, 6)]]) - p8 = GI.Polygon([[(6, 6), (1, 6), (1, 1), (6, 1)]]) - # Point and polygon aren't equal - GO.equals(pt1, p1) == LG.equals(pt1, p1) - # Linear ring and polygon aren't equal - @test GO.equals(r1, p1) == LG.equals(r1, p1) - # Equal polygon - @test GO.equals(p1, p1) == LG.equals(p1, p1) - @test GO.equals(p2, p2) == LG.equals(p2, p2) - # Equal but offset polygons - @test GO.equals(p2, p6) == LG.equals(p2, p6) - # Equal but opposite winding orders - @test GO.equals(p2, p7) == LG.equals(p2, p7) - # Equal but without closing point (implied) - @test GO.equals(p7, p8) - # Different polygons - @test GO.equals(p1, p2) == LG.equals(p1, p2) - # Equal polygons with holes - @test GO.equals(p3, p3) == LG.equals(p3, p3) - # Same exterior, different hole - @test GO.equals(p3, p4) == LG.equals(p3, p4) - # Same exterior and first hole, has an extra hole - @test GO.equals(p3, p5) == LG.equals(p3, p5) - - p9 = LG.Polygon( - [[ - [-53.57208251953125, 28.287451910503744], - [-53.33038330078125, 28.29228897739706], - [-53.34136962890625, 28.430052892335723], - [-53.57208251953125, 28.287451910503744], - ]] - ) - # Complex polygon - @test GO.equals(p9, p9) == LG.equals(p9, p9) - - 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]] - ] - ]) - m2 = LG.MultiPolygon([ - [ - [[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]] - ], - [[[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0], [0.0, 0.0]]] - ]) - # Equal multipolygon - @test GO.equals(m1, m1) == LG.equals(m1, m1) - # Equal multipolygon with different order - @test GO.equals(m2, m2) == LG.equals(m2, m2) - # Equal polygon to multipolygon - m3 = LG.MultiPolygon([p3]) - @test GO.equals(p1, m3) == LG.equals(p1, m3) -end \ No newline at end of file diff --git a/test/methods/geom_relations/contains.jl b/test/methods/geom_relations/contains.jl new file mode 100644 index 000000000..763b5eede --- /dev/null +++ b/test/methods/geom_relations/contains.jl @@ -0,0 +1,130 @@ +# # Point and Geometry + +# Same point -> contains +@test GO.contains(pt1, pt1) == LG.contains(pt1, pt1) == true +# Different point -> doesn't contain +@test GO.contains(pt1, pt2) == LG.contains(pt1, pt2) == false +# Point on line endpoint -> does not contain +@test GO.contains(l1, pt1) == LG.contains(l1, pt1) == false +# Point outside line -> does not contain +@test GO.contains(l1, pt2) == LG.contains(l1, pt2) == false +# Point on line segment -> contains +@test GO.contains(l1, pt3) == LG.contains(l1, pt3) == true +# Point on line vertex between segments -> contain +@test GO.contains(l1, pt4) == LG.contains(l1, pt4) == true +# Point cannot contain a line -> doesn't contain +@test GO.contains(pt3, l1) == LG.contains(pt3, l1) == false +# Point on ring endpoint -> contains +@test GO.contains(r1, pt1) == LG.contains(r1, pt1) == true +# Point outside ring -> does not contain +@test GO.contains(r1, pt2) == LG.contains(r1, pt2) == false +# Point on ring segment -> contains +@test GO.contains(r1, pt3) == LG.contains(r1, pt3) == true +# Point on ring vertex between segments -> contain +@test GO.contains(r1, pt4) == LG.contains(r1, pt4) == true +# Point cannot contain a ring -> doesn't contain +@test GO.contains(pt3, r1) == LG.contains(pt3, r1) == false +# Point on vertex of polygon --> doesn't contain +@test GO.contains(p1, pt1) == LG.contains(p1, pt1) == false +# Point outside of polygon's external ring -> doesn't contain +@test GO.contains(p1, pt2) == LG.contains(p1, pt2) == false +# Point on polygon's edge -> doesn't contain +@test GO.contains(p1, pt4) == LG.contains(p1, pt4) == false +# Point inside of polygon -> contains +@test GO.contains(p1, pt5) == LG.contains(p1, pt5) == true +# Point on hole edge -> doesn't contain +@test GO.contains(p1, pt6) == LG.contains(p1, pt6) == false +# Point inside of polygon hole -> doesn't contain +@test GO.contains(p1, pt7) == LG.contains(p1, pt7) == false +# Point cannot contain a polygon -> doesn't contain +@test GO.contains(pt5, p1) == LG.contains(pt5, p1) == false + +# # Line and Geometry + +# Same line -> contains +@test GO.contains(l1, l1) == LG.contains(l1, l1) == true +# Line overlaps line edge and endpoint -> contains +@test GO.contains(l1, l2) == LG.contains(l1, l2) == true +# Line overlaps with one edge and is outside of other edge -> doesn't contain +@test GO.contains(l1, l3) == LG.contains(l1, l3) == false +# Line segments both within other line segments -> contain +@test GO.contains(l1, l4) == LG.contains(l1, l4) == true +# Line segments connect at endpoint -> doesn't contain +@test GO.contains(l1, l5) == LG.contains(l1, l5) == false +# Line segments don't touch -> doesn't contain +@test GO.contains(l1, l6) == LG.contains(l1, l6) == false +# Line segments cross -> doesn't contain +@test GO.contains(l1, l7) == LG.contains(l1, l7) == false +# Line segments cross and go over and out -> doesn't contain +@test GO.contains(l1, l8) == LG.contains(l1, l8) == false +# Line segments cross and overlap on endpoint -> doesn't contain +@test GO.contains(l1, l9) == LG.contains(l1, l9) == false +# Line is within linear ring -> doesn't contain +@test GO.contains(l1, r1) == LG.contains(l1, r1) == false +# Line covers one edge of linera ring and has segment outside -> doesn't contain +@test GO.contains(l3, r1) == LG.contains(l3, r1) == false +# Line and linear ring are only connected at vertex -> doesn't contain +@test GO.contains(l5, r1) == LG.contains(l5, r1) == false +# Line and linear ring are disjoint -> doesn't contain +@test GO.contains(l6, r1) == LG.contains(l6, r1) == false +# Line crosses through two ring edges -> doesn't contain +@test GO.contains(l7, r1) == LG.contains(l7, r1) == false +# Line crosses through two ring edges and touches third edge -> doesn't contain +@test GO.contains(l8, r1) == LG.contains(l8, r1) == false +# Line is equal to linear ring -> contain +@test GO.contains(l10, r1) == LG.contains(l10, r1) == true +# Line covers linear ring and then has extra segment -> contain +@test GO.contains(l11, r1) == LG.contains(l11, r1) == true + +# # Ring and Geometry + +# Line is within linear ring -> contains +@test GO.contains(r1, l1) == LG.contains(r1, l1) == true +# Line covers one edge of linera ring and has segment outside -> doesn't contain +@test GO.contains(r1, l3) == LG.contains(r1, l3) == false +# Line and linear ring are only connected at vertex -> doesn't contain +@test GO.contains(r1, l5) == LG.contains(r1, l5) == false +# Line and linear ring are disjoint -> doesn't contain +@test GO.contains(r1, l6) == LG.contains(r1, l6) == false +# Line crosses through two ring edges -> doesn't contain +@test GO.contains(r1, l7) == LG.contains(r1, l7) == false +# Line crosses through two ring edges and touches third edge -> doesn't contain +@test GO.contains(r1, l8) == LG.contains(r1, l8) == false +# Line is equal to linear ring -> contain +@test GO.contains(r1, l10) == LG.contains(r1, l10) == true +# Line covers linear ring and then has extra segment -> doesn't contain +@test GO.contains(r1, l11) == LG.contains(r1, l11) == false + + + +# p1 = LG.Point([0.0, 0.0]) +# p2 = LG.Point([0.0, 0.1]) +# p3 = LG.Point([1.0, 0.0]) + +# l1 = LG.LineString([[0.0, 1.0]]) +# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) + +# # Point and point +# @test GO.contains(p1, p1) == LG.contains(p1, p1) +# @test GO.contains(p1, p2) == LG.contains(p1, p2) + +# # Point and line +# @test GO.contains(l1, p1) == LG.contains(l1, p1) +# @test GO.contains(l1, p2) == LG.contains(l1, p2) +# @test GO.contains(l1, p3) == LG.contains(l1, p3) +# @test GO.contains(l2, p1) == LG.contains(l2, p1) +# @test GO.contains(l2, p2) == LG.contains(l2, p2) +# @test GO.contains(l2, p3) == LG.contains(l2, p3) + +# # Line and line +# @test GO.contains(l1, l1) == LG.contains(l1, l1) +# @test GO.contains(l1, l2) == LG.contains(l1, l2) +# @test GO.contains(l1, l3) == LG.contains(l1, l3) +# @test GO.contains(l1, l4) == LG.contains(l1, l4) +# @test GO.contains(l1, l5) == LG.contains(l1, l5) + +# @test GO.contais(l1, l1) == LG.contains(l1, l1) +# @test GO.contais(l2, l1) == LG.contains(l2, l1) +# @test GO.contais(l3, l1) == LG.contains(l3, l1) +# @test GO.contais(l4, l1) == LG.contains(l4, l1) +# @test GO.contais(l5, l1) == LG.contains(l5, l1) \ No newline at end of file diff --git a/test/methods/geom_relations/coveredby.jl b/test/methods/geom_relations/coveredby.jl new file mode 100644 index 000000000..590dd70d3 --- /dev/null +++ b/test/methods/geom_relations/coveredby.jl @@ -0,0 +1,61 @@ +# # Point and Geometry + +# Same point -> covered by +@test GO.coveredby(pt1, pt1) == LG.coveredby(pt1, pt1) == true +# Different point -> not covered by +@test GO.coveredby(pt1, pt2) == LG.coveredby(pt1, pt2) == false +# Point on line endpoint -> covered by +@test GO.coveredby(pt1, l1) == LG.coveredby(pt1, l1) == true +# Point outside line -> not covered by +@test GO.coveredby(pt2, l1) == LG.coveredby(pt2, l1) == false +# Point on line segment -> covered by +@test GO.coveredby(pt3, l1) == LG.coveredby(pt3, l1) == true +# Point on line vertex between segments -> covered by +@test GO.coveredby(pt4, l1) == LG.coveredby(pt4, l1) == true +# line cannot be covered by a point -> not covered by +@test GO.coveredby(l1, pt3) == LG.coveredby(l1, pt3) == false +# Point on ring endpoint -> covered by +@test GO.coveredby(pt1, r1) == LG.coveredby(pt1, r1) == true +# Point outside ring -> isn't covered by +@test GO.coveredby(pt2, r1) == LG.coveredby(pt2, r1) == false +# Point on ring segment -> covered by +@test GO.coveredby(pt3, r1) == LG.coveredby(pt3, r1) == true +# Point on ring vertex between segments -> covered by +@test GO.coveredby(pt4, r1) == LG.coveredby(pt4, r1) == true +# Ring cannot be covered by a point -> isn't covered by +@test GO.coveredby(r1, pt3) == LG.coveredby(r1, pt3) == false +# Point on vertex of polygon --> covered +@test GO.coveredby(pt1, p1) == LG.coveredby(pt1, p1) == true +# Point outside of polygon's external ring -> not covered by +@test GO.coveredby(pt2, p1) == LG.coveredby(pt2, p1) == false +# Point on polygon's edge -> covered by +@test GO.coveredby(pt4, p1) == LG.coveredby(pt4, p1) == true +# Point inside of polygon -> covered by +@test GO.coveredby(pt5, p1) == LG.coveredby(pt5, p1) == true +# Point on hole edge -> covered by +@test GO.coveredby(pt6, p1) == LG.coveredby(pt6, p1) == true +# Point inside of polygon hole -> not covered by +@test GO.coveredby(pt7, p1) == LG.coveredby(pt7, p1) == false +# Polygon can't be covered by a polygon -> not covered by +@test GO.coveredby(p1, pt5) == LG.coveredby(p1, pt5) == false + +# # Line and Geometry + +# Same line -> covered by +@test GO.coveredby(l1, l1) == LG.coveredby(l1, l1) == true +# Line overlaps line edge and endpoint -> covered by +@test GO.coveredby(l2, l1) == LG.coveredby(l2, l1) == true +# Line overlaps with one edge and is outside of other edge -> isn't covered by +@test GO.coveredby(l3, l1) == LG.coveredby(l3, l1) == false +# Line segments both within other line segments -> covered by +@test GO.coveredby(l4, l1) == LG.coveredby(l4, l1) == true +# Line segments connect at endpoint -> isn't covered by +@test GO.coveredby(l5, l1) == LG.coveredby(l5, l1) == false +# Line segments don't touch -> isn't covered by +@test GO.coveredby(l6, l1) == LG.coveredby(l6, l1) == false +# Line segments cross -> isn't covered by +@test GO.coveredby(l7, l1) == LG.coveredby(l7, l1) == false +# Line segments cross and go over and out -> isn't covered by +@test GO.coveredby(l8, l1) == LG.coveredby(l8, l1) == false +# Line segments cross and overlap on endpoint -> isn't covered by +@test GO.coveredby(l9, l1) == LG.coveredby(l9, l1) == false \ No newline at end of file diff --git a/test/methods/geom_relations/covers.jl b/test/methods/geom_relations/covers.jl new file mode 100644 index 000000000..b0481b3f7 --- /dev/null +++ b/test/methods/geom_relations/covers.jl @@ -0,0 +1,92 @@ +# # Point and Geometry + +# Same point -> covers +@test GO.covers(pt1, pt1) == LG.covers(pt1, pt1) == true +# Different point -> doesn't cover +@test GO.covers(pt1, pt2) == LG.covers(pt1, pt2) == false +# Point on line endpoint -> covers +@test GO.covers(l1, pt1) == LG.covers(l1, pt1) == true +# Point outside line -> does not cover +@test GO.covers(l1, pt2) == LG.covers(l1, pt2) == false +# Point on line segment -> covers +@test GO.covers(l1, pt3) == LG.covers(l1, pt3) == true +# Point on line vertex between segments -> cover +@test GO.covers(l1, pt4) == LG.covers(l1, pt4) == true +# Point cannot cover a line -> doesn't cover +@test GO.covers(pt3, l1) == LG.covers(pt3, l1) == false +# Point on ring endpoint -> covers +@test GO.covers(r1, pt1) == LG.covers(r1, pt1) == true +# Point outside ring -> doesn't cover +@test GO.covers(r1, pt2) == LG.covers(r1, pt2) == false +# Point on ring segment -> covers +@test GO.covers(r1, pt3) == LG.covers(r1, pt3) == true +# Point on ring vertex between segments -> covers +@test GO.covers(r1, pt4) == LG.covers(r1, pt4) == true +# Point cannot cover a ring -> doesn't cover +@test GO.covers(pt3, r1) == LG.covers(pt3, r1) == false +# Point on vertex of polygon --> covers +@test GO.covers(p1, pt1) == LG.covers(p1, pt1) == true +# Point outside of polygon's external ring -> not covered +@test GO.covers(p1, pt2) == LG.covers(p1, pt2) == false +# Point on polygon's edge -> covers +@test GO.covers(p1, pt4) == LG.covers(p1, pt4) == true +# Point inside of polygon -> covers +@test GO.covers(p1, pt5) == LG.covers(p1, pt5) == true +# Point on hole edge -> covers +@test GO.covers(p1, pt6) == LG.covers(p1, pt6) == true +# Point inside of polygon hole -> not covered +@test GO.covers(p1, pt7) == LG.covers(p1, pt7) == false +# Point can't cover a polygon -> not covered +@test GO.covers(pt5, p1) == LG.covers(pt5, p1) == false + +# # Line and Geometry + +# Same line -> covers +@test GO.covers(l1, l1) == LG.covers(l1, l1) == true +# Line overlaps line edge and endpoint -> covers +@test GO.covers(l1, l2) == LG.covers(l1, l2) == true +# Line overlaps with one edge and is outside of other edge -> not covered +@test GO.covers(l1, l3) == LG.covers(l1, l3) == false +# Line segments both within other line segments -> covers +@test GO.covers(l1, l4) == LG.covers(l1, l4) == true +# Line segments connect at endpoint -> not covered +@test GO.covers(l1, l5) == LG.covers(l1, l5) == false +# Line segments don't touch -> not covered +@test GO.covers(l1, l6) == LG.covers(l1, l6) == false +# Line segments cross -> not covered +@test GO.covers(l1, l7) == LG.covers(l1, l7) == false +# Line segments cross and go over and out -> not covered +@test GO.covers(l1, l8) == LG.covers(l1, l8) == false +# Line segments cross and overlap on endpoint -> doesn't cover +@test GO.covers(l1, l9) == LG.covers(l1, l9) == false + + + + + + +# p1 = LG.Point([0.0, 0.0]) +# p2 = LG.Point([0.0, 0.1]) +# p3 = LG.Point([1.0, 0.0]) + +# l1 = LG.LineString([[0.0, 1.0]]) +# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) + +# # Point and point +# @test GO.covers(p1, p1) == LG.covers(p1, p1) +# @test GO.covers(p1, p2) == LG.covers(p1, p2) + +# # Point and line +# @test GO.covers(l1, p1) == LG.covers(l1, p1) +# @test GO.covers(l1, p2) == LG.covers(l1, p2) +# @test GO.covers(l1, p3) == LG.covers(l1, p3) +# @test GO.covers(l2, p1) == LG.covers(l2, p1) +# @test GO.covers(l2, p2) == LG.covers(l2, p2) +# @test GO.covers(l2, p3) == LG.covers(l2, p3) + +# # Line and line +# @test GO.covers(l1, l1) == LG.covers(l1, l1) +# @test GO.covers(l2, l1) == LG.covers(l2, l1) +# @test GO.covers(l3, l1) == LG.covers(l3, l1) +# @test GO.covers(l4, l1) == LG.covers(l4, l1) +# @test GO.covers(l5, l1) == LG.covers(l5, l1) \ No newline at end of file diff --git a/test/methods/geom_relations/crosses.jl b/test/methods/geom_relations/crosses.jl new file mode 100644 index 000000000..fa938ef7f --- /dev/null +++ b/test/methods/geom_relations/crosses.jl @@ -0,0 +1,39 @@ +# # Point and Geometry + +# Same point -> doesn't cross +@test GO.crosses(pt1, pt1) == LG.crosses(pt1, pt1) == false +# Different point -> doesn't cross +@test GO.crosses(pt1, pt2) == LG.crosses(pt1, pt2) == false +# Point cannot cross line -> doesn't cross +@test GO.crosses(pt3, l1) == LG.crosses(pt3, l1) == false +# Line cannot cross point -> doesn't cross +@test GO.crosses(l1, pt3) == LG.crosses(l1, pt3) == false +# Point cannot cross ring -> doesn't cross +@test GO.crosses(pt3, r1) == LG.crosses(pt3, r1) == false +# Ring cannot cross point -> doesn't cross +@test GO.crosses(r1, pt3) == LG.crosses(r1, pt3) == false +# Point cannot cross polygon -> doesn't cross +@test GO.crosses(pt3, p1) == LG.crosses(pt3, p1) == false +# Polygon cannot cross point -> doesn't cross +@test GO.crosses(p1, pt3) == LG.crosses(p1, pt3) == false + +# # Line and Geometry + +# Same line -> doesn't cross +@test GO.crosses(l1, l1) == LG.crosses(l1, l1) == false +# Line overlaps line edge and endpoint -> doesn't cross +@test GO.crosses(l1, l2) == LG.crosses(l1, l2) == false +# Line overlaps with one edge and is outside of other edge -> not covered +@test GO.crosses(l1, l3) == LG.crosses(l1, l3) == false +# Line segments both within other line segments -> doesn't cross +@test GO.crosses(l1, l4) == LG.crosses(l1, l4) == false +# Line segments connect at endpoint -> doesn't cross +@test GO.crosses(l1, l5) == LG.crosses(l1, l5) == false +# Line segments don't touch -> doesn't cross +@test GO.crosses(l1, l6) == LG.crosses(l1, l6) == false +# Line segments cross -> crosses +@test GO.crosses(l1, l7) == LG.crosses(l1, l7) == true +# Line segments cross and go over and out -> doesn't cross +@test GO.crosses(l1, l8) == LG.crosses(l1, l8) == false +# Line segments cross and overlap on endpoint -> crosses +@test GO.crosses(l1, l9) == LG.crosses(l1, l9) == true \ No newline at end of file diff --git a/test/methods/geom_relations/disjoint.jl b/test/methods/geom_relations/disjoint.jl new file mode 100644 index 000000000..386ce09e7 --- /dev/null +++ b/test/methods/geom_relations/disjoint.jl @@ -0,0 +1,266 @@ +# # Point and Geometry + +# Same point --> not disjoint +@test GO.disjoint(pt1, pt1) == LG.disjoint(pt1, pt1) == false +# Different point --> disjoint +@test GO.disjoint(pt1, pt2) == LG.disjoint(pt1, pt2) == true +# Point on line endpoint -> not disjoint +@test GO.disjoint(pt1, l1) == GO.disjoint(l1, pt1) == LG.disjoint(l1, pt1) == false +# Point outside line -> disjoint +@test GO.disjoint(pt2, l1) == GO.disjoint(l1, pt2) == LG.disjoint(l1, pt2) == true +# Point on line segment -> not disjoint +@test GO.disjoint(pt3, l1) == GO.disjoint(l1, pt3) == LG.disjoint(l1, pt3) == false +# Point on line vertex between segments -> not disjoint +@test GO.disjoint(pt4, l1) == GO.disjoint(l1, pt4) == LG.disjoint(l1, pt4) == false +# Point on ring endpoint -> not disjoint +@test GO.disjoint(pt1, r1) == GO.disjoint(r1, pt1) == LG.disjoint(r1, pt1) == false +# Point outside ring -> disjoint +@test GO.disjoint(pt2, r1) == GO.disjoint(r1, pt2) == LG.disjoint(r1, pt2) == true +# Point on ring segment -> not disjoint +@test GO.disjoint(pt3, r1) == GO.disjoint(r1, pt3) == LG.disjoint(r1, pt3) == false +# Point on ring vertex between segments -> not disjoint +@test GO.disjoint(pt4, r1) == GO.disjoint(r1, pt4) == LG.disjoint(r1, pt4) == false +# Point within hole formed by ring -> disjoint +@test GO.disjoint(pt5, r1) == GO.disjoint(r1, pt5) == LG.disjoint(r1, pt5) == true +# Point on vertex of polygon --> not disjoint +@test GO.disjoint(pt1, p1) == GO.disjoint(p1, pt1) == LG.disjoint(p1, pt1) == false +# Point outside of polygon's external ring -> disjoint +@test GO.disjoint(pt2, p1) == GO.disjoint(p1, pt2) == LG.disjoint(p1, pt2) == true +# Point on polygon's edge -> not disjoint +@test GO.disjoint(pt4, p1) == GO.disjoint(p1, pt4) == LG.disjoint(p1, pt4) == false +# Point inside of polygon -> not disjoint +@test GO.disjoint(pt5, p1) == GO.disjoint(p1, pt5) == LG.disjoint(p1, pt5) == false +# Point on hole edge -> not disjoint +@test GO.disjoint(pt6, p1) == GO.disjoint(p1, pt6) == LG.disjoint(p1, pt6) == false +# Point inside of polygon hole -> disjoint +@test GO.disjoint(pt7, p1) == GO.disjoint(p1, pt7) == LG.disjoint(p1, pt7) == true + +# # Line and Geometry + +# Same line -> not disjoint +@test GO.disjoint(l1, l1) == LG.disjoint(l1, l1) == false +# Line overlaps line edge and endpoint -> not disjoint +@test GO.disjoint(l1, l2) == LG.disjoint(l1, l2) == false +# Line overlaps with one edge and is outside of other edge -> not disjoint +@test GO.disjoint(l1, l3) == LG.disjoint(l1, l3) == false +# Line segments both within other line segments -> not disjoint +@test GO.disjoint(l1, l4) == LG.disjoint(l1, l4) == false +# Line segments connect at endpoint -> not disjoint +@test GO.disjoint(l1, l5) == LG.disjoint(l1, l5) == false +# Line segments don't touch -> disjoint +@test GO.disjoint(l1, l6) == LG.disjoint(l1, l6) == true +# Line segments cross -> not disjoint +@test GO.disjoint(l1, l7) == LG.disjoint(l1, l7) == false +# Line segments cross and go over and out -> not disjoint +@test GO.disjoint(l1, l8) == LG.disjoint(l1, l8) == false +# Line segments cross and overlap on endpoint -> not disjoint +@test GO.disjoint(l1, l9) == LG.disjoint(l1, l9) == false + +# # Test points +# pt1 = LG.Point([0.0, 0.0]) +# pt2 = LG.Point([0.0, 0.1]) +# pt3 = LG.Point([1.0, 0.0]) +# pt4 = LG.Point([0.5, 1.0]) +# pt5 = LG.Point([0.2, 0.5]) +# pt6 = LG.Point([0.3, 0.55]) +# pt7 = LG.Point([0.6, 0.49]) +# pt8 = LG.Point([0.25, 0.75]) +# pt9 = LG.Point([-1.0, 0.0]) +# # Test lines +# l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) +# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) +# l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) +# l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) +# l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) +# l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) +# l7 = LG.LineString([[0.6, 0.6], [0.8, 0.6]]) +# l8 = LG.LineString([[0.3, 0.55], [0.3, 0.65]]) +# l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) +# l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) +# l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) +# l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) +# l13 = LG.LineString([[0.5, 0.01], [0.5, 0.09]]) +# # Test rings +# r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) +# r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) +# r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) +# r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) +# r5 = LG.LinearRing([[5.0, 5.0], [6.0, 6.0], [7.0, 5.0], [5.0, 5.0]]) +# r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) +# # Test polygons +# p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) +# p2 = LG.Polygon([ +# [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], +# [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], +# [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] +# ]) +# p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) +# p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) +# p5 = LG.Polygon([[[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]]) +# p6 = LG.Polygon([[[0.1, 0.4], [0.1, 0.8], [0.3, 0.8], [0.3, 0.4], [0.1, 0.4]]]) +# p7 = LG.Polygon([[[-2.0, 0.0], [-1.0, 0.0], [-1.5, 1.5], [-2.0, 0.0]]]) +# p8 = LG.Polygon([ +# [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] +# ]) +# # Test multipolygons +# m1 = LG.MultiPolygon([p3, p6]) +# m2 = LG.MultiPolygon([p3, p4]) +# m3 = LG.MultiPolygon([p2, p7]) +# m4 = LG.MultiPolygon([p7]) + +# # # Point and point +# # Equal points -> not disjoint +# @test GO.disjoint(pt1, pt1) == LG.disjoint(pt1, pt1) +# # Non-equal points -> disjoint +# @test GO.disjoint(pt1, pt2) == LG.disjoint(pt1, pt2) + +# # # Point and line +# # Line endpoint (1 segment) -> not disjoint +# @test GO.disjoint(pt1, l1) == LG.disjoint(pt1, l1) +# # Middle of line (1 segment) -> not disjoint +# @test GO.disjoint(pt2, l1) == LG.disjoint(pt2, l1) +# # Not on line (1 segment) -> disjoint +# @test GO.disjoint(pt3, l1) == LG.disjoint(pt3, l1) +# # Line endpoint (2 segments) -> not disjoing +# @test GO.disjoint(pt2, l2) == LG.disjoint(pt2, l2) +# # Middle of line on joint (2 segments) -> not disjoint +# @test GO.disjoint(pt3, l2) == LG.disjoint(pt3, l2) +# # Endpoint on closed line -> not disjoint +# @test GO.disjoint(pt1, l6) == LG.disjoint(pt1, l6) + +# # # Point and ring +# # On ring corner -> not disjoint +# @test GO.disjoint(pt1, r1) == LG.disjoint(pt1, r1) +# # Outside of ring -> disjoint +# @test GO.disjoint(pt2, r1) == LG.disjoint(pt2, r1) +# # Inside of ring center (not on line) -> disjoint +# @test GO.disjoint(pt3, r1) == LG.disjoint(pt3, r1) +# # On ring edge -> not disjoint +# @test GO.disjoint(pt8, r1) == LG.disjoint(pt8, r1) + +# # # Point and polygon +# # Point on polygon vertex -> not disjoint +# @test GO.disjoint(pt1, p2) == LG.disjoint(pt1, p2) +# # Point on polygon edge -> not disjoint +# @test GO.disjoint(pt2, p2) == LG.disjoint(pt2, p2) +# # Point on edge of hold --> not disjoint +# @test GO.disjoint(pt5, p2) == LG.disjoint(pt5, p2) +# # Point in hole -> disjoint +# @test GO.disjoint(pt6, p2) == LG.disjoint(pt6, p2) +# # Point inside of polygon -> not disjoint +# @test GO.disjoint(pt7, p2) == LG.disjoint(pt7, p2) +# # Point outside of polygon -> disjoint +# @test GO.disjoint(pt9, p2) == LG.disjoint(pt9, p2) + +# # # Geometry and point (switched direction) +# @test GO.disjoint(pt1, l1) == GO.disjoint(l1, pt1) +# @test GO.disjoint(pt1, r1) == GO.disjoint(r1, pt1) +# @test GO.disjoint(pt1, p2) == GO.disjoint(p2, pt1) + +# # # Line and line +# # Equal lines -> not disjoint +# @test GO.disjoint(l1, l1) == LG.disjoint(l1, l1) +# # Lines share 2 endpoints, but don't overlap -> not disjoint +# @test GO.disjoint(l1, l2) == LG.disjoint(l1, l2) +# # Lines overlap, but neither is within other -> not disjoint +# @test GO.disjoint(l1, l3) == LG.disjoint(l1, l3) +# # Within line (no shared endpoints) -> not disjoint +# @test GO.disjoint(l1, l4) == LG.disjoint(l1, l4) +# # Line shares just 1 endpoint -> not disjoint +# @test GO.disjoint(l1, l5) == LG.disjoint(l1, l5) +# # Lines don't touch at all -> disjoint +# @test GO.disjoint(l7, l1) == LG.disjoint(l7, l1) + +# # # Line and ring +# # Shares all endpoints -> not disjoint +# @test GO.disjoint(l6, r1) == LG.disjoint(l6, r1) +# # Shares only some edges -> not disjoint +# @test GO.disjoint(l2, r3) == LG.disjoint(l2, r3) +# # line inside of ring -> disjoint +# @test GO.disjoint(l7, r1) == LG.disjoint(l7, r1) +# # line outside of ring -> disjoint +# @test GO.disjoint(l7, r2) == LG.disjoint(l7, r2) + +# # # # Line and polygon +# # # Line traces entire outline of polygon edges -> not disjoint +# # @test GO.disjoint(l6, p1) == LG.disjoint(l6, p1) +# # # Line is on edge + inside of polygon -> not disjoint +# # @test GO.disjoint(l2, p2) == LG.disjoint(l2, p2) +# # # Line goes outside of polygon -> not disjoint +# # @test GO.disjoint(l3, p2) == LG.disjoint(l3, p2) +# # # Line is fully within hole -> disjoint +# # @test GO.disjoint(l8, p2) == LG.disjoint(l8, p2) +# # # Line is on polygon edge and then cuts through hole -> not disjoint +# # @test GO.disjoint(l11, p2) == LG.disjoint(l11, p2) + +# # # Geometry and line (switched direction) +# @test GO.disjoint(l7, r1) == GO.disjoint(r1, l7) +# # @test GO.disjoint(l8, p2) == GO.disjoint(p2, l8) + +# # # Ring and line +# # Shares all endpoints -> not disjoint +# @test GO.disjoint(r1, l6) == LG.disjoint(r1, l6) +# # Shares some edges -> not disjoint +# @test GO.disjoint(r3, l2) == LG.disjoint(r3, l2) +# # Doesn't share any edges -> disjoint +# @test GO.disjoint(r4, l2) == LG.disjoint(r4, l2) + +# # # Ring and ring +# # Equal ring -> not disjoint +# @test GO.disjoint(r1, r1) == LG.disjoint(r1, r1) +# # Not equal ring but share a vertex -> not disjoint +# @test GO.disjoint(r1, r2) == LG.disjoint(r1, r2) +# # Rings not touching -> not disjoint +# @test GO.disjoint(r3, r4) == LG.disjoint(r3, r4) +# # Ring inside of ring -> disjoint +# @test GO.disjoint(r4, r2) == LG.disjoint(r4, r2) +# # Ring outside of other ring -> disjoint +# @test GO.disjoint(r2, r4) == LG.disjoint(r2, r4) + +# # Ring and polygon +# # Ring goes outside of polygon's external ring -> not disjoint +# @test GO.within(r1, p2) == LG.within(r1, p1) +# # Ring is one of polygon's holes -> not disjoint +# @test GO.disjoint(r4, p2) == LG.disjoint(r4, p2) +# # Ring is fully within polygon -> not disjoint +# @test GO.disjoint(r5, p2) == LG.disjoint(r5, p2) +# # Ring is fully within polygon's hole -> disjoint +# @test GO.disjoint(r6, p2) == LG.disjoint(r6, p2) +# # Ring is fully outside of the polygon -> disjoint +# @test GO.disjoint(r5, p2) == LG.disjoint(r5, p2) + +# # # Geometry and ring (switched direction) +# @test GO.disjoint(r4, r2) == GO.disjoint(r2, r4) +# @test GO.disjoint(p2, r6) == GO.disjoint(r6, p2) + +# # # Polygon and polygon +# # Overlapping polygons -> not disjoint +# @test GO.disjoint(p1, p2) == LG.disjoint(p1, p2) +# # Polygon is within polygon, but also on edges -> not disjoint +# @test GO.disjoint(p3, p2) == LG.disjoint(p3, p2) +# # Polygon within polygon hole --> disjoint +# @test GO.disjoint(p5, p2) == LG.disjoint(p5, p2) +# # polygon extactly overlaps with other polygon's hole -> not disjoint +# @test GO.disjoint(p8, p7) == LG.disjoint(p8, p7) + +# # # Multipolygon tests +# # Point in multipolygon -> not disjoint +# @test GO.disjoint(pt5, m1) == LG.disjoint(pt5, m1) +# # Point outside of multipolygon -> disjoint +# @test GO.disjoint(pt4, m1) == LG.disjoint(pt4, m1) +# # Line in multipolygon -> not disjoint +# @test GO.disjoint(l13, m1) == LG.disjoint(l13, m1) +# # Line outside of multipolygon -> disjoint +# @test GO.disjoint(l8, m3) == LG.disjoint(l8, m3) +# # Ring in multipolygon -> not disjoint +# @test GO.disjoint(r1, m2) == LG.disjoint(r1, m2) +# # Ring outside of multipolygon +# @test GO.disjoint(r6, m3) == LG.disjoint(r6, m3) +# # Polygon in multipolygon -> not disjoint +# @test GO.disjoint(p3, m1) == LG.disjoint(p3, m1) +# # Polygon outside of multipolygon -> disjoint +# @test GO.disjoint(p5, m3) == LG.disjoint(p5, m3) +# # Multipolygon in multipolygon -> not disjoint +# @test GO.disjoint(m1, m1) == LG.disjoint(m1, m1) +# # Multipolygon outside of multipolygon -> disjoint +# @test GO.disjoint(m1, m4) == LG.disjoint(m1, m4) \ No newline at end of file diff --git a/test/methods/geom_relations/equals.jl b/test/methods/geom_relations/equals.jl new file mode 100644 index 000000000..f7fd257d0 --- /dev/null +++ b/test/methods/geom_relations/equals.jl @@ -0,0 +1,160 @@ +# # Point and Geometry + +# Same point --> equal +@test GO.equals(pt1, pt1) == LG.equals(pt1, pt1) == true +# Different point --> not equal +@test GO.equals(pt1, pt2) == LG.equals(pt1, pt2) == false +# Point cannot equal line -> not equal +@test GO.equals(pt3, l1) == LG.equals(pt3, l1) == false +# Line cannot equal point -> not equal +@test GO.equals(l1, pt3) == LG.equals(l1, pt3) == false +# Point cannot equal ring -> not equal +@test GO.equals(pt3, r1) == LG.equals(pt3, r1) == false +# Ring cannot equal point -> not equal +@test GO.equals(r1, pt3) == LG.equals(r1, pt3) == false +# Point cannot equal polygon -> not equal +@test GO.equals(p1, pt3) == LG.equals(p1, pt3) == false +# Polygon cannot equal point -> not equal +@test GO.equals(pt3, p1) == LG.equals(pt3, p1) == false + +# # Line and Geometry +# Same line -> equals +@test GO.equals(l1, l1) == LG.equals(l1, l1) == true +# Line overlaps line edge and endpoint (nothing exterior) -> not equal +@test GO.equals(l1, l2) == LG.equals(l1, l2) == false +# Line segments both within other line segments -> doesn't touch +@test GO.equals(l1, l4) == LG.equals(l1, l4) == false + + + +# @testset "Points/MultiPoints" begin +# p1 = LG.Point([0.0, 0.0]) +# p2 = LG.Point([0.0, 1.0]) +# # Same points +# @test GO.equals(p1, p1) == LG.equals(p1, p1) +# @test GO.equals(p2, p2) == LG.equals(p2, p2) +# # Different points +# @test GO.equals(p1, p2) == LG.equals(p1, p2) + +# mp1 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0]]) +# mp2 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0], [3.0, 3.0]]) +# mp3 = LG.MultiPoint([p2]) +# # Same points +# @test GO.equals(mp1, mp1) == LG.equals(mp1, mp1) +# @test GO.equals(mp2, mp2) == LG.equals(mp2, mp2) +# # Different points +# @test GO.equals(mp1, mp2) == LG.equals(mp1, mp2) +# @test GO.equals(mp1, p1) == LG.equals(mp1, p1) +# # Point and multipoint +# @test GO.equals(p2, mp3) == LG.equals(p2, mp3) +# 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]]) +# # Equal lines +# @test GO.equals(l1, l1) == LG.equals(l1, l1) +# @test GO.equals(l2, l2) == LG.equals(l2, l2) +# # Different lines +# @test GO.equals(l1, l2) == GO.equals(l2, l1) == LG.equals(l1, l2) + +# r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) +# r2 = LG.LinearRing([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) +# r3 = GI.LinearRing([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0]]) +# l3 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) +# # Equal rings +# @test GO.equals(r1, r1) == LG.equals(r1, r1) +# @test GO.equals(r2, r2) == LG.equals(r2, r2) +# # Test equal rings without closing point +# @test GO.equals(r2, r3) +# @test GO.equals(r3, l3) +# # Different rings +# @test GO.equals(r1, r2) == GO.equals(r2, r1) == LG.equals(r1, r2) +# # Equal linear ring and line string +# @test GO.equals(r2, l3) == LG.equals(r2, l3) +# # Equal line string and line +# @test GO.equals(l1, GI.Line([(0.0, 0.0), (0.0, 10.0)])) +# end + +# @testset "Polygons/MultiPolygons" begin +# pt1 = LG.Point([0.0, 0.0]) +# r1 = GI.LinearRing([(0, 0), (0, 5), (5, 5), (5, 0), (0, 0)]) +# p1 = GI.Polygon([[(0, 0), (0, 5), (5, 5), (5, 0), (0, 0)]]) +# p2 = GI.Polygon([[(1, 1), (1, 6), (6, 6), (6, 1), (1, 1)]]) +# p3 = 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]] +# ] +# ) +# p4 = LG.Polygon( +# [ +# [[10.0, 0.0], [10.0, 20.0], [30.0, 20.0], [30.0, 0.0], [10.0, 0.0]], +# [[16.0, 1.0], [16.0, 11.0], [25.0, 11.0], [25.0, 1.0], [16.0, 1.0]] +# ] +# ) +# p5 = 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]], +# [[11.0, 1.0], [11.0, 2.0], [12.0, 2.0], [12.0, 1.0], [11.0, 1.0]] +# ] +# ) +# p6 = GI.Polygon([[(6, 6), (6, 1), (1, 1), (1, 6), (6, 6)]]) +# p7 = GI.Polygon([[(6, 6), (1, 6), (1, 1), (6, 1), (6, 6)]]) +# p8 = GI.Polygon([[(6, 6), (1, 6), (1, 1), (6, 1)]]) +# # Point and polygon aren't equal +# GO.equals(pt1, p1) == LG.equals(pt1, p1) +# # Linear ring and polygon aren't equal +# @test GO.equals(r1, p1) == LG.equals(r1, p1) +# # Equal polygon +# @test GO.equals(p1, p1) == LG.equals(p1, p1) +# @test GO.equals(p2, p2) == LG.equals(p2, p2) +# # Equal but offset polygons +# @test GO.equals(p2, p6) == LG.equals(p2, p6) +# # Equal but opposite winding orders +# @test GO.equals(p2, p7) == LG.equals(p2, p7) +# # Equal but without closing point (implied) +# @test GO.equals(p7, p8) +# # Different polygons +# @test GO.equals(p1, p2) == LG.equals(p1, p2) +# # Equal polygons with holes +# @test GO.equals(p3, p3) == LG.equals(p3, p3) +# # Same exterior, different hole +# @test GO.equals(p3, p4) == LG.equals(p3, p4) +# # Same exterior and first hole, has an extra hole +# @test GO.equals(p3, p5) == LG.equals(p3, p5) + +# p9 = LG.Polygon( +# [[ +# [-53.57208251953125, 28.287451910503744], +# [-53.33038330078125, 28.29228897739706], +# [-53.34136962890625, 28.430052892335723], +# [-53.57208251953125, 28.287451910503744], +# ]] +# ) +# # Complex polygon +# @test GO.equals(p9, p9) == LG.equals(p9, p9) + +# 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]] +# ] +# ]) +# m2 = LG.MultiPolygon([ +# [ +# [[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]] +# ], +# [[[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0], [0.0, 0.0]]] +# ]) +# # Equal multipolygon +# @test GO.equals(m1, m1) == LG.equals(m1, m1) +# # Equal multipolygon with different order +# @test GO.equals(m2, m2) == LG.equals(m2, m2) +# # Equal polygon to multipolygon +# m3 = LG.MultiPolygon([p3]) +# @test GO.equals(p1, m3) == LG.equals(p1, m3) +# end \ No newline at end of file diff --git a/test/methods/geom_relations/intersects.jl b/test/methods/geom_relations/intersects.jl new file mode 100644 index 000000000..9747c49ce --- /dev/null +++ b/test/methods/geom_relations/intersects.jl @@ -0,0 +1,201 @@ +# Same point --> intersects +@test GO.intersects(pt1, pt1) == LG.intersects(pt1, pt1) == true +# Different point --> don't intersect +@test GO.intersects(pt1, pt2) == LG.intersects(pt1, pt2) == false +# Point on line endpoint -> intersects +@test GO.intersects(pt1, l1) == GO.intersects(l1, pt1) == LG.intersects(l1, pt1) == true +# Point outside line -> don't intersect +@test GO.intersects(pt2, l1) == GO.intersects(l1, pt2) == LG.intersects(l1, pt2) == false +# Point on line segment -> intersects +@test GO.intersects(pt3, l1) == GO.intersects(l1, pt3) == LG.intersects(l1, pt3) == true +# Point on line vertex between segments -> intersects +@test GO.intersects(pt4, l1) == GO.intersects(l1, pt4) == LG.intersects(l1, pt4) == true +# Point on ring endpoint -> intersects +@test GO.intersects(pt1, r1) == GO.intersects(r1, pt1) == LG.intersects(r1, pt1) == true +# Point outside ring -> don't intersect +@test GO.intersects(pt2, r1) == GO.intersects(r1, pt2) == LG.intersects(r1, pt2) == false +# Point on ring segment -> intersects +@test GO.intersects(pt3, r1) == GO.intersects(r1, pt3) == LG.intersects(r1, pt3) == true +# Point on ring vertex between segments -> intersects +@test GO.intersects(pt4, r1) == GO.intersects(r1, pt4) == LG.intersects(r1, pt4) == true +# Point within hole formed by ring -> don't intersect +@test GO.intersects(pt5, r1) == GO.intersects(r1, pt5) == LG.intersects(r1, pt5) == false +# Point on vertex of polygon --> intersects +@test GO.intersects(pt1, p1) == GO.intersects(p1, pt1) == LG.intersects(p1, pt1) == true +# Point outside of polygon's external ring -> doesn't intersect +@test GO.intersects(pt2, p1) == GO.intersects(p1, pt2) == LG.intersects(p1, pt2) == false +# Point on polygon's edge -> intersects +@test GO.intersects(pt4, p1) == GO.intersects(p1, pt4) == LG.intersects(p1, pt4) == true +# Point inside of polygon -> intersects +@test GO.intersects(pt5, p1) == GO.intersects(p1, pt5) == LG.intersects(p1, pt5) == true +# Point on hole edge -> intersects +@test GO.intersects(pt6, p1) == GO.intersects(p1, pt6) == LG.intersects(p1, pt6) == true +# Point inside of polygon hole -> doesn't intersect +@test GO.intersects(pt7, p1) == GO.intersects(p1, pt7) == LG.intersects(p1, pt7) == false + +# # Line and Geometry + +# Same line -> intersects +@test GO.intersects(l1, l1) == LG.intersects(l1, l1) == true +# Line overlaps line edge and endpoint -> intersects +@test GO.intersects(l1, l2) == LG.intersects(l1, l2) == true +# Line overlaps with one edge and is outside of other edge -> intersects +@test GO.intersects(l1, l3) == LG.intersects(l1, l3) == true +# Line segments both within other line segments -> intersects +@test GO.intersects(l1, l4) == LG.intersects(l1, l4) == true +# Line segments connect at endpoint -> intersects +@test GO.intersects(l1, l5) == LG.intersects(l1, l5) == true +# Line segments don't touch -> doesn't intersect +@test GO.intersects(l1, l6) == LG.intersects(l1, l6) == false +# Line segments cross -> intersects +@test GO.intersects(l1, l7) == LG.intersects(l1, l7) == true +# Line segments cross and go over and out -> intersects +@test GO.intersects(l1, l8) == LG.intersects(l1, l8) == true +# Line segments cross and overlap on endpoint -> intersects +@test GO.intersects(l1, l9) == LG.intersects(l1, l9) == true + +# @testset "Lines/Rings" begin +# # Line test intersects ----------------------------------------------------- + +# # Test for parallel lines + # l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) + # l2 = GI.Line([(0.0, 1.0), (2.5, 1.0)]) + # @test !GO.intersects(l1, l2) +# @test isnothing(GO.intersection(l1, l2)) + +# # Test for non-parallel lines that don't intersect +# l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) +# l2 = GI.Line([(2.0, -3.0), (3.0, 0.0)]) +# @test !GO.intersects(l1, l2) +# @test isnothing(GO.intersection(l1, l2)) + +# # Test for lines only touching at endpoint +# l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) +# l2 = GI.Line([(2.0, -3.0), (2.5, 0.0)]) +# @test GO.intersects(l1, l2) +# @test all(GO.intersection(l1, l2) .≈ (2.5, 0.0)) + +# # Test for lines that intersect in the middle +# l1 = GI.Line([(0.0, 0.0), (5.0, 5.0)]) +# l2 = GI.Line([(0.0, 5.0), (5.0, 0.0)]) +# @test GO.intersects(l1, l2) +# @test all(GO.intersection(l1, l2) .≈ (2.5, 2.5)) + +# # Line string test intersects ---------------------------------------------- + +# # Single element line strings crossing over each other +# l1 = LG.LineString([[5.5, 7.2], [11.2, 12.7]]) +# l2 = LG.LineString([[4.3, 13.3], [9.6, 8.1]]) +# @test GO.intersects(l1, l2) +# go_inter = GO.intersection(l1, l2) +# lg_inter = LG.intersection(l1, l2) +# @test go_inter[1][1] .≈ GI.x(lg_inter) +# @test go_inter[1][2] .≈ GI.y(lg_inter) + +# # Multi-element line strings crossing over on vertex +# l1 = LG.LineString([[0.0, 0.0], [2.5, 0.0], [5.0, 0.0]]) +# l2 = LG.LineString([[2.0, -3.0], [3.0, 0.0], [4.0, 3.0]]) +# @test GO.intersects(l1, l2) +# go_inter = GO.intersection(l1, l2) +# @test length(go_inter) == 1 +# lg_inter = LG.intersection(l1, l2) +# @test go_inter[1][1] .≈ GI.x(lg_inter) +# @test go_inter[1][2] .≈ GI.y(lg_inter) + +# # Multi-element line strings crossing over with multiple intersections +# l1 = LG.LineString([[0.0, -1.0], [1.0, 1.0], [2.0, -1.0], [3.0, 1.0]]) +# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [3.0, 0.0]]) +# @test GO.intersects(l1, l2) +# go_inter = GO.intersection(l1, l2) +# @test length(go_inter) == 3 +# lg_inter = LG.intersection(l1, l2) +# @test issetequal( +# Set(go_inter), +# Set(GO._tuple_point.(GI.getpoint(lg_inter))) +# ) + +# # Line strings far apart so extents don't overlap +# l1 = LG.LineString([[100.0, 0.0], [101.0, 0.0], [103.0, 0.0]]) +# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [3.0, 0.0]]) +# @test !GO.intersects(l1, l2) +# @test isnothing(GO.intersection(l1, l2)) + +# # Line strings close together that don't overlap +# l1 = LG.LineString([[3.0, 0.25], [5.0, 0.25], [7.0, 0.25]]) +# l2 = LG.LineString([[0.0, 0.0], [5.0, 10.0], [10.0, 0.0]]) +# @test !GO.intersects(l1, l2) +# @test isempty(GO.intersection(l1, l2)) + +# # Closed linear ring with open line string +# r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) +# l2 = LG.LineString([[0.0, -2.0], [12.0, 10.0],]) +# @test GO.intersects(r1, l2) +# go_inter = GO.intersection(r1, l2) +# @test length(go_inter) == 2 +# lg_inter = LG.intersection(r1, l2) +# @test issetequal( +# Set(go_inter), +# Set(GO._tuple_point.(GI.getpoint(lg_inter))) +# ) + +# # Closed linear ring with closed linear ring +# r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) +# r2 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) +# @test GO.intersects(r1, r2) +# go_inter = GO.intersection(r1, r2) +# @test length(go_inter) == 2 +# lg_inter = LG.intersection(r1, r2) +# @test issetequal( +# Set(go_inter), +# Set(GO._tuple_point.(GI.getpoint(lg_inter))) +# ) +# end + +# @testset "Polygons" begin +# # Two polygons that intersect +# p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) +# p2 = LG.Polygon([[[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]]) +# @test GO.intersects(p1, p2) +# @test all(GO.intersection_points(p1, p2) .== [(6.5, 3.5), (6.5, -3.5)]) + +# # Two polygons that don't intersect +# p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) +# p2 = LG.Polygon([[[13.0, 0.0], [18.0, 5.0], [23.0, 0.0], [18.0, -5.0], [13.0, 0.0]]]) +# @test !GO.intersects(p1, p2) +# @test isnothing(GO.intersection_points(p1, p2)) + +# # Polygon that intersects with linestring +# p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) +# l2 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) +# @test GO.intersects(p1, l2) +# GO.intersection_points(p1, l2) +# @test all(GO.intersection_points(p1, l2) .== [(0.0, 0.0), (10.0, 0.0)]) + +# # Polygon with a hole, line through polygon and hole +# p1 = LG.Polygon([ +# [[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]], +# [[2.0, -1.0], [2.0, 1.0], [3.0, 1.0], [3.0, -1.0], [2.0, -1.0]] +# ]) +# l2 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) +# @test GO.intersects(p1, l2) +# @test all(GO.intersection_points(p1, l2) .== [(0.0, 0.0), (2.0, 0.0), (3.0, 0.0), (10.0, 0.0)]) + +# # Polygon with a hole, line only within the hole +# p1 = LG.Polygon([ +# [[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]], +# [[2.0, -1.0], [2.0, 1.0], [3.0, 1.0], [3.0, -1.0], [2.0, -1.0]] +# ]) +# l2 = LG.LineString([[2.25, 0.0], [2.75, 0.0]]) +# @test !GO.intersects(p1, l2) +# @test isempty(GO.intersection_points(p1, l2)) +# end + +# @testset "MultiPolygons" begin +# # TODO: Add these tests +# # Multi-polygon and polygon that intersect + +# # Multi-polygon and polygon that don't intersect + +# # Multi-polygon that intersects with linestring + +# end \ No newline at end of file diff --git a/test/methods/geom_relations/overlaps.jl b/test/methods/geom_relations/overlaps.jl new file mode 100644 index 000000000..0079be0cd --- /dev/null +++ b/test/methods/geom_relations/overlaps.jl @@ -0,0 +1,151 @@ +# # Point and Geometry + +# Same point -> doesn't overlap +@test GO.overlaps(pt1, pt1) == LG.overlaps(pt1, pt1) == false +# Different point -> doesn't overlap +@test GO.overlaps(pt1, pt2) == LG.overlaps(pt1, pt2) == false +# Point cannot overlap line -> doesn't overlap +@test GO.overlaps(pt3, l1) == GO.overlaps(l1, pt3) == LG.overlaps(pt3, l1) == false +# Line cannot overlap point -> doesn't overlap +@test GO.overlaps(l1, pt3) == LG.overlaps(l1, pt3) == false +# Point cannot overlap ring -> doesn't overlap +@test GO.overlaps(pt3, r1) == LG.overlaps(pt3, r1) == false +# Ring cannot overlap point -> doesn't overlap +@test GO.overlaps(r1, pt3) == LG.overlaps(r1, pt3) == false +# Point cannot overlap polygon -> doesn't overlap +@test GO.overlaps(pt3, p1) == LG.overlaps(pt3, p1) == false +# Polygon cannot overlap point -> doesn't overlap +@test GO.overlaps(p1, pt3) == LG.overlaps(p1, pt3) == false + +# # Line and Geometry + +# Same line -> doesn't overlap +@test GO.overlaps(l1, l1) == LG.overlaps(l1, l1) == false +# Line overlaps line edge and endpoint (nothing exterior) -> doesn't overlap +@test GO.overlaps(l1, l2) == LG.overlaps(l1, l2) == false +# Line overlaps with one edge and is outside of other edge -> overlaps +@test GO.overlaps(l1, l3) == LG.overlaps(l1, l3) == true +# Line segments both within other line segments -> doesn't overlap +@test GO.overlaps(l1, l4) == LG.overlaps(l1, l4) == false +# Line segments connect at endpoint -> doesn't overlap +@test GO.overlaps(l1, l5) == LG.overlaps(l1, l5) == false +# Line segments don't touch -> doesn't overlap +@test GO.overlaps(l1, l6) == LG.overlaps(l1, l6) == false +# Line segments cross -> doesn't overlaps +@test GO.overlaps(l1, l7) == LG.overlaps(l1, l7) == false +# Line segments cross and go over and out -> overlaps +# @test GO.overlaps(l1, l8) == LG.overlaps(l1, l8) == true +# Line segments cross and overlap on endpoint -> don't overlap +# @test GO.overlaps(l1, l9) == LG.overlaps(l1, l9) == false + + +# @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]]) +# l5 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) +# l6 = LG.LineString([[0.0, 0.0], [0.0, -10.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) +# # Lines that form a hinge at the origin +# @test GO.overlaps(l1, l5) == LG.overlaps(l1, l5) +# # Lines meet at one point and continue parallel in opposite directions +# @test GO.overlaps(l1, l6) == LG.overlaps(l1, l6) +# # 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 diff --git a/test/methods/geom_relations/touches.jl b/test/methods/geom_relations/touches.jl new file mode 100644 index 000000000..2178646c6 --- /dev/null +++ b/test/methods/geom_relations/touches.jl @@ -0,0 +1,61 @@ +# # Point and Geometry + +# Same point -> doesn't touch +@test GO.touches(pt1, pt1) == LG.touches(pt1, pt1) == false +# Different point -> doesn't touch +@test GO.touches(pt1, pt2) == LG.touches(pt1, pt2) == false +# Point on line endpoint -> touches +@test GO.touches(pt1, l1) == GO.touches(l1, pt1) == LG.touches(l1, pt1) == true +# Point outside line -> doesn't touch +@test GO.touches(pt2, l1) == GO.touches(l1, pt2) == LG.touches(l1, pt2) == false +# Point on line segment -> doesn't touch +@test GO.touches(pt3, l1) == GO.touches(l1, pt3) == LG.touches(l1, pt3) == false +# Point on line vertex between segments -> doesn't touch +@test GO.touches(pt4, l1) == GO.touches(l1, pt4) == LG.touches(l1, pt4) == false +# Point on ring endpoint -> doesn't touch +@test GO.touches(pt1, r1) == GO.touches(r1, pt1) == LG.touches(r1, pt1) == false +# Point outside ring -> doesn't touch +@test GO.touches(pt2, r1) == GO.touches(r1, pt2) == LG.touches(r1, pt2) == false +# Point on ring segment -> doesn't touch +@test GO.touches(pt3, r1) == GO.touches(r1, pt3) == LG.touches(r1, pt3) == false +# Point on ring vertex between segments -> doesn't touch +@test GO.touches(pt4, r1) == GO.touches(r1, pt4) == LG.touches(r1, pt4) == false +# Point within hole formed by ring -> doesn't touch +@test GO.touches(pt5, r1) == GO.touches(r1, pt5) == LG.touches(r1, pt5) == false +# Point on vertex of polygon --> touches +@test GO.touches(pt1, p1) == GO.touches(p1, pt1) == LG.touches(p1, pt1) == true +# Point outside of polygon's external ring -> doesn't touch +@test GO.touches(pt2, p1) == GO.touches(p1, pt2) == LG.touches(p1, pt2) == false +# Point on polygon's edge -> touches +@test GO.touches(pt4, p1) == GO.touches(p1, pt4) == LG.touches(p1, pt4) == true +# Point inside of polygon -> doesn't touch +@test GO.touches(pt5, p1) == GO.touches(p1, pt5) == LG.touches(p1, pt5) == false +# Point on hole edge -> touches +@test GO.touches(pt6, p1) == GO.touches(p1, pt6) == LG.touches(p1, pt6) == true +# Point inside of polygon hole -> doesn't touch +@test GO.touches(pt7, p1) == GO.touches(p1, pt7) == LG.touches(p1, pt7) == false + +# # Line and Geometry + +# Same line -> doesn't touch +@test GO.touches(l1, l1) == LG.touches(l1, l1) == false +# Line overlaps line edge and endpoint (nothing exterior) -> doesn't touch +@test GO.touches(l1, l2) == LG.touches(l1, l2) == false +# Line overlaps with one edge and is outside of other edge -> doesn't touch +@test GO.touches(l1, l3) == LG.touches(l1, l3) == false +# Line segments both within other line segments -> doesn't touch +@test GO.touches(l1, l4) == LG.touches(l1, l4) == false +# Line segments connect at endpoint -> touches +@test GO.touches(l1, l5) == LG.touches(l1, l5) == true +# Line segments don't touch -> doesn't touch +@test GO.touches(l1, l6) == LG.touches(l1, l6) == false +# Line segments cross -> doesn't touch +@test GO.touches(l1, l7) == LG.touches(l1, l7) == false +# Line segments cross and go over and out -> doesn't touch +@test GO.touches(l1, l8) == LG.touches(l1, l8) == false +# Line segments cross and overlap on endpoint -> doesn't touch +@test GO.touches(l1, l9) == LG.touches(l1, l9) == false + + + + diff --git a/test/methods/geom_relations/within.jl b/test/methods/geom_relations/within.jl new file mode 100644 index 000000000..06b0e3003 --- /dev/null +++ b/test/methods/geom_relations/within.jl @@ -0,0 +1,306 @@ +# # Point and Geometry +# Same point --> within +@test GO.within(pt1, pt1) == LG.within(pt1, pt1) == true +# Different point --> not within +@test GO.within(pt1, pt2) == LG.within(pt1, pt2) == false +# Point on line endpoint -> not within +@test GO.within(pt1, l1) == LG.within(pt1, l1) == false +# Point outside line -> not within +@test GO.within(pt2, l1) == LG.within(pt2, l1) == false +# Point on line segment -> within +@test GO.within(pt3, l1) == LG.within(pt3, l1) == true +# Point on line vertex between segments -> within +@test GO.within(pt4, l1) == LG.within(pt4, l1) == true +# line cannot be within a point -> not within +@test GO.within(l1, pt3) == LG.within(l1, pt3) == false +# Point on ring endpoint -> within +@test GO.within(pt1, r1) == LG.within(pt1, r1) == true +# Point outside ring -> not within +@test GO.within(pt2, r1) == LG.within(pt2, r1) == false +# Point on ring segment -> within +@test GO.within(pt3, r1) == LG.within(pt3, r1) == true +# Point on ring vertex between segments -> within +@test GO.within(pt4, r1) == LG.within(pt4, r1) == true +# Ring cannot be covered by a point -> not within +@test GO.within(r1, pt3) == LG.within(r1, pt3) == false +# Point on vertex of polygon --> not within +@test GO.within(pt1, p1) == LG.within(pt1, p1) == false +# Point outside of polygon's external ring -> not within +@test GO.within(pt2, p1) == LG.within(pt2, p1) == false +# Point on polygon's edge -> not within +@test GO.within(pt4, p1) == LG.within(pt4, p1) == false +# Point inside of polygon -> within +@test GO.within(pt5, p1) == LG.within(pt5, p1) == true +# Point on hole edge -> not within +@test GO.within(pt6, p1) == LG.within(pt6, p1) == false +# Point inside of polygon hole -> not within +@test GO.within(pt7, p1) == LG.within(pt7, p1) == false +# Polygon can't be within point -> not within +@test GO.within(p1, pt5) == LG.within(p1, pt5) == false + +# # Line and Geometry + +# Same line -> within +@test GO.within(l1, l1) == LG.within(l1, l1) == true +# Line overlaps line edge and endpoint (nothing exterior) -> within +@test GO.within(l2, l1) == LG.within(l2, l1) == true +# Line overlaps with one edge and is outside of other edge -> not within +@test GO.within(l3, l1) == LG.within(l3, l1) == false +# Line segments both within other line segments -> within +@test GO.within(l4, l1) == LG.within(l4, l1) == true +# Line segments connect at endpoint -> not within +@test GO.within(l5, l1) == LG.within(l5, l1) == false +# Line segments don't touch -> not within +@test GO.within(l6, l1) == LG.within(l6, l1) == false +# Line segments cross -> not within +@test GO.within(l7, l1) == LG.within(l7, l1) == false +# Line segments cross and go over and out -> not within +@test GO.within(l8, l1) == LG.within(l8, l1) == false +# Line segments cross and overlap on endpoint -> not within +@test GO.within(l9, l1) == LG.within(l9, l1) == false + +# # Test points +# pt1 = LG.Point([0.0, 0.0]) +# pt2 = LG.Point([0.0, 0.1]) +# pt3 = LG.Point([1.0, 0.0]) +# pt4 = LG.Point([0.5, 1.0]) +# pt5 = LG.Point([0.2, 0.5]) +# pt6 = LG.Point([0.3, 0.55]) +# pt7 = LG.Point([0.6, 0.49]) +# pt8 = LG.Point([0.25, 0.75]) +# pt9 = LG.Point([0.5, 0.1]) +# # Test lines +# l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) +# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) +# l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) +# l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) +# l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) +# l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) +# l7 = LG.LineString([[0.6, 0.6], [0.8, 0.6]]) +# l8 = LG.LineString([[0.3, 0.55], [0.3, 0.65]]) +# l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) +# l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) +# l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) +# l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) +# l13 = LG.LineString([[0.5, 0.01], [0.5, 0.09]]) +# # Test rings +# r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) +# r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) +# r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) +# r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) +# r5 = LG.LinearRing([[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]) +# r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) +# r7 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.5, 0.3], [0.0, 0.3],[0.0, 0.0]]) +# # Test polygons +# p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) +# p2 = LG.Polygon([ +# [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], +# [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], +# [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] +# ]) +# p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) +# p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) +# p5 = LG.Polygon([[[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]]) +# p6 = LG.Polygon([[[0.1, 0.4], [0.1, 0.8], [0.3, 0.8], [0.3, 0.4], [0.1, 0.4]]]) +# p7 = LG.Polygon([ +# [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], +# [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]], +# ]) +# p8 = LG.Polygon([ +# [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], +# [[0.3, 0.3], [0.3, 0.7], [0.7, 0.7], [0.7, 0.3], [0.3, 0.3]] +# ]) +# p9 = LG.Polygon([ +# [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], +# [[0.45, 0.45], [0.45, 0.55], [0.55, 0.55], [0.55, 0.45], [0.45, 0.45]] +# ]) +# p10 = LG.Polygon([ +# [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], +# [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] +# ]) +# p11 = LG.Polygon([ +# [[0.45, 0.5], [0.45, 0.75], [0.55, 0.75], [0.55, 0.5], [0.45, 0.5]] +# ]) +# p12 = LG.Polygon([ +# [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] +# ]) +# # Test multipolygons +# m1 = LG.MultiPolygon([p3, p6]) +# m2 = LG.MultiPolygon([p3, p4]) + +# # # Point and point +# # Equal points -> within +# @test GO.within(pt1, pt1) == LG.within(pt1, pt1) +# # Different points -> not within +# @test GO.within(pt1, pt2) == LG.within(pt1, pt2) + +# # # Point and line +# # Line endpoint (1 segment) -> not within +# @test GO.within(pt1, l1) == LG.within(pt1, l1) +# # Line endpoint (2 segments) -> not within +# @test GO.within(pt2, l2) == LG.within(pt2, l2) +# # Middle of line (1 segment) -> within +# @test GO.within(pt2, l1) == LG.within(pt2, l1) +# # Not on line (1 segment) -> not within +# @test GO.within(pt3, l1) == LG.within(pt3, l1) +# # Middle of line on joint (2 segments) -> within +# @test GO.within(pt3, l2) == LG.within(pt3, l2) +# # Endpoint on closed line -> within +# @test GO.within(pt1, l6) == LG.within(pt1, l6) + +# # # Point and Ring +# # On ring corner -> within +# @test GO.within(pt1, r1) == LG.within(pt1, r1) +# # Outside of ring -> not within +# @test GO.within(pt2, r1) == LG.within(pt2, r1) +# # Inside of ring center (not on line) -> not within +# @test GO.within(pt3, r1) == LG.within(pt3, r1) +# # On ring edge -> within +# @test GO.within(pt8, r1) == LG.within(pt8, r1) + +# # # Point and polygon +# # On polygon vertex -> not within +# @test GO.within(pt1, p1) == LG.within(pt1, p1) +# # Outside of polygon -> not within +# @test GO.within(pt2, p1) == LG.within(pt2, p1) +# # Inside of polygon -> within +# @test GO.within(pt3, p1) == LG.within(pt3, p1) +# # On polygon vertex (with holes) -> not within +# @test GO.within(pt1, p2) == LG.within(pt1, p2) +# # On polygon edge (with holes) -> not within +# @test GO.within(pt2, p2) == LG.within(pt2, p2) +# # On hole vertex -> not within +# @test GO.within(pt5, p2) == LG.within(pt5, p2) +# # Within hole -> not within +# @test GO.within(pt6, p2) == LG.within(pt6, p2) +# # Inside of polygon (with holes) -> within +# @test GO.within(pt7, p2) == LG.within(pt7, p2) + +# # # Line and line +# # Equal lines -> within +# @test GO.within(l1, l1) == LG.within(l1, l1) +# # Lines share 2 endpoints, but don't overlap -> not within +# @test GO.within(l1, l2) == LG.within(l1, l2) +# # Lines overlap, but neither is within other -> not within +# @test GO.within(l1, l3) == LG.within(l1, l3) +# # Within line (no shared endpoints) -> within +# @test GO.within(l1, l4) == LG.within(l1, l4) +# # Line shares just one endpoint -> not within +# @test GO.within(l1, l5) == LG.within(l1, l5) + +# # # Line and ring +# # Shares all endpoints -> within +# @test GO.within(l6, r1) == LG.within(l6, r1) +# # Shares all endpoints, but ring has extra edge -> within +# @test GO.within(l2, r2) == LG.within(l2, r2) +# # Doesn't share all edges -> not within +# @test GO.within(l2, r3) == LG.within(l2, r3) +# # Shares all endpoints, but adds one extra segment -> not within +# @test GO.within(l12, r1) == LG.within(l12, r1) + +# # Line and polygon +# # Line traces entire outline of polygon edges -> not within +# @test GO.within(l6, p1) == LG.within(l6, p1) +# # Line is edge of polygon -> not within +# @test GO.within(l1, p2) == LG.within(l1, p2) +# # Line is on edge + inside of polygon -> within +# @test GO.within(l2, p2) == LG.within(l2, p2) +# # Line goes outside of polygon -> not within +# @test GO.within(l3, p2) == LG.within(l3, p2) +# # Line is fully within polygon -> within +# @test GO.within(l7, p2) == LG.within(l7, p2) +# # Line is fully within hole -> not within +# @test GO.within(l8, p2) == LG.within(l8, p2) +# # Line is on hole edge -> not within +# @test GO.within(l9, p2) == LG.within(l9, p2) +# # Line on polygon edge and then enters polygon to end on hole vertex -> within +# @test GO.within(l10, p2) == LG.within(l10, p2) +# # Line is on polygon edge and then cuts through hole -> not within +# @test GO.within(l11, p2) == LG.within(l11, p2) + +# # # Ring and line +# # Shares all endpoints -> within +# @test GO.within(r1, l6) == LG.within(r1, l6) +# # Shares all endpoints but ring has closing edge -> not within +# @test GO.within(r2, l2) == LG.within(r2, l2) +# # Doesn't share all edges -> not within +# @test GO.within(r3, l2) == LG.within(r3, l2) +# # Shares all endpoints, but line has one extra segment -> within +# @test GO.within(r1, l12) == LG.within(r1, l12) + +# # # Ring and ring +# # Equal ring -> within +# @test GO.within(r1, r1) == LG.within(r1, r1) +# # Not equal ring -> not within +# @test GO.within(r1, r2) == LG.within(r1, r2) +# # Not equal ring -> not within +# @test GO.within(r1, r3) == LG.within(r1, r3) +# # Rings share all edges, but second ring has extra edges -> within +# @test GO.within(r2, r7) == LG.within(r2, r7) + +# # # Ring and polygon +# # Ring is equal to polygon's external ring, no holes -> not within +# @test GO.within(r1, p1) == LG.within(r1, p1) +# # Ring goes outside of polygon's external ring -> not within +# @test GO.within(r1, p2) == LG.within(r1, p1) +# # Ring is within polygon, but also on edges -> within +# @test GO.within(r2, p2) == LG.within(r2, p2) +# # Ring is within polygon, but also on edges -> within +# @test GO.within(r3, p2) == LG.within(r3, p2) +# # Ring is one of polygon's holes -> not within +# @test GO.within(r4, p2) == LG.within(r4, p2) +# # Ring is fully within polygon that has holes -> within +# @test GO.within(r5, p2) == LG.within(r5, p2) +# # Ring is fully within polygon's hole -> not within +# @test GO.within(r6, p2) == LG.within(r6, p2) + +# # # Polygon in polygon +# # Same polygon -> within +# @test GO.within(p1, p1) == LG.within(p1, p1) +# @test GO.within(p2, p2) == LG.within(p2, p2) +# # Polygon not in polygon -> not within +# @test GO.within(p1, p2) == LG.within(p1, p2) +# @test GO.within(p2, p1) == LG.within(p2, p1) +# # Polygon is within polygon, but also on edges -> within +# @test GO.within(p3, p2) == LG.within(p3, p2) +# # Polygon within polygon with holes -> within +# @test GO.within(p4, p2) == LG.within(p4, p2) +# # Polygon within polygon hole --> not within +# @test GO.within(p5, p2) == LG.within(p5, p2) +# # Polygon overlapping with other polygon's hole -> not within +# @test GO.within(p6, p2) == LG.within(p6, p2) +# # Polygon with hole nested with other polygon's hole --> within +# @test GO.within(p8, p7) == LG.within(p8, p7) +# # Nested holes but not within -> not within +# @test GO.within(p9, p7) == LG.within(p9, p7) +# # Nested with same hole -> within +# @test GO.within(p10, p7) == LG.within(p10, p7) +# # within external ring but intersects with hole -> not within +# @test GO.within(p11, p7) == LG.within(p11, p7) +# # polygon extactly overlaps with other polygon's hole -> not within +# @test GO.within(p12, p7) == LG.within(p12, p7) + +# # # Multipolygon tests +# # Point in multipolygon +# @test GO.within(pt5, m1) == LG.within(pt5, m1) +# @test GO.within(pt9, m1) == LG.within(pt9, m1) +# # Point outside of multipolygon +# @test GO.within(pt4, m1) == LG.within(pt4, m1) +# # Line in multipolygon +# @test GO.within(l13, m1) == LG.within(l13, m1) +# @test GO.within(l9, m1) == LG.within(l9, m1) +# # Line outside of multipolygon +# @test GO.within(l1, m1) == LG.within(l1, m1) +# # Ring in multipolygon +# @test GO.within(r1, m2) == LG.within(r1, m2) +# # Ring outside of multipolygon +# @test GO.within(r1, m1) == LG.within(r1, m1) +# # Polygon in multipolygon +# @test GO.within(p3, m1) == LG.within(p3, m1) +# @test GO.within(p6, m1) == LG.within(p6, m1) +# # Polygon outside of multipolygon +# @test GO.within(p1, m1) == LG.within(p1, m1) +# # Multipolygon in multipolygon +# @test GO.within(m1, m1) == LG.within(m1, m1) +# # Multipolygon outside of multipolygon +# @test GO.within(m2, m1) == LG.within(m2, m1) diff --git a/test/methods/intersects.jl b/test/methods/intersects.jl deleted file mode 100644 index 4251d45a8..000000000 --- a/test/methods/intersects.jl +++ /dev/null @@ -1,145 +0,0 @@ -@testset "Lines/Rings" begin - # Line test intersects ----------------------------------------------------- - - # Test for parallel lines - l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) - l2 = GI.Line([(0.0, 1.0), (2.5, 1.0)]) - @test !GO.intersects(l1, l2) - @test isnothing(GO.intersection(l1, l2)) - - # Test for non-parallel lines that don't intersect - l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) - l2 = GI.Line([(2.0, -3.0), (3.0, 0.0)]) - @test !GO.intersects(l1, l2) - @test isnothing(GO.intersection(l1, l2)) - - # Test for lines only touching at endpoint - l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) - l2 = GI.Line([(2.0, -3.0), (2.5, 0.0)]) - @test GO.intersects(l1, l2) - @test all(GO.intersection(l1, l2) .≈ (2.5, 0.0)) - - # Test for lines that intersect in the middle - l1 = GI.Line([(0.0, 0.0), (5.0, 5.0)]) - l2 = GI.Line([(0.0, 5.0), (5.0, 0.0)]) - @test GO.intersects(l1, l2) - @test all(GO.intersection(l1, l2) .≈ (2.5, 2.5)) - - # Line string test intersects ---------------------------------------------- - - # Single element line strings crossing over each other - l1 = LG.LineString([[5.5, 7.2], [11.2, 12.7]]) - l2 = LG.LineString([[4.3, 13.3], [9.6, 8.1]]) - @test GO.intersects(l1, l2) - go_inter = GO.intersection(l1, l2) - lg_inter = LG.intersection(l1, l2) - @test go_inter[1][1] .≈ GI.x(lg_inter) - @test go_inter[1][2] .≈ GI.y(lg_inter) - - # Multi-element line strings crossing over on vertex - l1 = LG.LineString([[0.0, 0.0], [2.5, 0.0], [5.0, 0.0]]) - l2 = LG.LineString([[2.0, -3.0], [3.0, 0.0], [4.0, 3.0]]) - @test GO.intersects(l1, l2) - go_inter = GO.intersection(l1, l2) - @test length(go_inter) == 1 - lg_inter = LG.intersection(l1, l2) - @test go_inter[1][1] .≈ GI.x(lg_inter) - @test go_inter[1][2] .≈ GI.y(lg_inter) - - # Multi-element line strings crossing over with multiple intersections - l1 = LG.LineString([[0.0, -1.0], [1.0, 1.0], [2.0, -1.0], [3.0, 1.0]]) - l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [3.0, 0.0]]) - @test GO.intersects(l1, l2) - go_inter = GO.intersection(l1, l2) - @test length(go_inter) == 3 - lg_inter = LG.intersection(l1, l2) - @test issetequal( - Set(go_inter), - Set(GO._tuple_point.(GI.getpoint(lg_inter))) - ) - - # Line strings far apart so extents don't overlap - l1 = LG.LineString([[100.0, 0.0], [101.0, 0.0], [103.0, 0.0]]) - l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [3.0, 0.0]]) - @test !GO.intersects(l1, l2) - @test isnothing(GO.intersection(l1, l2)) - - # Line strings close together that don't overlap - l1 = LG.LineString([[3.0, 0.25], [5.0, 0.25], [7.0, 0.25]]) - l2 = LG.LineString([[0.0, 0.0], [5.0, 10.0], [10.0, 0.0]]) - @test !GO.intersects(l1, l2) - @test isempty(GO.intersection(l1, l2)) - - # Closed linear ring with open line string - r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) - l2 = LG.LineString([[0.0, -2.0], [12.0, 10.0],]) - @test GO.intersects(r1, l2) - go_inter = GO.intersection(r1, l2) - @test length(go_inter) == 2 - lg_inter = LG.intersection(r1, l2) - @test issetequal( - Set(go_inter), - Set(GO._tuple_point.(GI.getpoint(lg_inter))) - ) - - # Closed linear ring with closed linear ring - r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) - r2 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) - @test GO.intersects(r1, r2) - go_inter = GO.intersection(r1, r2) - @test length(go_inter) == 2 - lg_inter = LG.intersection(r1, r2) - @test issetequal( - Set(go_inter), - Set(GO._tuple_point.(GI.getpoint(lg_inter))) - ) -end - -@testset "Polygons" begin - # Two polygons that intersect - p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) - p2 = LG.Polygon([[[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]]) - @test GO.intersects(p1, p2) - @test all(GO.intersection_points(p1, p2) .== [(6.5, 3.5), (6.5, -3.5)]) - - # Two polygons that don't intersect - p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) - p2 = LG.Polygon([[[13.0, 0.0], [18.0, 5.0], [23.0, 0.0], [18.0, -5.0], [13.0, 0.0]]]) - @test !GO.intersects(p1, p2) - @test isnothing(GO.intersection_points(p1, p2)) - - # Polygon that intersects with linestring - p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) - l2 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) - @test GO.intersects(p1, l2) - GO.intersection_points(p1, l2) - @test all(GO.intersection_points(p1, l2) .== [(0.0, 0.0), (10.0, 0.0)]) - - # Polygon with a hole, line through polygon and hole - p1 = LG.Polygon([ - [[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]], - [[2.0, -1.0], [2.0, 1.0], [3.0, 1.0], [3.0, -1.0], [2.0, -1.0]] - ]) - l2 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) - @test GO.intersects(p1, l2) - @test all(GO.intersection_points(p1, l2) .== [(0.0, 0.0), (2.0, 0.0), (3.0, 0.0), (10.0, 0.0)]) - - # Polygon with a hole, line only within the hole - p1 = LG.Polygon([ - [[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]], - [[2.0, -1.0], [2.0, 1.0], [3.0, 1.0], [3.0, -1.0], [2.0, -1.0]] - ]) - l2 = LG.LineString([[2.25, 0.0], [2.75, 0.0]]) - @test !GO.intersects(p1, l2) - @test isempty(GO.intersection_points(p1, l2)) -end - -@testset "MultiPolygons" begin - # TODO: Add these tests - # Multi-polygon and polygon that intersect - - # Multi-polygon and polygon that don't intersect - - # Multi-polygon that intersects with linestring - -end \ No newline at end of file diff --git a/test/methods/overlaps.jl b/test/methods/overlaps.jl deleted file mode 100644 index 126924743..000000000 --- a/test/methods/overlaps.jl +++ /dev/null @@ -1,110 +0,0 @@ -@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]]) - l5 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) - l6 = LG.LineString([[0.0, 0.0], [0.0, -10.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) - # Lines that form a hinge at the origin - @test GO.overlaps(l1, l5) == LG.overlaps(l1, l5) - # Lines meet at one point and continue parallel in opposite directions - @test GO.overlaps(l1, l6) == LG.overlaps(l1, l6) - # 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 diff --git a/test/methods/touches.jl b/test/methods/touches.jl deleted file mode 100644 index 2f9ace932..000000000 --- a/test/methods/touches.jl +++ /dev/null @@ -1,28 +0,0 @@ -pt1 = LG.Point([0.0, 0.0]) -pt2 = LG.Point([0.0, 0.1]) -pt3 = LG.Point([1.0, 0.0]) - -l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) -l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) -l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) -l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) -l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) - -# Point and point -@test GO.touches(pt1, pt1) == LG.touches(pt1, pt1) -@test GO.touches(pt1, pt2) == LG.touches(pt1, pt2) - -# Point and line -@test GO.touches(pt1, l1) == LG.touches(pt1, l1) -@test GO.touches(pt2, l1) == LG.touches(pt2, l1) -@test GO.touches(pt3, l1) == LG.touches(pt3, l1) -@test GO.touches(pt1, l2) == LG.touches(pt1, l2) -@test GO.touches(pt2, l2) == LG.touches(pt2, l2) -@test GO.touches(pt3, l2) == LG.touches(pt3, l2) - -# Line and line -@test GO.touches(l1, l1) == LG.touches(l1, l1) -@test GO.touches(l1, l2) == LG.touches(l1, l2) -@test GO.touches(l1, l3) == LG.touches(l1, l3) -@test GO.touches(l1, l4) == LG.touches(l1, l4) -@test GO.touches(l1, l5) == LG.touches(l1, l5) \ No newline at end of file diff --git a/test/methods/within.jl b/test/methods/within.jl deleted file mode 100644 index 0a89e8a06..000000000 --- a/test/methods/within.jl +++ /dev/null @@ -1,245 +0,0 @@ -# Test points -pt1 = LG.Point([0.0, 0.0]) -pt2 = LG.Point([0.0, 0.1]) -pt3 = LG.Point([1.0, 0.0]) -pt4 = LG.Point([0.5, 1.0]) -pt5 = LG.Point([0.2, 0.5]) -pt6 = LG.Point([0.3, 0.55]) -pt7 = LG.Point([0.6, 0.49]) -pt8 = LG.Point([0.25, 0.75]) -pt9 = LG.Point([0.5, 0.1]) -# Test lines -l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) -l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) -l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) -l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) -l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) -l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) -l7 = LG.LineString([[0.6, 0.6], [0.8, 0.6]]) -l8 = LG.LineString([[0.3, 0.55], [0.3, 0.65]]) -l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) -l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) -l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) -l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) -l13 = LG.LineString([[0.5, 0.01], [0.5, 0.09]]) -# Test rings -r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) -r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) -r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) -r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) -r5 = LG.LinearRing([[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]) -r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) -r7 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.5, 0.3], [0.0, 0.3],[0.0, 0.0]]) -# Test polygons -p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) -p2 = LG.Polygon([ - [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], - [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], - [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] -]) -p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) -p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) -p5 = LG.Polygon([[[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]]) -p6 = LG.Polygon([[[0.1, 0.4], [0.1, 0.8], [0.3, 0.8], [0.3, 0.4], [0.1, 0.4]]]) -p7 = LG.Polygon([ - [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], - [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]], -]) -p8 = LG.Polygon([ - [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], - [[0.3, 0.3], [0.3, 0.7], [0.7, 0.7], [0.7, 0.3], [0.3, 0.3]] -]) -p9 = LG.Polygon([ - [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], - [[0.45, 0.45], [0.45, 0.55], [0.55, 0.55], [0.55, 0.45], [0.45, 0.45]] -]) -p10 = LG.Polygon([ - [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], - [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] -]) -p11 = LG.Polygon([ - [[0.45, 0.5], [0.45, 0.75], [0.55, 0.75], [0.55, 0.5], [0.45, 0.5]] -]) -p12 = LG.Polygon([ - [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] -]) -# Test multipolygons -m1 = LG.MultiPolygon([p3, p6]) -m2 = LG.MultiPolygon([p3, p4]) - -# # Point and point -# Equal points -> within -@test GO.within(pt1, pt1) == LG.within(pt1, pt1) -# Different points -> not within -@test GO.within(pt1, pt2) == LG.within(pt1, pt2) - -# # Point and line -# Line endpoint (1 segment) -> not within -@test GO.within(pt1, l1) == LG.within(pt1, l1) -# Line endpoint (2 segments) -> not within -@test GO.within(pt2, l2) == LG.within(pt2, l2) -# Middle of line (1 segment) -> within -@test GO.within(pt2, l1) == LG.within(pt2, l1) -# Not on line (1 segment) -> not within -@test GO.within(pt3, l1) == LG.within(pt3, l1) -# Middle of line on joint (2 segments) -> within -@test GO.within(pt3, l2) == LG.within(pt3, l2) -# Endpoint on closed line -> within -@test GO.within(pt1, l6) == LG.within(pt1, l6) - -# # Point and Ring -# On ring corner -> within -@test GO.within(pt1, r1) == LG.within(pt1, r1) -# Outside of ring -> not within -@test GO.within(pt2, r1) == LG.within(pt2, r1) -# Inside of ring center (not on line) -> not within -@test GO.within(pt3, r1) == LG.within(pt3, r1) -# On ring edge -> within -@test GO.within(pt8, r1) == LG.within(pt8, r1) - -# # Point and polygon -# On polygon vertex -> not within -@test GO.within(pt1, p1) == LG.within(pt1, p1) -# Outside of polygon -> not within -@test GO.within(pt2, p1) == LG.within(pt2, p1) -# Inside of polygon -> within -@test GO.within(pt3, p1) == LG.within(pt3, p1) -# On polygon vertex (with holes) -> not within -@test GO.within(pt1, p2) == LG.within(pt1, p2) -# On polygon edge (with holes) -> not within -@test GO.within(pt2, p2) == LG.within(pt2, p2) -# On hole vertex -> not within -@test GO.within(pt5, p2) == LG.within(pt5, p2) -# Within hole -> not within -@test GO.within(pt6, p2) == LG.within(pt6, p2) -# Inside of polygon (with holes) -> within -@test GO.within(pt7, p2) == LG.within(pt7, p2) - -# # Line and line -# Equal lines -> within -@test GO.within(l1, l1) == LG.within(l1, l1) -# Lines share 2 endpoints, but don't overlap -> not within -@test GO.within(l1, l2) == LG.within(l1, l2) -# Lines overlap, but neither is within other -> not within -@test GO.within(l1, l3) == LG.within(l1, l3) -# Within line (no shared endpoints) -> within -@test GO.within(l1, l4) == LG.within(l1, l4) -# Line shares just one endpoint -> not within -@test GO.within(l1, l5) == LG.within(l1, l5) - -# # Line and ring -# Shares all endpoints -> within -@test GO.within(l6, r1) == LG.within(l6, r1) -# Shares all endpoints, but ring has extra edge -> within -@test GO.within(l2, r2) == LG.within(l2, r2) -# Doesn't share all edges -> not within -@test GO.within(l2, r3) == LG.within(l2, r3) -# Shares all endpoints, but adds one extra segment -> not within -@test GO.within(l12, r1) == LG.within(l12, r1) - -# Line and polygon -# Line traces entire outline of polygon edges -> not within -@test GO.within(l6, p1) == LG.within(l6, p1) -# Line is edge of polygon -> not within -@test GO.within(l1, p2) == LG.within(l1, p2) -# Line is on edge + inside of polygon -> within -@test GO.within(l2, p2) == LG.within(l2, p2) -# Line goes outside of polygon -> not within -@test GO.within(l3, p2) == LG.within(l3, p2) -# Line is fully within polygon -> within -@test GO.within(l7, p2) == LG.within(l7, p2) -# Line is fully within hole -> not within -@test GO.within(l8, p2) == LG.within(l8, p2) -# Line is on hole edge -> not within -@test GO.within(l9, p2) == LG.within(l9, p2) -# Line on polygon edge and then enters polygon to end on hole vertex -> within -@test GO.within(l10, p2) == LG.within(l10, p2) -# Line is on polygon edge and then cuts through hole -> not within -@test GO.within(l11, p2) == LG.within(l11, p2) - -# # Ring and line -# Shares all endpoints -> within -@test GO.within(r1, l6) == LG.within(r1, l6) -# Shares all endpoints but ring has closing edge -> not within -@test GO.within(r2, l2) == LG.within(r2, l2) -# Doesn't share all edges -> not within -@test GO.within(r3, l2) == LG.within(r3, l2) -# Shares all endpoints, but line has one extra segment -> within -@test GO.within(r1, l12) == LG.within(r1, l12) - -# # Ring and ring -# Equal ring -> within -@test GO.within(r1, r1) == LG.within(r1, r1) -# Not equal ring -> not within -@test GO.within(r1, r2) == LG.within(r1, r2) -# Not equal ring -> not within -@test GO.within(r1, r3) == LG.within(r1, r3) -# Rings share all edges, but second ring has extra edges -> within -@test GO.within(r2, r7) == LG.within(r2, r7) - -# # Ring and polygon -# Ring is equal to polygon's external ring, no holes -> not within -@test GO.within(r1, p1) == LG.within(r1, p1) -# Ring goes outside of polygon's external ring -> not within -@test GO.within(r1, p2) == LG.within(r1, p1) -# Ring is within polygon, but also on edges -> within -@test GO.within(r2, p2) == LG.within(r2, p2) -# Ring is within polygon, but also on edges -> within -@test GO.within(r3, p2) == LG.within(r3, p2) -# Ring is one of polygon's holes -> not within -@test GO.within(r4, p2) == LG.within(r4, p2) -# Ring is fully within polygon that has holes -> within -@test GO.within(r5, p2) == LG.within(r5, p2) -# Ring is fully within polygon's hole -> not within -@test GO.within(r6, p2) == LG.within(r6, p2) - -# # Polygon in polygon -# Same polygon -> within -@test GO.within(p1, p1) == LG.within(p1, p1) -@test GO.within(p2, p2) == LG.within(p2, p2) -# Polygon not in polygon -> not within -@test GO.within(p1, p2) == LG.within(p1, p2) -@test GO.within(p2, p1) == LG.within(p2, p1) -# Polygon is within polygon, but also on edges -> within -@test GO.within(p3, p2) == LG.within(p3, p2) -# Polygon within polygon with holes -> within -@test GO.within(p4, p2) == LG.within(p4, p2) -# Polygon within polygon hole --> not within -@test GO.within(p5, p2) == LG.within(p5, p2) -# Polygon overlapping with other polygon's hole -> not within -@test GO.within(p6, p2) == LG.within(p6, p2) -# Polygon with hole nested with other polygon's hole --> within -@test GO.within(p8, p7) == LG.within(p8, p7) -# Nested holes but not within -> not within -@test GO.within(p9, p7) == LG.within(p9, p7) -# Nested with same hole -> within -@test GO.within(p10, p7) == LG.within(p10, p7) -# within external ring but intersects with hole -> not within -@test GO.within(p11, p7) == LG.within(p11, p7) -# polygon extactly overlaps with other polygon's hole -> not within -@test GO.within(p12, p7) == LG.within(p12, p7) - -# # Multipolygon tests -# Point in multipolygon -@test GO.within(pt5, m1) == LG.within(pt5, m1) -@test GO.within(pt9, m1) == LG.within(pt9, m1) -# Point outside of multipolygon -@test GO.within(pt4, m1) == LG.within(pt4, m1) -# Line in multipolygon -@test GO.within(l13, m1) == LG.within(l13, m1) -@test GO.within(l9, m1) == LG.within(l9, m1) -# Line outside of multipolygon -@test GO.within(l1, m1) == LG.within(l1, m1) -# Ring in multipolygon -@test GO.within(r1, m2) == LG.within(r1, m2) -# Ring outside of multipolygon -@test GO.within(r1, m1) == LG.within(r1, m1) -# Polygon in multipolygon -@test GO.within(p3, m1) == LG.within(p3, m1) -@test GO.within(p6, m1) == LG.within(p6, m1) -# Polygon outside of multipolygon -@test GO.within(p1, m1) == LG.within(p1, m1) -# Multipolygon in multipolygon -@test GO.within(m1, m1) == LG.within(m1, m1) -# Multipolygon outside of multipolygon -@test GO.within(m2, m1) == LG.within(m2, m1) diff --git a/test/runtests.jl b/test/runtests.jl index c9c34572f..56b79a738 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,17 +16,10 @@ const GO = GeometryOps @testset "Primitives" begin include("primitives.jl") end # Methods @testset "Barycentric coordinate operations" begin include("methods/barycentric.jl") end - @testset "Bools" begin include("methods/bools.jl") end @testset "Centroid" begin include("methods/centroid.jl") end - @testset "Disjoint" begin include("methods/disjoint.jl") end - @testset "Equals" begin include("methods/equals.jl") end - # @testset "Geom in geom" begin include("methods/geom_in_geom.jl") end - # @testset "Geom on geom" begin include("methods/geom_on_geom.jl") end - @testset "Intersect" begin include("methods/intersects.jl") end + @testset "Bools" begin include("methods/bools.jl") end @testset "Signed Area" begin include("methods/signed_area.jl") end - @testset "Touches" begin include("methods/touches.jl") end - @testset "Overlaps" begin include("methods/overlaps.jl") end - @testset "Within" begin include("methods/within.jl") end + # Transformations @testset "Reproject" begin include("transformations/reproject.jl") end From 4be9961fbafe468ed70c84b3608838d63aa8e669 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Fri, 22 Dec 2023 09:18:11 -0800 Subject: [PATCH 24/33] Continue to debug and test --- src/methods/geom_relations/coveredby.jl | 14 + src/methods/geom_relations/crosses.jl | 47 +- .../geom_relations/geom_geom_processors.jl | 189 ++++--- src/methods/geom_relations/within.jl | 91 ++-- src/try.jl | 10 +- test/methods/bools.jl | 15 +- test/methods/geom_relations/coveredby.jl | 37 +- test/methods/geom_relations/covers.jl | 36 ++ test/methods/geom_relations/within.jl | 464 +++++++++--------- 9 files changed, 497 insertions(+), 406 deletions(-) diff --git a/src/methods/geom_relations/coveredby.jl b/src/methods/geom_relations/coveredby.jl index 5e29b5d74..0421a531f 100644 --- a/src/methods/geom_relations/coveredby.jl +++ b/src/methods/geom_relations/coveredby.jl @@ -264,3 +264,17 @@ coveredby( closed_line = true, ) +""" + coveredby( + ::Union{GI.PolygonTrait, GI.MultiPolygonTrait}, g1, + ::Union{GI.LineTrait, GI.LineStringTrait, GI.LinearRingTrait}, g2, + )::Bool + +Polygons and multipolygons cannot be covered by curves as they are not filled. +Return false +""" +coveredby( + ::Union{GI.PolygonTrait, GI.MultiPolygonTrait}, g1, + ::Union{GI.LineTrait, GI.LineStringTrait, GI.LinearRingTrait}, g2, +) = false + diff --git a/src/methods/geom_relations/crosses.jl b/src/methods/geom_relations/crosses.jl index 8af4bc34d..1c318ccc3 100644 --- a/src/methods/geom_relations/crosses.jl +++ b/src/methods/geom_relations/crosses.jl @@ -77,17 +77,26 @@ A line string is crosses another linestring if the vertices and edges of the first linestring are crosses the second linestring, including the first and last vertex. Return true if those conditions are met, else false. """ -crosses( - ::GI.LineStringTrait, g1, - ::GI.LineStringTrait, g2, -) = _line_curve_process( - g1, g2; - in_allow = true, on_allow = false, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = false, - closed_curve = false, +# crosses( +# ::GI.LineStringTrait, g1, +# ::GI.LineStringTrait, g2, +# ) = _line_curve_process( +# g1, g2; +# in_allow = true, on_allow = false, out_allow = true, +# in_require = true, on_require = false, out_require = true, +# closed_line = false, +# closed_curve = false, +# ) + +function crosses( + ::LineStringTrait, g1, + ::LineStringTrait, g2 ) + + +end + """ crosses(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool @@ -95,16 +104,16 @@ A line string is crosses a linear ring if the vertices and edges of the linestring are crosses the linear ring. Return true if those conditions are met, else false. """ -crosses( - ::GI.LineStringTrait, g1, - ::GI.LinearRingTrait, g2, -) = _line_curve_process( - g1, g2; - in_allow = false, on_allow = true, out_allow = true, - in_require = false, on_require = true, out_require = true, - closed_line = false, - closed_curve = true, -) +# crosses( +# ::GI.LineStringTrait, g1, +# ::GI.LinearRingTrait, g2, +# ) = _line_curve_process( +# g1, g2; +# in_allow = false, on_allow = true, out_allow = true, +# in_require = false, on_require = true, out_require = true, +# closed_line = false, +# closed_curve = true, +# ) """ crosses(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool diff --git a/src/methods/geom_relations/geom_geom_processors.jl b/src/methods/geom_relations/geom_geom_processors.jl index 0a45b00d4..a049b709f 100644 --- a/src/methods/geom_relations/geom_geom_processors.jl +++ b/src/methods/geom_relations/geom_geom_processors.jl @@ -318,7 +318,6 @@ end function _line_filled_curve_interactions( line, curve; closed_line = false, - filled_line = false, ) in_curve = false on_curve = false @@ -332,7 +331,6 @@ function _line_filled_curve_interactions( nl -= first_last_equal_line ? 1 : 0 nc -= first_last_equal_curve ? 1 : 0 closed_line |= first_last_equal_line - filled_line &= closed_line # See if first point is in an acceptable orientation l_start = GI.getpoint(line, closed_line ? nl : 1) @@ -416,19 +414,36 @@ function _line_filled_curve_interactions( end l_start = l_end end - if filled_line && !in_curve - if !out_curve # line overlaps entire curve boundary - in_curve = true # line interior overlaps boundary filled interior - else - cent = centroid(line) - if within(cent, curve) - in_curve = true - on_curve = true - end + return in_curve, on_curve, out_curve +end + +function _line_polygon_interactions( + line, polygon; + closed_line = false, +) + in_ext, on_ext, out_ext = _line_filled_curve_interactions( + line, GI.getexterior(polygon); + closed_line = closed_line, + ) + !in_ext && return (in_ext, on_ext, out_ext) + # Loop over polygon holes + for hole in GI.gethole(polygon) + in_hole, on_hole, out_hole =_line_filled_curve_interactions( + line, hole; + closed_line = closed_line, + ) + if in_hole + out_ext = true + end + if on_hole + on_ext = true + end + if !out_hole # entire line is in/on hole, can't be in/on other holes + in_ext = false + return (in_ext, on_ext, out_ext) end end - - return in_curve, on_curve, out_curve + return in_ext, on_ext, out_ext end function _line_polygon_process( @@ -436,7 +451,6 @@ function _line_polygon_process( in_allow, on_allow, out_allow, in_require, on_require, out_require, closed_line = false, - filled_line = false, ) in_req_met = !in_require on_req_met = !on_require @@ -445,7 +459,6 @@ function _line_polygon_process( in_curve, on_curve, out_curve = _line_filled_curve_interactions( line, GI.getexterior(polygon); closed_line = closed_line, - filled_line = filled_line, ) if on_curve !on_allow && return false @@ -491,15 +504,12 @@ function _polygon_polygon_process( in_req_met = !in_require on_req_met = !on_require out_req_met = !out_require - + # Check if exterior of poly1 is within poly2 ext1 = GI.getexterior(poly1) - e1_in_p2, e1_on_p2, e1_out_p2 = _line_polygon_process( + ext2 = GI.getexterior(poly2) + e1_in_p2, e1_on_p2, e1_out_p2 = _line_polygon_interactions( ext1, poly2; - in_allow = in_allow, in_require = in_require, - on_allow = on_allow, on_require = on_require, - out_allow = out_allow, out_require = out_require, closed_line = true, - filled_line = true, ) if e1_on_p2 !on_allow && return false @@ -509,83 +519,66 @@ function _polygon_polygon_process( !out_allow && return false out_req_met = true end - !e1_in_p2 && return in_req_met && on_req_met && out_req_met - - # is the part if p1 that is in p2 actually in p2? - - # does p1 touch any other edges or exit p2 at any point? - - # for h2 in GI.gethole(g2) - # # check if h2 is inside of e1 - # e1_in_h2, e1_on_h2, e1_out_h2 = _line_filled_curve_interactions( - # ext1, h2; - # closed_line = true, - # filled_line = true, - # ) - # # skip if poly1 doesn't interact with the hole at all - # !e1_in_h2 && !e1_on_h2 && break - # # if hole interacts with an edge of poly1 - # if e1_on_h2 - # !on_allow && return false - # on_req_met = true - # #= - # we know that h2 touches edge of p1 so: - # (1) no hole of p1 can touch the edge of p1 and - # (2) no other hole of p2 can line up with current h2 - # This means there is at least a small border of p1 that is either - # inside of p2 (e1_out_h2) or outside of p2 (e1_in_h2) - # =# - # if e1_out_h2 - # !in_allow && return false - # in_req_met = true - # end - # if e1_in_h2 - # !out_allow && return false - # out_req_met = true - # # entirety of poly1 is within/on h2 - # !e1_out_h2 && return in_req_met && on_req_met && out_req_met - # end - # else # if hole is completly within poly1 - # !in_allow && return false - # in_req_met = true - # # Check to see if h2 is within a hole of poly1 - # for h1 in GI.gethole(poly1) - # h2_in_h1, h2_on_h1, h2_out_h1 = _line_filled_curve_interactions( - # h2, h1; - # closed_line = true, - # filled_line = true, - # ) - # if !h2_out_h1 - # !out_allow && return false - # out_req_met = true - # else - - # end - # # h2 is outside of h1 and cannot be excluded by another hole since it touches the boundary - # h2_on_h1 && h2_out_h1 && return false - # if !h2_out_h1 #h2 is within bounds of h1, so not in e1 - # h2_in_e1 = false - # break - # end - # end - - # end - - # for h1 in GI.gethole(g1) - # _, h2_on_h1, h2_out_h1 = _line_filled_curve_interactions( - # h2, h1; - # closed_line = true, - # ) - # # h2 is outside of h1 and cannot be excluded by another hole since it touches the boundary - # h2_on_h1 && h2_out_h1 && return false - # if !h2_out_h1 #h2 is within bounds of h1, so not in e1 - # h2_in_e1 = false - # break - # end - # end - # h2_in_e1 && return false - # end - # return true + #= + If exterior of poly1 isn't in poly2 and poly2 isn't within poly1 (checked + with centroid), polygon interiors do not interact and there is nothing left + to check. + =# + c2 = centroid(poly2) + if !e1_in_p2 + _, _, e2_out_e1 = _line_filled_curve_interactions( + ext2, ext1; + closed_line = true, + ) + e2_out_e1 && return in_req_met && on_req_met && out_req_met + end + # If interiors interact, check if poly2 interacts with any of poly1's holes + for h1 in GI.gethole(poly1) + h1_in_p2, h1_on_p2, h1_out_p2 = _line_polygon_interactions( + h1, poly2; + closed_line = true, + ) + if h1_on_p2 + !on_allow && return false + on_req_met = true + end + if h1_out_p2 + !out_allow && return false + out_req_met = true + end + if !h1_in_p2 + _, _, e2_out_h1 = _line_filled_curve_interactions( + ext2, h1; + closed_line = true, + ) + # hole encompasses all of poly2 + !e2_out_h1 && return in_req_met && on_req_met && out_req_met + break + end + end + #= + Poly2 isn't outside of poly1 and isn't in a hole, poly1 interior must + interact with poly2 interior + =# + !in_allow && return false + in_req_met = true + + # If any of poly2 holes are within poly1, if so, poly1 is exterior to poly2 + for h2 in GI.gethole(poly2) + h2_in_p1, h2_on_p1, _ = _line_polygon_interactions( + h2, poly1; + closed_line = true, + ) + if h2_on_p1 + !on_allow && return false + on_req_met = true + end + if h2_in_p1 + !out_allow && return false + out_req_met = true + end + end + return in_req_met && on_req_met && out_req_met end diff --git a/src/methods/geom_relations/within.jl b/src/methods/geom_relations/within.jl index f3ce7cdbe..6cc1433b0 100644 --- a/src/methods/geom_relations/within.jl +++ b/src/methods/geom_relations/within.jl @@ -262,49 +262,58 @@ A polygon is within another polygon if the interior of the first polygon is inside of the second, including edges, and does not intersect with any holes of the second polygon. If these conditions are met, return true, else false. """ -function within( +within( ::GI.PolygonTrait, g1, - ::GI.PolygonTrait, g2; + ::GI.PolygonTrait, g2, +) = _polygon_polygon_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, + in_require = true, on_require = false, out_require = false, ) - ext1 = GI.getexterior(g1) - e1_in_e2, _, e1_out_e2 = _line_filled_curve_interactions( - ext1, GI.getexterior(g2); - closed_line = true, - ) - e1_out_e2 && return false - - for h2 in GI.gethole(g2) - if e1_in_e2 # h2 could be outside of e1, but inside of e2 - h2_in_e1, h2_on_e1, _ = _line_filled_curve_interactions( - h2, ext1; - closed_line = true, - ) - # h2 is inside of e1 and cannot be excluded by a hole since it touches the boundary - h2_on_e1 && h2_in_e1 && return false - if !h2_in_e1 # is h2 disjoint from e1, or is e1 within h2? - c1_val = point_filled_curve_orientation(centroid(ext1), h2) - c1_val == point_in && return false # e1 is within h2 - break # e1 is disjoint from h2 - end - end - # h2 is within e1, but is it within a hole of g1? - h2_in_e1 = true - for h1 in GI.gethole(g1) - _, h2_on_h1, h2_out_h1 = _line_filled_curve_interactions( - h2, h1; - closed_line = true, - ) - # h2 is outside of h1 and cannot be excluded by another hole since it touches the boundary - h2_on_h1 && h2_out_h1 && return false - if !h2_out_h1 #h2 is within bounds of h1, so not in e1 - h2_in_e1 = false - break - end - end - h2_in_e1 && return false - end - return true -end + +# function within( +# ::GI.PolygonTrait, g1, +# ::GI.PolygonTrait, g2; +# ) +# ext1 = GI.getexterior(g1) +# e1_in_e2, _, e1_out_e2 = _line_filled_curve_interactions( +# ext1, GI.getexterior(g2); +# closed_line = true, +# ) +# e1_out_e2 && return false + +# for h2 in GI.gethole(g2) +# if e1_in_e2 # h2 could be outside of e1, but inside of e2 +# h2_in_e1, h2_on_e1, _ = _line_filled_curve_interactions( +# h2, ext1; +# closed_line = true, +# ) +# # h2 is inside of e1 and cannot be excluded by a hole since it touches the boundary +# h2_on_e1 && h2_in_e1 && return false +# if !h2_in_e1 # is h2 disjoint from e1, or is e1 within h2? +# c1_val = point_filled_curve_orientation(centroid(ext1), h2) +# c1_val == point_in && return false # e1 is within h2 +# break # e1 is disjoint from h2 +# end +# end +# # h2 is within e1, but is it within a hole of g1? +# h2_in_e1 = true +# for h1 in GI.gethole(g1) +# _, h2_on_h1, h2_out_h1 = _line_filled_curve_interactions( +# h2, h1; +# closed_line = true, +# ) +# # h2 is outside of h1 and cannot be excluded by another hole since it touches the boundary +# h2_on_h1 && h2_out_h1 && return false +# if !h2_out_h1 #h2 is within bounds of h1, so not in e1 +# h2_in_e1 = false +# break +# end +# end +# h2_in_e1 && return false +# end +# return true +# end # Geometries within multipolygons """ diff --git a/src/try.jl b/src/try.jl index f95ec399c..08e86fd0d 100644 --- a/src/try.jl +++ b/src/try.jl @@ -2,12 +2,6 @@ import GeometryOps as GO import GeoInterface as GI import LibGEOS as LG -p2 = LG.Polygon([ - [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], - [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], - [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] -]) -p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) -p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) -a = GO.within(p3, p2) +p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) +a = GO.within(p1, p1) \ No newline at end of file diff --git a/test/methods/bools.jl b/test/methods/bools.jl index b0d5522fd..21b3a9369 100644 --- a/test/methods/bools.jl +++ b/test/methods/bools.jl @@ -29,13 +29,14 @@ p1 = LG.Polygon(r1, [r2, r3]) @testset "Contains" begin include("geom_relations/contains.jl") end @testset "Covered By" begin include("geom_relations/coveredby.jl") end @testset "Covers" begin include("geom_relations/covers.jl") end -@testset "Crosses" begin include("geom_relations/crosses.jl") end -@testset "Disjoint" begin include("geom_relations/disjoint.jl") end -@testset "Equals" begin include("geom_relations/equals.jl") end -@testset "Touches" begin include("geom_relations/touches.jl") end -@testset "Overlaps" begin include("geom_relations/overlaps.jl") end -@testset "Within" begin include("geom_relations/within.jl") end -@testset "Intersect" begin include("geom_relations/intersects.jl") end +# @testset "Crosses" begin include("geom_relations/crosses.jl") end +# @testset "Disjoint" begin include("geom_relations/disjoint.jl") end +# @testset "Equals" begin include("geom_relations/equals.jl") end +# @testset "Touches" begin include("geom_relations/touches.jl") end +# @testset "Overlaps" begin include("geom_relations/overlaps.jl") end +# @testset "Intersect" begin include("geom_relations/intersects.jl") end +# @testset "Within" begin include("geom_relations/within.jl") end + # @testset "booleans" begin # line1 = GI.LineString([[9.170356, 45.477985], [9.164434, 45.482551], [9.166644, 45.484003]]) diff --git a/test/methods/geom_relations/coveredby.jl b/test/methods/geom_relations/coveredby.jl index 590dd70d3..919958f3d 100644 --- a/test/methods/geom_relations/coveredby.jl +++ b/test/methods/geom_relations/coveredby.jl @@ -58,4 +58,39 @@ # Line segments cross and go over and out -> isn't covered by @test GO.coveredby(l8, l1) == LG.coveredby(l8, l1) == false # Line segments cross and overlap on endpoint -> isn't covered by -@test GO.coveredby(l9, l1) == LG.coveredby(l9, l1) == false \ No newline at end of file +@test GO.coveredby(l9, l1) == LG.coveredby(l9, l1) == false +# Line is within linear ring -> covered by +@test GO.coveredby(l1, r1) == LG.coveredby(l1, r1) == true +# Line covers one edge of linera ring and has segment outside -> isn't covered by +@test GO.coveredby(l3, r1) == LG.coveredby(l3, r1) == false +# Line and linear ring are only connected at vertex -> isn't covered by +@test GO.coveredby(l5, r1) == LG.coveredby(l5, r1) == false +# Line and linear ring are disjoint -> isn't covered by +@test GO.coveredby(l6, r1) == LG.coveredby(l6, r1) == false +# Line crosses through two ring edges -> isn't covered by +@test GO.coveredby(l7, r1) == LG.coveredby(l7, r1) == false +# Line crosses through two ring edges and touches third edge -> isn't covered by +@test GO.coveredby(l8, r1) == LG.coveredby(l8, r1) == false +# Line is equal to linear ring -> covered by +@test GO.coveredby(l10, r1) == LG.coveredby(l10, r1) == true +# Line covers linear ring and then has extra segment -> isn't covered by +@test GO.coveredby(l11, r1) == LG.coveredby(l11, r1) == false + +# # Ring and Geometry + +# Line is within linear ring -> isn't covered by +@test GO.coveredby(r1, l1) == LG.coveredby(r1, l1) == false +# Line covers one edge of linera ring and has segment outside -> isn't covered by +@test GO.coveredby(r1, l3) == LG.coveredby(r1, l3) == false +# Line and linear ring are only connected at vertex -> isn't covered by +@test GO.coveredby(r1, l5) == LG.coveredby(r1, l5) == false +# Line and linear ring are disjoint -> isn't covered by +@test GO.coveredby(r1, l6) == LG.coveredby(r1, l6) == false +# Line crosses through two ring edges -> isn't covered by +@test GO.coveredby(r1, l7) == LG.coveredby(r1, l7) == false +# Line crosses through two ring edges and touches third edge -> isn't covered by +@test GO.coveredby(r1, l8) == LG.coveredby(r1, l8) == false +# Line is equal to linear ring -> covered by +@test GO.coveredby(r1, l10) == LG.coveredby(r1, l10) == true +# Line covers linear ring and then has extra segment -> covered by +@test GO.coveredby(r1, l11) == LG.coveredby(r1, l11) == true diff --git a/test/methods/geom_relations/covers.jl b/test/methods/geom_relations/covers.jl index b0481b3f7..a31da7265 100644 --- a/test/methods/geom_relations/covers.jl +++ b/test/methods/geom_relations/covers.jl @@ -59,6 +59,42 @@ @test GO.covers(l1, l8) == LG.covers(l1, l8) == false # Line segments cross and overlap on endpoint -> doesn't cover @test GO.covers(l1, l9) == LG.covers(l1, l9) == false +# Line is within linear ring -> doesn't cover +@test GO.covers(l1, r1) == LG.covers(l1, r1) == false +# Line covers one edge of linera ring and has segment outside -> doesn't cover +@test GO.covers(l3, r1) == LG.covers(l3, r1) == false +# Line and linear ring are only connected at vertex -> doesn't cover +@test GO.covers(l5, r1) == LG.covers(l5, r1) == false +# Line and linear ring are disjoint -> doesn't cover +@test GO.covers(l6, r1) == LG.covers(l6, r1) == false +# Line crosses through two ring edges -> doesn't cover +@test GO.covers(l7, r1) == LG.covers(l7, r1) == false +# Line crosses through two ring edges and touches third edge -> doesn't cover +@test GO.covers(l8, r1) == LG.covers(l8, r1) == false +# Line is equal to linear ring -> covers +@test GO.covers(l10, r1) == LG.covers(l10, r1) == true +# Line covers linear ring and then has extra segment -> covers +@test GO.covers(l11, r1) == LG.covers(l11, r1) == true + +# # Ring and Geometry + +# Line is within linear ring -> covers +@test GO.covers(r1, l1) == LG.covers(r1, l1) == true +# Line covers one edge of linear ring and has segment outside -> doesn't cover +@test GO.covers(r1, l3) == LG.covers(r1, l3) == false +# Line and linear ring are only connected at vertex -> doesn't cover +@test GO.covers(r1, l5) == LG.covers(r1, l5) == false +# Line and linear ring are disjoint -> doesn't cover +@test GO.covers(r1, l6) == LG.covers(r1, l6) == false +# Line crosses through two ring edges -> doesn't cover +@test GO.covers(r1, l7) == LG.covers(r1, l7) == false +# Line crosses through two ring edges and touches third edge -> doesn't cover +@test GO.covers(r1, l8) == LG.covers(r1, l8) == false +# Line is equal to linear ring -> cover +@test GO.covers(r1, l10) == LG.covers(r1, l10) == true +# Line covers linear ring and then has extra segment -> doesn't cover +@test GO.covers(r1, l11) == LG.covers(r1, l11) == false + diff --git a/test/methods/geom_relations/within.jl b/test/methods/geom_relations/within.jl index 06b0e3003..2deb1ed3c 100644 --- a/test/methods/geom_relations/within.jl +++ b/test/methods/geom_relations/within.jl @@ -60,247 +60,247 @@ @test GO.within(l9, l1) == LG.within(l9, l1) == false # # Test points -# pt1 = LG.Point([0.0, 0.0]) -# pt2 = LG.Point([0.0, 0.1]) -# pt3 = LG.Point([1.0, 0.0]) -# pt4 = LG.Point([0.5, 1.0]) -# pt5 = LG.Point([0.2, 0.5]) -# pt6 = LG.Point([0.3, 0.55]) -# pt7 = LG.Point([0.6, 0.49]) -# pt8 = LG.Point([0.25, 0.75]) -# pt9 = LG.Point([0.5, 0.1]) -# # Test lines -# l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) -# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) -# l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) -# l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) -# l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) -# l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) -# l7 = LG.LineString([[0.6, 0.6], [0.8, 0.6]]) -# l8 = LG.LineString([[0.3, 0.55], [0.3, 0.65]]) -# l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) -# l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) -# l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) -# l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) -# l13 = LG.LineString([[0.5, 0.01], [0.5, 0.09]]) -# # Test rings -# r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) -# r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) -# r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) -# r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) -# r5 = LG.LinearRing([[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]) -# r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) -# r7 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.5, 0.3], [0.0, 0.3],[0.0, 0.0]]) -# # Test polygons -# p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) -# p2 = LG.Polygon([ -# [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], -# [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], -# [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] -# ]) -# p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) -# p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) -# p5 = LG.Polygon([[[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]]) -# p6 = LG.Polygon([[[0.1, 0.4], [0.1, 0.8], [0.3, 0.8], [0.3, 0.4], [0.1, 0.4]]]) -# p7 = LG.Polygon([ -# [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], -# [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]], -# ]) -# p8 = LG.Polygon([ -# [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], -# [[0.3, 0.3], [0.3, 0.7], [0.7, 0.7], [0.7, 0.3], [0.3, 0.3]] -# ]) -# p9 = LG.Polygon([ -# [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], -# [[0.45, 0.45], [0.45, 0.55], [0.55, 0.55], [0.55, 0.45], [0.45, 0.45]] -# ]) -# p10 = LG.Polygon([ -# [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], -# [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] -# ]) -# p11 = LG.Polygon([ -# [[0.45, 0.5], [0.45, 0.75], [0.55, 0.75], [0.55, 0.5], [0.45, 0.5]] -# ]) -# p12 = LG.Polygon([ -# [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] -# ]) -# # Test multipolygons -# m1 = LG.MultiPolygon([p3, p6]) -# m2 = LG.MultiPolygon([p3, p4]) +pt1 = LG.Point([0.0, 0.0]) +pt2 = LG.Point([0.0, 0.1]) +pt3 = LG.Point([1.0, 0.0]) +pt4 = LG.Point([0.5, 1.0]) +pt5 = LG.Point([0.2, 0.5]) +pt6 = LG.Point([0.3, 0.55]) +pt7 = LG.Point([0.6, 0.49]) +pt8 = LG.Point([0.25, 0.75]) +pt9 = LG.Point([0.5, 0.1]) +# Test lines +l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) +l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) +l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) +l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) +l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) +l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) +l7 = LG.LineString([[0.6, 0.6], [0.8, 0.6]]) +l8 = LG.LineString([[0.3, 0.55], [0.3, 0.65]]) +l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) +l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) +l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) +l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) +l13 = LG.LineString([[0.5, 0.01], [0.5, 0.09]]) +# Test rings +r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) +r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) +r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) +r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) +r5 = LG.LinearRing([[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]) +r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) +r7 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.5, 0.3], [0.0, 0.3],[0.0, 0.0]]) +# Test polygons +p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) +p2 = LG.Polygon([ + [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], + [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], + [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] +]) +p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) +p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) +p5 = LG.Polygon([[[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]]) +p6 = LG.Polygon([[[0.1, 0.4], [0.1, 0.8], [0.3, 0.8], [0.3, 0.4], [0.1, 0.4]]]) +p7 = LG.Polygon([ + [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], + [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]], +]) +p8 = LG.Polygon([ + [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], + [[0.3, 0.3], [0.3, 0.7], [0.7, 0.7], [0.7, 0.3], [0.3, 0.3]] +]) +p9 = LG.Polygon([ + [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], + [[0.45, 0.45], [0.45, 0.55], [0.55, 0.55], [0.55, 0.45], [0.45, 0.45]] +]) +p10 = LG.Polygon([ + [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], + [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] +]) +p11 = LG.Polygon([ + [[0.45, 0.5], [0.45, 0.75], [0.55, 0.75], [0.55, 0.5], [0.45, 0.5]] +]) +p12 = LG.Polygon([ + [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] +]) +# Test multipolygons +m1 = LG.MultiPolygon([p3, p6]) +m2 = LG.MultiPolygon([p3, p4]) -# # # Point and point -# # Equal points -> within -# @test GO.within(pt1, pt1) == LG.within(pt1, pt1) -# # Different points -> not within -# @test GO.within(pt1, pt2) == LG.within(pt1, pt2) +# # Point and point +# Equal points -> within +@test GO.within(pt1, pt1) == LG.within(pt1, pt1) +# Different points -> not within +@test GO.within(pt1, pt2) == LG.within(pt1, pt2) -# # # Point and line -# # Line endpoint (1 segment) -> not within -# @test GO.within(pt1, l1) == LG.within(pt1, l1) -# # Line endpoint (2 segments) -> not within -# @test GO.within(pt2, l2) == LG.within(pt2, l2) -# # Middle of line (1 segment) -> within -# @test GO.within(pt2, l1) == LG.within(pt2, l1) -# # Not on line (1 segment) -> not within -# @test GO.within(pt3, l1) == LG.within(pt3, l1) -# # Middle of line on joint (2 segments) -> within -# @test GO.within(pt3, l2) == LG.within(pt3, l2) -# # Endpoint on closed line -> within -# @test GO.within(pt1, l6) == LG.within(pt1, l6) +# # Point and line +# Line endpoint (1 segment) -> not within +@test GO.within(pt1, l1) == LG.within(pt1, l1) +# Line endpoint (2 segments) -> not within +@test GO.within(pt2, l2) == LG.within(pt2, l2) +# Middle of line (1 segment) -> within +@test GO.within(pt2, l1) == LG.within(pt2, l1) +# Not on line (1 segment) -> not within +@test GO.within(pt3, l1) == LG.within(pt3, l1) +# Middle of line on joint (2 segments) -> within +@test GO.within(pt3, l2) == LG.within(pt3, l2) +# Endpoint on closed line -> within +@test GO.within(pt1, l6) == LG.within(pt1, l6) -# # # Point and Ring -# # On ring corner -> within -# @test GO.within(pt1, r1) == LG.within(pt1, r1) -# # Outside of ring -> not within -# @test GO.within(pt2, r1) == LG.within(pt2, r1) -# # Inside of ring center (not on line) -> not within -# @test GO.within(pt3, r1) == LG.within(pt3, r1) -# # On ring edge -> within -# @test GO.within(pt8, r1) == LG.within(pt8, r1) +# # Point and Ring +# On ring corner -> within +@test GO.within(pt1, r1) == LG.within(pt1, r1) +# Outside of ring -> not within +@test GO.within(pt2, r1) == LG.within(pt2, r1) +# Inside of ring center (not on line) -> not within +@test GO.within(pt3, r1) == LG.within(pt3, r1) +# On ring edge -> within +@test GO.within(pt8, r1) == LG.within(pt8, r1) -# # # Point and polygon -# # On polygon vertex -> not within -# @test GO.within(pt1, p1) == LG.within(pt1, p1) -# # Outside of polygon -> not within -# @test GO.within(pt2, p1) == LG.within(pt2, p1) -# # Inside of polygon -> within -# @test GO.within(pt3, p1) == LG.within(pt3, p1) -# # On polygon vertex (with holes) -> not within -# @test GO.within(pt1, p2) == LG.within(pt1, p2) -# # On polygon edge (with holes) -> not within -# @test GO.within(pt2, p2) == LG.within(pt2, p2) -# # On hole vertex -> not within -# @test GO.within(pt5, p2) == LG.within(pt5, p2) -# # Within hole -> not within -# @test GO.within(pt6, p2) == LG.within(pt6, p2) -# # Inside of polygon (with holes) -> within -# @test GO.within(pt7, p2) == LG.within(pt7, p2) +# # Point and polygon +# On polygon vertex -> not within +@test GO.within(pt1, p1) == LG.within(pt1, p1) +# Outside of polygon -> not within +@test GO.within(pt2, p1) == LG.within(pt2, p1) +# Inside of polygon -> within +@test GO.within(pt3, p1) == LG.within(pt3, p1) +# On polygon vertex (with holes) -> not within +@test GO.within(pt1, p2) == LG.within(pt1, p2) +# On polygon edge (with holes) -> not within +@test GO.within(pt2, p2) == LG.within(pt2, p2) +# On hole vertex -> not within +@test GO.within(pt5, p2) == LG.within(pt5, p2) +# Within hole -> not within +@test GO.within(pt6, p2) == LG.within(pt6, p2) +# Inside of polygon (with holes) -> within +@test GO.within(pt7, p2) == LG.within(pt7, p2) -# # # Line and line -# # Equal lines -> within -# @test GO.within(l1, l1) == LG.within(l1, l1) -# # Lines share 2 endpoints, but don't overlap -> not within -# @test GO.within(l1, l2) == LG.within(l1, l2) -# # Lines overlap, but neither is within other -> not within -# @test GO.within(l1, l3) == LG.within(l1, l3) -# # Within line (no shared endpoints) -> within -# @test GO.within(l1, l4) == LG.within(l1, l4) -# # Line shares just one endpoint -> not within -# @test GO.within(l1, l5) == LG.within(l1, l5) +# # Line and line +# Equal lines -> within +@test GO.within(l1, l1) == LG.within(l1, l1) +# Lines share 2 endpoints, but don't overlap -> not within +@test GO.within(l1, l2) == LG.within(l1, l2) +# Lines overlap, but neither is within other -> not within +@test GO.within(l1, l3) == LG.within(l1, l3) +# Within line (no shared endpoints) -> within +@test GO.within(l1, l4) == LG.within(l1, l4) +# Line shares just one endpoint -> not within +@test GO.within(l1, l5) == LG.within(l1, l5) -# # # Line and ring -# # Shares all endpoints -> within -# @test GO.within(l6, r1) == LG.within(l6, r1) -# # Shares all endpoints, but ring has extra edge -> within -# @test GO.within(l2, r2) == LG.within(l2, r2) -# # Doesn't share all edges -> not within -# @test GO.within(l2, r3) == LG.within(l2, r3) -# # Shares all endpoints, but adds one extra segment -> not within -# @test GO.within(l12, r1) == LG.within(l12, r1) +# # Line and ring +# Shares all endpoints -> within +@test GO.within(l6, r1) == LG.within(l6, r1) +# Shares all endpoints, but ring has extra edge -> within +@test GO.within(l2, r2) == LG.within(l2, r2) +# Doesn't share all edges -> not within +@test GO.within(l2, r3) == LG.within(l2, r3) +# Shares all endpoints, but adds one extra segment -> not within +@test GO.within(l12, r1) == LG.within(l12, r1) -# # Line and polygon -# # Line traces entire outline of polygon edges -> not within -# @test GO.within(l6, p1) == LG.within(l6, p1) -# # Line is edge of polygon -> not within -# @test GO.within(l1, p2) == LG.within(l1, p2) -# # Line is on edge + inside of polygon -> within -# @test GO.within(l2, p2) == LG.within(l2, p2) -# # Line goes outside of polygon -> not within -# @test GO.within(l3, p2) == LG.within(l3, p2) -# # Line is fully within polygon -> within -# @test GO.within(l7, p2) == LG.within(l7, p2) -# # Line is fully within hole -> not within -# @test GO.within(l8, p2) == LG.within(l8, p2) -# # Line is on hole edge -> not within -# @test GO.within(l9, p2) == LG.within(l9, p2) -# # Line on polygon edge and then enters polygon to end on hole vertex -> within -# @test GO.within(l10, p2) == LG.within(l10, p2) -# # Line is on polygon edge and then cuts through hole -> not within -# @test GO.within(l11, p2) == LG.within(l11, p2) +# Line and polygon +# Line traces entire outline of polygon edges -> not within +@test GO.within(l6, p1) == LG.within(l6, p1) +# Line is edge of polygon -> not within +@test GO.within(l1, p2) == LG.within(l1, p2) +# Line is on edge + inside of polygon -> within +@test GO.within(l2, p2) == LG.within(l2, p2) +# Line goes outside of polygon -> not within +@test GO.within(l3, p2) == LG.within(l3, p2) +# Line is fully within polygon -> within +@test GO.within(l7, p2) == LG.within(l7, p2) +# Line is fully within hole -> not within +@test GO.within(l8, p2) == LG.within(l8, p2) +# Line is on hole edge -> not within +@test GO.within(l9, p2) == LG.within(l9, p2) +# Line on polygon edge and then enters polygon to end on hole vertex -> within +@test GO.within(l10, p2) == LG.within(l10, p2) +# Line is on polygon edge and then cuts through hole -> not within +@test GO.within(l11, p2) == LG.within(l11, p2) -# # # Ring and line -# # Shares all endpoints -> within -# @test GO.within(r1, l6) == LG.within(r1, l6) -# # Shares all endpoints but ring has closing edge -> not within -# @test GO.within(r2, l2) == LG.within(r2, l2) -# # Doesn't share all edges -> not within -# @test GO.within(r3, l2) == LG.within(r3, l2) -# # Shares all endpoints, but line has one extra segment -> within -# @test GO.within(r1, l12) == LG.within(r1, l12) +# # Ring and line +# Shares all endpoints -> within +@test GO.within(r1, l6) == LG.within(r1, l6) +# Shares all endpoints but ring has closing edge -> not within +@test GO.within(r2, l2) == LG.within(r2, l2) +# Doesn't share all edges -> not within +@test GO.within(r3, l2) == LG.within(r3, l2) +# Shares all endpoints, but line has one extra segment -> within +@test GO.within(r1, l12) == LG.within(r1, l12) -# # # Ring and ring -# # Equal ring -> within -# @test GO.within(r1, r1) == LG.within(r1, r1) -# # Not equal ring -> not within -# @test GO.within(r1, r2) == LG.within(r1, r2) -# # Not equal ring -> not within -# @test GO.within(r1, r3) == LG.within(r1, r3) -# # Rings share all edges, but second ring has extra edges -> within -# @test GO.within(r2, r7) == LG.within(r2, r7) +# # Ring and ring +# Equal ring -> within +@test GO.within(r1, r1) == LG.within(r1, r1) +# Not equal ring -> not within +@test GO.within(r1, r2) == LG.within(r1, r2) +# Not equal ring -> not within +@test GO.within(r1, r3) == LG.within(r1, r3) +# Rings share all edges, but second ring has extra edges -> within +@test GO.within(r2, r7) == LG.within(r2, r7) -# # # Ring and polygon -# # Ring is equal to polygon's external ring, no holes -> not within -# @test GO.within(r1, p1) == LG.within(r1, p1) -# # Ring goes outside of polygon's external ring -> not within -# @test GO.within(r1, p2) == LG.within(r1, p1) -# # Ring is within polygon, but also on edges -> within -# @test GO.within(r2, p2) == LG.within(r2, p2) -# # Ring is within polygon, but also on edges -> within -# @test GO.within(r3, p2) == LG.within(r3, p2) -# # Ring is one of polygon's holes -> not within -# @test GO.within(r4, p2) == LG.within(r4, p2) -# # Ring is fully within polygon that has holes -> within -# @test GO.within(r5, p2) == LG.within(r5, p2) -# # Ring is fully within polygon's hole -> not within -# @test GO.within(r6, p2) == LG.within(r6, p2) +# # Ring and polygon +# Ring is equal to polygon's external ring, no holes -> not within +@test GO.within(r1, p1) == LG.within(r1, p1) +# Ring goes outside of polygon's external ring -> not within +@test GO.within(r1, p2) == LG.within(r1, p1) +# Ring is within polygon, but also on edges -> within +@test GO.within(r2, p2) == LG.within(r2, p2) +# Ring is within polygon, but also on edges -> within +@test GO.within(r3, p2) == LG.within(r3, p2) +# Ring is one of polygon's holes -> not within +@test GO.within(r4, p2) == LG.within(r4, p2) +# Ring is fully within polygon that has holes -> within +@test GO.within(r5, p2) == LG.within(r5, p2) +# Ring is fully within polygon's hole -> not within +@test GO.within(r6, p2) == LG.within(r6, p2) -# # # Polygon in polygon -# # Same polygon -> within -# @test GO.within(p1, p1) == LG.within(p1, p1) -# @test GO.within(p2, p2) == LG.within(p2, p2) -# # Polygon not in polygon -> not within -# @test GO.within(p1, p2) == LG.within(p1, p2) -# @test GO.within(p2, p1) == LG.within(p2, p1) -# # Polygon is within polygon, but also on edges -> within -# @test GO.within(p3, p2) == LG.within(p3, p2) -# # Polygon within polygon with holes -> within -# @test GO.within(p4, p2) == LG.within(p4, p2) -# # Polygon within polygon hole --> not within -# @test GO.within(p5, p2) == LG.within(p5, p2) -# # Polygon overlapping with other polygon's hole -> not within -# @test GO.within(p6, p2) == LG.within(p6, p2) -# # Polygon with hole nested with other polygon's hole --> within -# @test GO.within(p8, p7) == LG.within(p8, p7) -# # Nested holes but not within -> not within -# @test GO.within(p9, p7) == LG.within(p9, p7) -# # Nested with same hole -> within -# @test GO.within(p10, p7) == LG.within(p10, p7) -# # within external ring but intersects with hole -> not within -# @test GO.within(p11, p7) == LG.within(p11, p7) -# # polygon extactly overlaps with other polygon's hole -> not within -# @test GO.within(p12, p7) == LG.within(p12, p7) +# # Polygon in polygon +# Same polygon -> within +@test GO.within(p1, p1) == LG.within(p1, p1) +@test GO.within(p2, p2) == LG.within(p2, p2) +# Polygon not in polygon -> not within +@test GO.within(p1, p2) == LG.within(p1, p2) +@test GO.within(p2, p1) == LG.within(p2, p1) +# Polygon is within polygon, but also on edges -> within +@test GO.within(p3, p2) == LG.within(p3, p2) +# Polygon within polygon with holes -> within +@test GO.within(p4, p2) == LG.within(p4, p2) +# Polygon within polygon hole --> not within +@test GO.within(p5, p2) == LG.within(p5, p2) +# Polygon overlapping with other polygon's hole -> not within +@test GO.within(p6, p2) == LG.within(p6, p2) +# Polygon with hole nested with other polygon's hole --> within +@test GO.within(p8, p7) == LG.within(p8, p7) +# Nested holes but not within -> not within +@test GO.within(p9, p7) == LG.within(p9, p7) +# Nested with same hole -> within +@test GO.within(p10, p7) == LG.within(p10, p7) +# within external ring but intersects with hole -> not within +@test GO.within(p11, p7) == LG.within(p11, p7) +# polygon extactly overlaps with other polygon's hole -> not within +@test GO.within(p12, p7) == LG.within(p12, p7) -# # # Multipolygon tests -# # Point in multipolygon -# @test GO.within(pt5, m1) == LG.within(pt5, m1) -# @test GO.within(pt9, m1) == LG.within(pt9, m1) -# # Point outside of multipolygon -# @test GO.within(pt4, m1) == LG.within(pt4, m1) -# # Line in multipolygon -# @test GO.within(l13, m1) == LG.within(l13, m1) -# @test GO.within(l9, m1) == LG.within(l9, m1) -# # Line outside of multipolygon -# @test GO.within(l1, m1) == LG.within(l1, m1) -# # Ring in multipolygon -# @test GO.within(r1, m2) == LG.within(r1, m2) -# # Ring outside of multipolygon -# @test GO.within(r1, m1) == LG.within(r1, m1) -# # Polygon in multipolygon -# @test GO.within(p3, m1) == LG.within(p3, m1) -# @test GO.within(p6, m1) == LG.within(p6, m1) -# # Polygon outside of multipolygon -# @test GO.within(p1, m1) == LG.within(p1, m1) -# # Multipolygon in multipolygon -# @test GO.within(m1, m1) == LG.within(m1, m1) -# # Multipolygon outside of multipolygon -# @test GO.within(m2, m1) == LG.within(m2, m1) +# # Multipolygon tests +# Point in multipolygon +@test GO.within(pt5, m1) == LG.within(pt5, m1) +@test GO.within(pt9, m1) == LG.within(pt9, m1) +# Point outside of multipolygon +@test GO.within(pt4, m1) == LG.within(pt4, m1) +# Line in multipolygon +@test GO.within(l13, m1) == LG.within(l13, m1) +@test GO.within(l9, m1) == LG.within(l9, m1) +# Line outside of multipolygon +@test GO.within(l1, m1) == LG.within(l1, m1) +# Ring in multipolygon +@test GO.within(r1, m2) == LG.within(r1, m2) +# Ring outside of multipolygon +@test GO.within(r1, m1) == LG.within(r1, m1) +# Polygon in multipolygon +@test GO.within(p3, m1) == LG.within(p3, m1) +@test GO.within(p6, m1) == LG.within(p6, m1) +# Polygon outside of multipolygon +@test GO.within(p1, m1) == LG.within(p1, m1) +# Multipolygon in multipolygon +@test GO.within(m1, m1) == LG.within(m1, m1) +# Multipolygon outside of multipolygon +@test GO.within(m2, m1) == LG.within(m2, m1) From d9d636a2d928aed2d355e2c2f6cbc283fe1874e5 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 26 Dec 2023 13:23:24 -0800 Subject: [PATCH 25/33] Update crosses algorithm --- .gitignore | 1 + Project.toml | 1 - src/methods/geom_relations/crosses.jl | 131 +++++++++++++++++++++++--- src/try.jl | 8 +- test/methods/bools.jl | 2 +- 5 files changed, 124 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index d6b4b1c05..0e58cf59b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /Manifest.toml /docs/build/ +.vscode/ diff --git a/Project.toml b/Project.toml index f6787b264..35b23ebe6 100644 --- a/Project.toml +++ b/Project.toml @@ -12,7 +12,6 @@ Proj = "c94c279d-25a6-4763-9509-64d165bea63e" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] -GeometryBasics = "0.4.7" Proj = "1" julia = "1.3" diff --git a/src/methods/geom_relations/crosses.jl b/src/methods/geom_relations/crosses.jl index 1c318ccc3..080387a59 100644 --- a/src/methods/geom_relations/crosses.jl +++ b/src/methods/geom_relations/crosses.jl @@ -77,24 +77,127 @@ A line string is crosses another linestring if the vertices and edges of the first linestring are crosses the second linestring, including the first and last vertex. Return true if those conditions are met, else false. """ -# crosses( -# ::GI.LineStringTrait, g1, -# ::GI.LineStringTrait, g2, -# ) = _line_curve_process( -# g1, g2; -# in_allow = true, on_allow = false, out_allow = true, -# in_require = true, on_require = false, out_require = true, -# closed_line = false, -# closed_curve = false, -# ) +crosses( + ::GI.LineStringTrait, g1, + ::GI.LineStringTrait, g2, +) = _line_curve_crosses_overlap_process( + g1, g2; + orientation = line_cross, + closed_line = false, closed_curve = false, + ) -function crosses( - ::LineStringTrait, g1, - ::LineStringTrait, g2 -) +crosses( + ::GI.LineStringTrait, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_crosses_overlap_process( + g1, g2; + orientation = line_cross, + closed_line = false, closed_curve = true, + ) +crosses( + ::GI.LinearRingTrait, g1, + ::GI.LineStringTrait, g2, +) = _line_curve_crosses_overlap_process( + g1, g2; + orientation = line_cross, + closed_line = true, closed_curve = false, + ) +crosses( + ::GI.LinearRingTrait, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_crosses_overlap_process( + g1, g2; + orientation = line_cross, + closed_line = true, closed_curve = true, +) +function _line_curve_crosses_overlap_process( + line, curve; + orientation = line_cross, + closed_line = false, closed_curve = false, +) + nl = GI.npoint(line) + nc = GI.npoint(curve) + first_last_equal_line = equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) + first_last_equal_curve = equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) + nl -= first_last_equal_line ? 1 : 0 + nc -= first_last_equal_curve ? 1 : 0 + closed_line |= first_last_equal_line + closed_curve |= first_last_equal_curve + + # Loop over each line segment + orientation_req_met = false + l_start = GI.getpoint(line, closed_line ? nl : 1) + for i in (closed_line ? 1 : 2):nl + l_end = GI.getpoint(line, i) + c_start = GI.getpoint(curve, closed_curve ? nc : 1) + for j in (closed_curve ? 1 : 2):nc + c_end = GI.getpoint(curve, j) + seg_val = _segment_segment_orientation( + (l_start, l_end), + (c_start, c_end), + ) + @show seg_val + if seg_val == line_over + return false + elseif seg_val == line_cross + orientation_req_met = true + elseif seg_val == line_hinge && !orientation_req_met + _, fracs = _intersection_point( + (_tuple_point(l_start), _tuple_point(l_end)), + (_tuple_point(c_start), _tuple_point(c_end)) + ) + if isnothing(fracs) # line and curve segments are parallel + + else + (α, β) = fracs # 0 ≤ α ≤ 1 and 0 ≤ β ≤ 1 since hinges + β == 0 && break # if crosses, found on previous segment + # curve intersects through line endpoint + if !closed_line && ( + (i == 2 && α == 0) || (i == nl && α == 1) + ) + break # doesn't cross + # curve segment intersects through line vertex or edge + elseif 0 < β < 1 + orientation_req_met = true # crosses + # curve meets line at curve segment endpoint + else # β == 1 + # no curve segment connecting at intersection point + !closed_curve && j == nc && break + # see if next curve segment is on the other side of line + c_next = GI.getpoint(curve, j < nc ? j + 1 : 1) + Δx = GI.x(l_end) - GI.x(l_start) + Δy = GI.y(l_end) - GI.y(l_start) + if Δx == 0 + x = GI.x(l_start) + x_next = GI.x(c_next) + x_next == x && break # next curve segment is on line + x_next < x && GI.x(c_start) < x && break + x_next > x && GI.x(c_start) > x && break + orientation_req_met = true + elseif Δy == 0 + y = GI.y(l_start) + y_next = GI.y(c_next) + y_next == y && break # next curve segment is on line + y_next < y && GI.y(c_start) < y && break + y_next > y && GI.y(c_start) > y && break + orientation_req_met = true + else + m = Δy / Δx + b = GI.y(c_start) - m * GI.x(c_start) + Δy_start = (m * GI.x(c_start) + b) - GI.y(c_start) + Δy_next = (m * GI.x(c_next) + b) - GI.y(c_next) + Δy_start * Δy_next > 0 && break + orientation_req_met = true + end + + end + end + end + end + return orientation_req_met end """ diff --git a/src/try.jl b/src/try.jl index 08e86fd0d..26674b483 100644 --- a/src/try.jl +++ b/src/try.jl @@ -1,7 +1,9 @@ import GeometryOps as GO -import GeoInterface as GI +#import GeoInterface as GI import LibGEOS as LG -p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) -a = GO.within(p1, p1) +l1 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]) +l9 = LG.LineString([[0.0, 1.0], [0.0, -1.0], [1.0, 1.0]]) + +GO.crosses(l1, l9) \ No newline at end of file diff --git a/test/methods/bools.jl b/test/methods/bools.jl index 21b3a9369..32979349d 100644 --- a/test/methods/bools.jl +++ b/test/methods/bools.jl @@ -29,7 +29,7 @@ p1 = LG.Polygon(r1, [r2, r3]) @testset "Contains" begin include("geom_relations/contains.jl") end @testset "Covered By" begin include("geom_relations/coveredby.jl") end @testset "Covers" begin include("geom_relations/covers.jl") end -# @testset "Crosses" begin include("geom_relations/crosses.jl") end +@testset "Crosses" begin include("geom_relations/crosses.jl") end # @testset "Disjoint" begin include("geom_relations/disjoint.jl") end # @testset "Equals" begin include("geom_relations/equals.jl") end # @testset "Touches" begin include("geom_relations/touches.jl") end From ff4b9e54c345534d3691cfb2784189a556cf58f8 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 26 Dec 2023 17:47:12 -0800 Subject: [PATCH 26/33] New crosses code --- src/methods/geom_relations/crosses.jl | 108 +++++++++++++++++--------- 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/src/methods/geom_relations/crosses.jl b/src/methods/geom_relations/crosses.jl index 080387a59..73f7a5c04 100644 --- a/src/methods/geom_relations/crosses.jl +++ b/src/methods/geom_relations/crosses.jl @@ -126,7 +126,6 @@ function _line_curve_crosses_overlap_process( nc -= first_last_equal_curve ? 1 : 0 closed_line |= first_last_equal_line closed_curve |= first_last_equal_curve - # Loop over each line segment orientation_req_met = false l_start = GI.getpoint(line, closed_line ? nl : 1) @@ -139,7 +138,6 @@ function _line_curve_crosses_overlap_process( (l_start, l_end), (c_start, c_end), ) - @show seg_val if seg_val == line_over return false elseif seg_val == line_cross @@ -149,53 +147,91 @@ function _line_curve_crosses_overlap_process( (_tuple_point(l_start), _tuple_point(l_end)), (_tuple_point(c_start), _tuple_point(c_end)) ) - if isnothing(fracs) # line and curve segments are parallel - - else - (α, β) = fracs # 0 ≤ α ≤ 1 and 0 ≤ β ≤ 1 since hinges - β == 0 && break # if crosses, found on previous segment - # curve intersects through line endpoint - if !closed_line && ( - (i == 2 && α == 0) || (i == nl && α == 1) - ) - break # doesn't cross - # curve segment intersects through line vertex or edge - elseif 0 < β < 1 - orientation_req_met = true # crosses - # curve meets line at curve segment endpoint - else # β == 1 - # no curve segment connecting at intersection point - !closed_curve && j == nc && break - # see if next curve segment is on the other side of line + if !isnothing(fracs) + (α, β) = fracs # 0 ≤ α ≤ 1 and 0 ≤ β ≤ 1 since hinge + if β == 0 # already checked on previous segment + c_start = c_end + continue + elseif α == 0 + if !closed_line && i == 2 + c_start = c_end + continue + end + elseif α == 1 + if !closed_line && i == nl + c_start = c_end + continue + end + else # 0 < α < 1, β = 1 (if 0 < β < 1 then seg_val = cross) + if !closed_curve && j == nc + c_start = c_end + continue + end c_next = GI.getpoint(curve, j < nc ? j + 1 : 1) + x_start, y_start = GI.x(c_start), GI.y(c_start) + x_next, y_next = GI.x(c_next), GI.y(c_next) Δx = GI.x(l_end) - GI.x(l_start) Δy = GI.y(l_end) - GI.y(l_start) if Δx == 0 x = GI.x(l_start) - x_next = GI.x(c_next) - x_next == x && break # next curve segment is on line - x_next < x && GI.x(c_start) < x && break - x_next > x && GI.x(c_start) > x && break - orientation_req_met = true + if (x_next - x) * (x_start - x) ≥ 0 + c_start = c_end + continue + end elseif Δy == 0 y = GI.y(l_start) - y_next = GI.y(c_next) - y_next == y && break # next curve segment is on line - y_next < y && GI.y(c_start) < y && break - y_next > y && GI.y(c_start) > y && break - orientation_req_met = true + if (y_next - y) * (y_start - y) ≥ 0 + c_start = c_end + continue + end else m = Δy / Δx - b = GI.y(c_start) - m * GI.x(c_start) - Δy_start = (m * GI.x(c_start) + b) - GI.y(c_start) - Δy_next = (m * GI.x(c_next) + b) - GI.y(c_next) - Δy_start * Δy_next > 0 && break - orientation_req_met = true + b = GI.y(l_start) - m * GI.x(l_start) + Δy_start = (m * x_start + b) - y_start + Δy_next = (m * x_next + b) - y_next + if Δy_start * Δy_next ≥ 0 + c_start = c_end + continue + end end - + orientation_req_met = true + continue + end + end + T = typeof(GI.x(l_start)) + (α, β) = # α = 0 or α = 1 + if equals(l_start, c_start) + (zero(T), zero(T)) + elseif equals(l_start, c_end) + (zero(T), one(T)) + elseif equals(l_end, c_start) + (one(T), zero(T)) + elseif equals(l_end, c_end) + (one(T), one(T)) + else + fracs end + if β == 0 + c_start = c_end + continue + end + l1, l2, l3 = α == 0 ? + (GI.getpoint(line, i > 2 ? (i - 2) : nl), l_start, l_end) : + (l_start, l_end, GI.getpoint(line, i < nl ? (i + 1) : 1)) + θ1 = atan(GI.y(l1) - GI.y(l2), GI.x(l1) - GI.x(l2)) + θ2 = atan(GI.y(l3) - GI.y(l2), GI.x(l3) - GI.x(l2)) + θ1, θ2 = θ1 < θ2 ? (θ1, θ2) : (θ2, θ1) + + c_next = β == 1 ? + GI.getpoint(curve, j < nc ? j + 1 : 1) : + c_end + ϕ1 = atan(GI.y(c_start) - GI.y(l2), GI.x(c_start) - GI.x(l2)) + ϕ2 = atan(GI.y(c_next) - GI.y(l2), GI.x(c_next) - GI.x(l2)) + orientation_req_met = ((θ1 < ϕ1 < θ2) ⊻ (θ1 < ϕ2 < θ2)) end + c_start = c_end end + l_start = l_end end return orientation_req_met end From e456796470800c0e83db2ad75cf09fb3127562cf Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Wed, 27 Dec 2023 17:05:08 -0800 Subject: [PATCH 27/33] Add crosses tests --- test/methods/geom_relations/crosses.jl | 49 +++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/test/methods/geom_relations/crosses.jl b/test/methods/geom_relations/crosses.jl index fa938ef7f..cb4d02d5d 100644 --- a/test/methods/geom_relations/crosses.jl +++ b/test/methods/geom_relations/crosses.jl @@ -36,4 +36,51 @@ # Line segments cross and go over and out -> doesn't cross @test GO.crosses(l1, l8) == LG.crosses(l1, l8) == false # Line segments cross and overlap on endpoint -> crosses -@test GO.crosses(l1, l9) == LG.crosses(l1, l9) == true \ No newline at end of file +@test GO.crosses(l1, l9) == LG.crosses(l1, l9) == true + +#= +Extra tests since this uses different code due to curve-curve crosses definition +requiring meeting in a point and having lines cross one another +=# +h = LG.LineString([[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]) +v1 = LG.LineString([[0.0, 1.0], [0.0, 0.0], [0.0, -1.0]]) +v2 = LG.LineString([[2.0, 1.0], [2.0, 0.0], [2.0, -1.0]]) +v3 = LG.LineString([[1.0, 1.0], [1.0, 0.0], [1.0, -1.0]]) +v4 = LG.LineString([[1.5, 1.0], [1.5, 0.0], [1.5, -1.0]]) +v5 = LG.LineString([[1.5, 1.0], [1.5, -1.0]]) +d1 = LG.LineString([[-1.0, -1.0], [1.0, 1.0]]) +d2 = LG.LineString([[-1.0, 1.0], [0.0, 0.0], [1.0, -1.0]]) +b1 = LG.LineString([[0.0, 1.0], [1.5, 0.0], [0.0, -1.0]]) +b2 = LG.LineString([[1.0, 1.0], [1.5, 0.0], [2.0, 1.0]]) +b3 = LG.LineString([[0.0, 1.0], [1.0, 0.0], [2.0, 1.0]]) +b4 = LG.LineString([[1.0, -0.5], [0.0, 0.0], [1.0, 0.0]]) +b5 = LG.LineString([[-1.0, 0.0], [0.0, 0.0], [1.0, 1.0]]) +b6 = LG.LineString([[-1.0, 0.0], [0.0, 0.0], [1.0, -1.0]]) +b7 = LG.LineString([[1.0, 0.0], [0.0, 0.0], [-1.0, -1.0]]) +# Crosses through line starting endpoint -> doesn't cross +@test GO.crosses(h, v1) == LG.crosses(h, v1) == false +# Crosses through line ending endpoint -> doesn't cross +@test GO.crosses(h, v2) == LG.crosses(h, v2) == false +# Crosses through line middle vertex -> crosses +@test GO.crosses(h, v3) == LG.crosses(h, v3) == true +# Crosses through line edge at vertex -> crosses +@test GO.crosses(h, v4) == LG.crosses(h, v4) == true +# Crosses through line edge -> crosses +@test GO.crosses(h, v5) == LG.crosses(h, v5) == true +# Crosses through line edge -> crosses +@test GO.crosses(v5, h) == LG.crosses(v5, h) == true +# Line bounces off of vertical curve on edge -> doesn't cross +@test GO.crosses(b1, v5) == GO.crosses(v5, b1) == LG.crosses(b1, v5) == false +# Line bounces off of horizontal curve on edge --> doesn't cross +@test GO.crosses(b2, h) == GO.crosses(h, b2) == LG.crosses(b2, h) == false +# Line bounces off of horizontal curve on vertex --> doesn't cross +@test GO.crosses(b3, h) == GO.crosses(h, b3) == LG.crosses(b3, h) == false +# Diagonal lines pass through one another --> crosses +@test GO.crosses(d1, d2) == GO.crosses(d2, d1) == LG.crosses(d1, d2) == true +# Curve bounces off of diagonal line -> doesn't cross +@test GO.crosses(d1, b4) == GO.crosses(b4, d1) == LG.crosses(d1, b4) == false +# Lines with parallel segments cross -> cross +@test GO.crosses(b5, b7) == GO.crosses(b7, b5) == LG.crosses(b7, b5) == true +# Lines with parallel segments bounce -> crosses +@test GO.crosses(b6, b7) == GO.crosses(b7, b6) == LG.crosses(b7, b6) == true + From 8bd648a84ef173279d908a332722b0244e8afabd Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Thu, 28 Dec 2023 12:13:05 -0800 Subject: [PATCH 28/33] Update line line processors for crosses and overlaps --- src/methods/geom_relations/coveredby.jl | 25 ++- src/methods/geom_relations/crosses.jl | 172 +++--------------- src/methods/geom_relations/disjoint.jl | 6 +- .../geom_relations/geom_geom_processors.jl | 171 +++++++++++++---- src/methods/geom_relations/overlaps.jl | 112 ++++++++++-- src/methods/geom_relations/touches.jl | 4 +- src/methods/geom_relations/within.jl | 8 +- test/methods/bools.jl | 12 +- test/methods/geom_relations/crosses.jl | 12 +- 9 files changed, 299 insertions(+), 223 deletions(-) diff --git a/src/methods/geom_relations/coveredby.jl b/src/methods/geom_relations/coveredby.jl index 0421a531f..720c61d17 100644 --- a/src/methods/geom_relations/coveredby.jl +++ b/src/methods/geom_relations/coveredby.jl @@ -40,7 +40,8 @@ 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 `coveredby` 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 in the interior of g2 (either through + overlap or crossing for lines) - points of g1 are allowed to be on the boundary of g2 - points of g1 are not allowed to be in the exterior of g2 - no points of g1 are required to be in the interior of g2 @@ -164,7 +165,7 @@ coveredby( ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + over_allow = true, cross_allow = true, on_allow = true, out_allow = false, in_require = false, on_require = false, out_require = false, closed_line = false, closed_curve = false, @@ -184,7 +185,7 @@ coveredby( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + over_allow = true, cross_allow = true, on_allow = true, out_allow = false, in_require = false, on_require = false, out_require = false, closed_line = false, closed_curve = true, @@ -223,7 +224,7 @@ coveredby( ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + over_allow = true, cross_allow = true, on_allow = true, out_allow = false, in_require = false, on_require = false, out_require = false, closed_line = true, closed_curve = false, @@ -241,7 +242,7 @@ coveredby( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + over_allow = true, cross_allow = true, on_allow = true, out_allow = false, in_require = false, on_require = false, out_require = false, closed_line = true, closed_curve = true, @@ -264,6 +265,20 @@ coveredby( closed_line = true, ) +# Polygons covered by geometries + +""" + +""" +coveredby( + ::GI.PolygonTrait, g1, + ::GI.PolygonTrait, g2, +) = _polygon_polygon_process( + g1, g2; + in_allow = true, on_allow = true, out_allow = false, + in_require = true, on_require = false, out_require = false, +) + """ coveredby( ::Union{GI.PolygonTrait, GI.MultiPolygonTrait}, g1, diff --git a/src/methods/geom_relations/crosses.jl b/src/methods/geom_relations/crosses.jl index 73f7a5c04..57ab54827 100644 --- a/src/methods/geom_relations/crosses.jl +++ b/src/methods/geom_relations/crosses.jl @@ -80,161 +80,46 @@ vertex. Return true if those conditions are met, else false. crosses( ::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2, -) = _line_curve_crosses_overlap_process( +) = _line_curve_process( g1, g2; - orientation = line_cross, - closed_line = false, closed_curve = false, + over_allow = false, cross_allow = true, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = false, ) crosses( ::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2, -) = _line_curve_crosses_overlap_process( +) = _line_curve_process( g1, g2; - orientation = line_cross, - closed_line = false, closed_curve = true, + over_allow = false, cross_allow = true, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = true, ) crosses( ::GI.LinearRingTrait, g1, ::GI.LineStringTrait, g2, -) = _line_curve_crosses_overlap_process( +) = _line_curve_process( g1, g2; - orientation = line_cross, - closed_line = true, closed_curve = false, + over_allow = false, cross_allow = true, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = true, + closed_curve = false, ) crosses( ::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2, -) = _line_curve_crosses_overlap_process( - g1, g2; - orientation = line_cross, - closed_line = true, closed_curve = true, -) - -function _line_curve_crosses_overlap_process( - line, curve; - orientation = line_cross, - closed_line = false, closed_curve = false, -) - nl = GI.npoint(line) - nc = GI.npoint(curve) - first_last_equal_line = equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) - first_last_equal_curve = equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) - nl -= first_last_equal_line ? 1 : 0 - nc -= first_last_equal_curve ? 1 : 0 - closed_line |= first_last_equal_line - closed_curve |= first_last_equal_curve - # Loop over each line segment - orientation_req_met = false - l_start = GI.getpoint(line, closed_line ? nl : 1) - for i in (closed_line ? 1 : 2):nl - l_end = GI.getpoint(line, i) - c_start = GI.getpoint(curve, closed_curve ? nc : 1) - for j in (closed_curve ? 1 : 2):nc - c_end = GI.getpoint(curve, j) - seg_val = _segment_segment_orientation( - (l_start, l_end), - (c_start, c_end), - ) - if seg_val == line_over - return false - elseif seg_val == line_cross - orientation_req_met = true - elseif seg_val == line_hinge && !orientation_req_met - _, fracs = _intersection_point( - (_tuple_point(l_start), _tuple_point(l_end)), - (_tuple_point(c_start), _tuple_point(c_end)) - ) - if !isnothing(fracs) - (α, β) = fracs # 0 ≤ α ≤ 1 and 0 ≤ β ≤ 1 since hinge - if β == 0 # already checked on previous segment - c_start = c_end - continue - elseif α == 0 - if !closed_line && i == 2 - c_start = c_end - continue - end - elseif α == 1 - if !closed_line && i == nl - c_start = c_end - continue - end - else # 0 < α < 1, β = 1 (if 0 < β < 1 then seg_val = cross) - if !closed_curve && j == nc - c_start = c_end - continue - end - c_next = GI.getpoint(curve, j < nc ? j + 1 : 1) - x_start, y_start = GI.x(c_start), GI.y(c_start) - x_next, y_next = GI.x(c_next), GI.y(c_next) - Δx = GI.x(l_end) - GI.x(l_start) - Δy = GI.y(l_end) - GI.y(l_start) - if Δx == 0 - x = GI.x(l_start) - if (x_next - x) * (x_start - x) ≥ 0 - c_start = c_end - continue - end - elseif Δy == 0 - y = GI.y(l_start) - if (y_next - y) * (y_start - y) ≥ 0 - c_start = c_end - continue - end - else - m = Δy / Δx - b = GI.y(l_start) - m * GI.x(l_start) - Δy_start = (m * x_start + b) - y_start - Δy_next = (m * x_next + b) - y_next - if Δy_start * Δy_next ≥ 0 - c_start = c_end - continue - end - end - orientation_req_met = true - continue - end - end - T = typeof(GI.x(l_start)) - (α, β) = # α = 0 or α = 1 - if equals(l_start, c_start) - (zero(T), zero(T)) - elseif equals(l_start, c_end) - (zero(T), one(T)) - elseif equals(l_end, c_start) - (one(T), zero(T)) - elseif equals(l_end, c_end) - (one(T), one(T)) - else - fracs - end - if β == 0 - c_start = c_end - continue - end - l1, l2, l3 = α == 0 ? - (GI.getpoint(line, i > 2 ? (i - 2) : nl), l_start, l_end) : - (l_start, l_end, GI.getpoint(line, i < nl ? (i + 1) : 1)) - θ1 = atan(GI.y(l1) - GI.y(l2), GI.x(l1) - GI.x(l2)) - θ2 = atan(GI.y(l3) - GI.y(l2), GI.x(l3) - GI.x(l2)) - θ1, θ2 = θ1 < θ2 ? (θ1, θ2) : (θ2, θ1) - - c_next = β == 1 ? - GI.getpoint(curve, j < nc ? j + 1 : 1) : - c_end - ϕ1 = atan(GI.y(c_start) - GI.y(l2), GI.x(c_start) - GI.x(l2)) - ϕ2 = atan(GI.y(c_next) - GI.y(l2), GI.x(c_next) - GI.x(l2)) - orientation_req_met = ((θ1 < ϕ1 < θ2) ⊻ (θ1 < ϕ2 < θ2)) - end - c_start = c_end - end - l_start = l_end - end - return orientation_req_met -end +) = _line_curve_process( + g1, g2; + over_allow = false, cross_allow = true, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = true, + closed_curve = true, + ) """ crosses(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool @@ -243,16 +128,7 @@ A line string is crosses a linear ring if the vertices and edges of the linestring are crosses the linear ring. Return true if those conditions are met, else false. """ -# crosses( -# ::GI.LineStringTrait, g1, -# ::GI.LinearRingTrait, g2, -# ) = _line_curve_process( -# g1, g2; -# in_allow = false, on_allow = true, out_allow = true, -# in_require = false, on_require = true, out_require = true, -# closed_line = false, -# closed_curve = true, -# ) + """ crosses(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool @@ -269,7 +145,7 @@ crosses( ) = _line_polygon_process( g1, g2; in_allow = false, on_allow = true, out_allow = true, - in_require = false, on_require = true, out_require = true, + in_require = true, on_require = false, out_require = true, closed_line = false, ) diff --git a/src/methods/geom_relations/disjoint.jl b/src/methods/geom_relations/disjoint.jl index 1e680ee35..3bca4a0fd 100644 --- a/src/methods/geom_relations/disjoint.jl +++ b/src/methods/geom_relations/disjoint.jl @@ -149,7 +149,7 @@ disjoint( ::GI.LineStringTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = false, on_allow = false, out_allow = true, + over_allow = false, cross_allow = false, on_allow = false, out_allow = true, in_require = false, on_require = false, out_require = false, closed_line = false, closed_curve = false, @@ -167,7 +167,7 @@ disjoint( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = false, on_allow = false, out_allow = true, + over_allow = false, cross_allow = false, on_allow = false, out_allow = true, in_require = false, on_require = false, out_require = false, closed_line = false, closed_curve = true, @@ -226,7 +226,7 @@ disjoint( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = false, on_allow = false, out_allow = true, + over_allow = false, cross_allow = false, on_allow = false, out_allow = true, in_require = false, on_require = false, out_require = false, closed_line = true, closed_curve = true, diff --git a/src/methods/geom_relations/geom_geom_processors.jl b/src/methods/geom_relations/geom_geom_processors.jl index a049b709f..c4d5092d0 100644 --- a/src/methods/geom_relations/geom_geom_processors.jl +++ b/src/methods/geom_relations/geom_geom_processors.jl @@ -227,7 +227,7 @@ end function _line_curve_process( line, curve; - in_allow, on_allow, out_allow, + over_allow, cross_allow, on_allow, out_allow, # TODO: seperate crosses and overlaps (?) in_require, on_require, out_require, closed_line = false, closed_curve = false, @@ -244,7 +244,6 @@ function _line_curve_process( nc -= first_last_equal_curve ? 1 : 0 closed_line |= first_last_equal_line closed_curve |= first_last_equal_curve - # Loop over each line segment l_start = GI.getpoint(line, closed_line ? nl : 1) i = closed_line ? 1 : 2 @@ -261,47 +260,72 @@ function _line_curve_process( ) # if segments are touching if seg_val == line_over - !in_allow && return false + !over_allow && return false # at least one point in, meets requirments in_req_met = true - if seg_val == line_over - point_val = point_segment_orientation( - l_start, + point_val = point_segment_orientation( + l_start, + c_start, c_end, + ) + if point_val != point_out + if point_segment_orientation( + l_end, c_start, c_end, - ) - if point_val != point_out - if point_segment_orientation( - l_end, - c_start, c_end, - ) != point_out - l_start = l_end - i += 1 - break - elseif point_segment_orientation( - c_start, - l_start, l_end, - ) != point_out - l_start = c_start - break - elseif point_segment_orientation( - c_end, - l_start, l_end, - ) != point_out - l_start = c_end - break - end + ) != point_out + l_start = l_end + i += 1 + break + elseif point_segment_orientation( + c_start, + l_start, l_end, + ) != point_out && !equals(l_start, c_start) + l_start = c_start + break + elseif point_segment_orientation( + c_end, + l_start, l_end, + ) != point_out && !equals(l_start, c_end) + l_start = c_end + break end end else - if seg_val == line_hinge - !on_allow && return false - # at least one point on, meets requirments - on_req_met = true - elseif seg_val == line_cross - !in_allow && return false - # at least one point in, meets requirments - in_req_met = true + if seg_val == line_cross + !cross_allow && return false + in_req_met = true + elseif seg_val == line_hinge + _, fracs = _intersection_point( + (_tuple_point(l_start), _tuple_point(l_end)), + (_tuple_point(c_start), _tuple_point(c_end)) + ) + (α, β) = + if !isnothing(fracs) + fracs + else # line and curve segments are parallel + if equals(l_start, c_start) + (0, 0) + elseif equals(l_start, c_end) + (0, 1) + elseif equals(l_end, c_start) + (1, 0) + else # equals(l_end, c_end) + (1, 1) + end + end + if ( + (β == 0 && !closed_curve && j == 2) || + (β == 1 && !closed_curve && j == nc) || + (α == 0 && !closed_line && i == 2) || + (α == 1 && !closed_line && i == nl) + ) + !on_allow && return false + on_req_met = true + else + !cross_allow && return false + in_req_met = true + end end + # no overlap for a give segment if j == nc !out_allow && return false @@ -309,12 +333,85 @@ function _line_curve_process( end end c_start = c_end - j == nc && (i += 1) + if j == nc + i += 1 + l_start = l_end + end end end return in_req_met && on_req_met && out_req_met end +function _line_curve_crosses_overlap_interactions( + line, curve; + closed_line = false, closed_curve = false, +) + nl = GI.npoint(line) + nc = GI.npoint(curve) + first_last_equal_line = equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) + first_last_equal_curve = equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) + nl -= first_last_equal_line ? 1 : 0 + nc -= first_last_equal_curve ? 1 : 0 + closed_line |= first_last_equal_line + closed_curve |= first_last_equal_curve + # Loop over each line segment + crosses = false + overlaps = false + out = false + l_start = GI.getpoint(line, closed_line ? nl : 1) + for i in (closed_line ? 1 : 2):nl + l_end = GI.getpoint(line, i) + c_start = GI.getpoint(curve, closed_curve ? nc : 1) + for j in (closed_curve ? 1 : 2):nc + crosses && overlaps && return (crosses, overlaps) + c_end = GI.getpoint(curve, j) + seg_val = _segment_segment_orientation( + (l_start, l_end), + (c_start, c_end), + ) + if seg_val == line_out + out = true + elseif seg_val == line_over + overlaps = true + + elseif seg_val == line_cross + crosses = true + elseif seg_val == line_hinge + out = true + _, fracs = _intersection_point( + (_tuple_point(l_start), _tuple_point(l_end)), + (_tuple_point(c_start), _tuple_point(c_end)) + ) + (α, β) = + if !isnothing(fracs) + fracs + else + if equals(l_start, c_start) + (0, 0) + elseif equals(l_start, c_end) + (0, 1) + elseif equals(l_end, c_start) + (1, 0) + else # equals(l_end, c_end) + (1, 1) + end + end + if ( + !(β == 0) && + !(β == 1 && !closed_curve && j == nc) && + !(α == 0 && !closed_line && i == 2) && + !(α == 1 && !closed_line && i == nl) + ) + crosses = true + end + end + c_start = c_end + end + l_start = l_end + end + return crosses, overlaps +end + function _line_filled_curve_interactions( line, curve; closed_line = false, diff --git a/src/methods/geom_relations/overlaps.jl b/src/methods/geom_relations/overlaps.jl index 66e5643ee..64258f83d 100644 --- a/src/methods/geom_relations/overlaps.jl +++ b/src/methods/geom_relations/overlaps.jl @@ -142,27 +142,115 @@ return true. Else false. # end # return false # end +# function overlaps( +# ::GI.LineStringTrait, g1, +# ::GI.LineStringTrait, g2, +# ) +# cross, overlap = _line_curve_crosses_overlap_interactions( +# g1, g2; +# closed_line = false, closed_curve = false, +# ) +# return !cross && overlap +# end + +# function overlaps( +# ::GI.LineStringTrait, g1, +# ::GI.LinearRingTrait, g2, +# ) +# cross, overlap = _line_curve_crosses_overlap_interactions( +# g1, g2; +# closed_line = true, closed_curve = false, +# ) +# return !cross && overlap +# end +# function overlaps( +# ::GI.LinearRingTrait, g1, +# ::GI.LineStringTrait, g2, +# ) +# cross, overlap = _line_curve_crosses_overlap_interactions( +# g1, g2; +# closed_line = false, closed_curve = true, +# ) +# return !cross && overlap +# end +# function overlaps( +# ::GI.LinearRingTrait, g1, +# ::GI.LinearRingTrait, g2, +# ) +# cross, overlap = _line_curve_crosses_overlap_interactions( +# g1, g2; +# closed_line = true, closed_curve = true, +# ) +# return !cross && overlap +# end + overlaps( ::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2, ) = _line_curve_process( - g1, g2; - in_allow = true, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = false, - closed_curve = false, -) + g1, g2; + over_allow = true, cross_allow = false, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = false, + ) && _line_curve_process( + g2, g1; + over_allow = true, cross_allow = false, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = false, + ) overlaps( ::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2, ) = _line_curve_process( - g1, g2; - in_allow = true, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = false, - closed_curve = true, -) + g1, g2; + over_allow = true, cross_allow = false, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = true, + ) && _line_curve_process( + g2, g1; + over_allow = true, cross_allow = false, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = true, + closed_curve = false, + ) + +overlaps( + ::GI.LinearRingTrait, g1, + ::GI.LineStringTrait, g2, +) = _line_curve_process( + g1, g2; + over_allow = true, cross_allow = false, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = true, + closed_curve = false, + ) && _line_curve_process( + g2, g1; + over_allow = true, cross_allow = false, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = true, + ) + +overlaps( + ::GI.LinearRingTrait, g1, + ::GI.LinearRingTrait, g2, +) = _line_curve_process( + g1, g2; + over_allow = true, cross_allow = false, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = true, + closed_curve = true, + ) && _line_curve_process( + g2, g1; + over_allow = true, cross_allow = false, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = true, + closed_curve = true, + ) """ overlaps( diff --git a/src/methods/geom_relations/touches.jl b/src/methods/geom_relations/touches.jl index 248641e7b..137745e1e 100644 --- a/src/methods/geom_relations/touches.jl +++ b/src/methods/geom_relations/touches.jl @@ -80,7 +80,7 @@ touches( ::GI.LineStringTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = false, on_allow = true, out_allow = true, + over_allow = false, cross_allow = false, on_allow = true, out_allow = true, in_require = false, on_require = true, out_require = false, closed_line = false, closed_curve = false, @@ -98,7 +98,7 @@ touches( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = false, on_allow = true, out_allow = true, + over_allow = false, cross_allow = false, on_allow = true, out_allow = true, in_require = false, on_require = true, out_require = false, closed_line = false, closed_curve = true, diff --git a/src/methods/geom_relations/within.jl b/src/methods/geom_relations/within.jl index 6cc1433b0..b492e4737 100644 --- a/src/methods/geom_relations/within.jl +++ b/src/methods/geom_relations/within.jl @@ -155,7 +155,7 @@ within( ::GI.LineStringTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + over_allow = true, cross_allow = true, on_allow = true, out_allow = false, in_require = true, on_require = false, out_require = false, closed_line = false, closed_curve = false, @@ -173,7 +173,7 @@ within( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + over_allow = true, cross_allow = true, on_allow = true, out_allow = false, in_require = true, on_require = false, out_require = false, closed_line = false, closed_curve = true, @@ -211,7 +211,7 @@ within( ::GI.LineStringTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + over_allow = true, cross_allow = true, on_allow = true, out_allow = false, in_require = true, on_require = false, out_require = false, closed_line = true, closed_curve = false, @@ -229,7 +229,7 @@ within( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + over_allow = true, cross_allow = true, on_allow = true, out_allow = false, in_require = true, on_require = false, out_require = false, closed_line = true, closed_curve = true, diff --git a/test/methods/bools.jl b/test/methods/bools.jl index 32979349d..45b8ef50d 100644 --- a/test/methods/bools.jl +++ b/test/methods/bools.jl @@ -30,12 +30,12 @@ p1 = LG.Polygon(r1, [r2, r3]) @testset "Covered By" begin include("geom_relations/coveredby.jl") end @testset "Covers" begin include("geom_relations/covers.jl") end @testset "Crosses" begin include("geom_relations/crosses.jl") end -# @testset "Disjoint" begin include("geom_relations/disjoint.jl") end -# @testset "Equals" begin include("geom_relations/equals.jl") end -# @testset "Touches" begin include("geom_relations/touches.jl") end -# @testset "Overlaps" begin include("geom_relations/overlaps.jl") end -# @testset "Intersect" begin include("geom_relations/intersects.jl") end -# @testset "Within" begin include("geom_relations/within.jl") end +@testset "Disjoint" begin include("geom_relations/disjoint.jl") end +@testset "Equals" begin include("geom_relations/equals.jl") end +@testset "Touches" begin include("geom_relations/touches.jl") end +@testset "Overlaps" begin include("geom_relations/overlaps.jl") end +@testset "Intersect" begin include("geom_relations/intersects.jl") end +@testset "Within" begin include("geom_relations/within.jl") end # @testset "booleans" begin diff --git a/test/methods/geom_relations/crosses.jl b/test/methods/geom_relations/crosses.jl index cb4d02d5d..c7b167f3e 100644 --- a/test/methods/geom_relations/crosses.jl +++ b/test/methods/geom_relations/crosses.jl @@ -70,15 +70,15 @@ b7 = LG.LineString([[1.0, 0.0], [0.0, 0.0], [-1.0, -1.0]]) # Crosses through line edge -> crosses @test GO.crosses(v5, h) == LG.crosses(v5, h) == true # Line bounces off of vertical curve on edge -> doesn't cross -@test GO.crosses(b1, v5) == GO.crosses(v5, b1) == LG.crosses(b1, v5) == false +@test GO.crosses(b1, v5) == GO.crosses(v5, b1) == LG.crosses(b1, v5) == true # Line bounces off of horizontal curve on edge --> doesn't cross -@test GO.crosses(b2, h) == GO.crosses(h, b2) == LG.crosses(b2, h) == false -# Line bounces off of horizontal curve on vertex --> doesn't cross -@test GO.crosses(b3, h) == GO.crosses(h, b3) == LG.crosses(b3, h) == false +@test GO.crosses(b2, h) == GO.crosses(h, b2) == LG.crosses(b2, h) == true +# Line bounces off of horizontal curve on vertex --> crosses +@test GO.crosses(b3, h) == GO.crosses(h, b3) == LG.crosses(b3, h) == true # Diagonal lines pass through one another --> crosses @test GO.crosses(d1, d2) == GO.crosses(d2, d1) == LG.crosses(d1, d2) == true -# Curve bounces off of diagonal line -> doesn't cross -@test GO.crosses(d1, b4) == GO.crosses(b4, d1) == LG.crosses(d1, b4) == false +# Curve bounces off of diagonal line -> crosses +@test GO.crosses(d1, b4) == GO.crosses(b4, d1) == LG.crosses(d1, b4) == true # Lines with parallel segments cross -> cross @test GO.crosses(b5, b7) == GO.crosses(b7, b5) == LG.crosses(b7, b5) == true # Lines with parallel segments bounce -> crosses From 920a24b9b2d32497d59dd99df25d597f3a512a31 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Thu, 28 Dec 2023 16:02:23 -0800 Subject: [PATCH 29/33] Clean up and comment 5 of 10 functions --- src/methods/geom_relations/contains.jl | 4 +- src/methods/geom_relations/coveredby.jl | 201 +++++++-------- src/methods/geom_relations/covers.jl | 16 +- src/methods/geom_relations/crosses.jl | 300 ++++++++++------------- src/methods/geom_relations/disjoint.jl | 285 ++++++++------------- src/methods/geom_relations/intersects.jl | 6 +- src/methods/geom_relations/overlaps.jl | 2 +- src/methods/geom_relations/touches.jl | 2 +- src/methods/geom_relations/within.jl | 4 +- 9 files changed, 339 insertions(+), 481 deletions(-) diff --git a/src/methods/geom_relations/contains.jl b/src/methods/geom_relations/contains.jl index e23317008..08146e280 100644 --- a/src/methods/geom_relations/contains.jl +++ b/src/methods/geom_relations/contains.jl @@ -54,11 +54,11 @@ boundary of the secondary (g2) must not intersect the exterior of the first ```jldoctest import GeometryOps as GO, GeoInterface as GI line = GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)]) -point = (1, 2) +point = GI.Point((1, 2)) GO.contains(line, point) # output true ``` """ -contains(g1, g2)::Bool = within(g2, g1) +contains(g1, g2)::Bool = GeometryOps.within(g2, g1) diff --git a/src/methods/geom_relations/coveredby.jl b/src/methods/geom_relations/coveredby.jl index 720c61d17..ccbcea539 100644 --- a/src/methods/geom_relations/coveredby.jl +++ b/src/methods/geom_relations/coveredby.jl @@ -73,34 +73,23 @@ GO.coveredby(p1, l1) true ``` """ -coveredby(g1, g2) = coveredby(trait(g1), g1, trait(g2), g2) -coveredby(::GI.FeatureTrait, g1, ::Any, g2) = coveredby(GI.geometry(g1), g2) -coveredby(::Any, g1, t2::GI.FeatureTrait, g2) = coveredby(g1, GI.geometry(g2)) +coveredby(g1, g2) = _coveredby(trait(g1), g1, trait(g2), g2) +# # Convert features to geometries +_coveredby(::GI.FeatureTrait, g1, ::Any, g2) = coveredby(GI.geometry(g1), g2) +_coveredby(::Any, g1, t2::GI.FeatureTrait, g2) = coveredby(g1, GI.geometry(g2)) -# Points coveredby geometries -""" - coveredby(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool -If a point is coveredby another point, then those points must be equal. If they -are not equal, then they are not coveredby and return false. -""" -coveredby( +# # Points coveredby geometries + +# Point is coveredby another point if those points are equal +_coveredby( ::GI.PointTrait, g1, ::GI.PointTrait, g2, ) = equals(g1, g2) - -""" - coveredby( - ::GI.PointTrait, g1, - ::Union{GI.LineTrait, GI.LineStringTrait}, g2, - )::Bool - -A point is coveredby a line or linestring if it is on a vertex or an edge of -that linestring. Return true if those conditions are met, else false. -""" -coveredby( +# Point is coveredby a line/linestring if it is on a line vertex or an edge +_coveredby( ::GI.PointTrait, g1, ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _point_curve_process( @@ -109,13 +98,8 @@ coveredby( repeated_last_coord = false, ) -""" - coveredby(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool - -A point is coveredby a linear ring if it is on a vertex or an edge of that -linear ring. Return true if those conditions are met, else false. -""" -coveredby( +# Point is coveredby a linearring if it is on a vertex or an edge of ring +_coveredby( ::GI.PointTrait, g1, ::GI.LinearRingTrait, g2, ) = _point_curve_process( @@ -124,13 +108,8 @@ coveredby( repeated_last_coord = true, ) -""" - coveredby(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool - -A point is coveredby a polygon if it is inside of that polygon, including edges -and vertices. Return true if those conditions are met, else false. -""" -coveredby( +# Point is coveredby a polygon if it is inside polygon, including edges/vertices +_coveredby( ::GI.PointTrait, g1, ::GI.PolygonTrait, g2, ) = _point_polygon_process( @@ -138,29 +117,18 @@ coveredby( in_allow = true, on_allow = true, out_allow = false, ) -""" -coveredby(::GI.AbstractTrait, g1, ::GI.PointTrait, g2)::Bool - -Points cannot cover any geometry other than points. Return false if not -dispatched to more specific function. -""" -coveredby( - ::GI.AbstractTrait, g1, +# Points cannot cover any geometry other than points +_coveredby( + ::GI.AbstractGeometryTrait, g1, ::GI.PointTrait, g2, ) = false -# Lines coveredby geometries -""" - coveredby( - ::Union{GI.LineTrait, GI.LineStringTrait}, g1, - ::Union{GI.LineTrait, GI.LineStringTrait}, g2, - )::Bool - -A line or linestring is coveredby another line or linestring if all of the -interior and boundary points of the first line are on the interior and -boundary points of the second line. -""" -coveredby( + +# # Lines coveredby geometries + +#= Linestring is coveredby a line if all interior and boundary points of the +first line are on the interior/boundary points of the second line. =# +_coveredby( ::Union{GI.LineTrait, GI.LineStringTrait}, g1, ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( @@ -171,16 +139,9 @@ coveredby( closed_curve = false, ) -""" - coveredby( - ::Union{GI.LineTrait, GI.LineStringTrait}, g1, - ::GI.LinearRingTrait, g2, - )::Bool - -A line or linestring is coveredby a linear ring if all of the interior and -boundary points of the line are on the edges of the ring. -""" -coveredby( +#= Linestring is coveredby a ring if all interior and boundary points of the +line are on the edges of the ring. =# +_coveredby( ::Union{GI.LineTrait, GI.LineStringTrait}, g1, ::GI.LinearRingTrait, g2, ) = _line_curve_process( @@ -191,14 +152,9 @@ coveredby( closed_curve = true, ) -""" - coveredby(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool - -A line or linestring is coveredby a polygon if all of the interior and boundary -points of the line are in the polygon interior or on its edges. This includes -edges of holes. Return true if those conditions are met, else false. -""" -coveredby( +#= Linestring is coveredby a polygon if all interior and boundary points of the +line are in the polygon interior or on its edges, inlcuding hole edges. =# +_coveredby( ::Union{GI.LineTrait, GI.LineStringTrait}, g1, ::GI.PolygonTrait, g2, ) = _line_polygon_process( @@ -208,18 +164,11 @@ coveredby( closed_line = false, ) -# Rings covered by geometries -""" - coveredby( - ::GI.LinearRingTrait, g1, - ::Union{GI.LineTrait, GI.LineStringTrait}, g2, - )::Bool - -A linear ring is covered by a linestring if all the vertices and edges of the -linear ring are on the edges/vertices of the linear ring. Return true if -those conditions are met, else false. -""" -coveredby( +# # Rings covered by geometries + +#= Linearring is covered by a line if all vertices and edges of the ring are on +the edges and vertices of the line. =# +_coveredby( ::GI.LinearRingTrait, g1, ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( @@ -230,14 +179,9 @@ coveredby( closed_curve = false, ) -""" - coveredby(::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2)::Bool - -A linear ring is covered by another linear ring if the vertices and edges of the -first linear ring are on the edges/vertices of the second linear ring. Return -true if those conditions are met, else false. -""" -coveredby( +#= Linearring is covered by another linear ring if all vertices and edges of the +first ring are on the edges/vertices of the second ring. =# +_coveredby( ::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2, ) = _line_curve_process( @@ -248,14 +192,9 @@ coveredby( closed_curve = true, ) -""" - coveredby(::GI.LinearRingTrait, g1, ::GI.PolygonTrait, g2)::Bool - -A linear ring is coveredby a polygon if the vertices and edges of the linear -ring are either in the polygon interior or on the polygon edges. This includes -edges of holes. Return true if those conditions are met, else false. -""" -coveredby( +#= Linearring is coveredby a polygon if all vertices and edges of the ring are +in the polygon interior or on the polygon edges, inlcuding hole edges. =# +_coveredby( ::GI.LinearRingTrait, g1, ::GI.PolygonTrait, g2, ) = _line_polygon_process( @@ -265,12 +204,13 @@ coveredby( closed_line = true, ) -# Polygons covered by geometries -""" +# # Polygons covered by geometries -""" -coveredby( +#= Polygon is covered by another polygon if if the interior and edges of the +first polygon are in the second polygon interior or on polygon edges, including +hole edges.=# +_coveredby( ::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2, ) = _polygon_polygon_process( @@ -279,17 +219,44 @@ coveredby( in_require = true, on_require = false, out_require = false, ) -""" - coveredby( - ::Union{GI.PolygonTrait, GI.MultiPolygonTrait}, g1, - ::Union{GI.LineTrait, GI.LineStringTrait, GI.LinearRingTrait}, g2, - )::Bool - -Polygons and multipolygons cannot be covered by curves as they are not filled. -Return false -""" -coveredby( - ::Union{GI.PolygonTrait, GI.MultiPolygonTrait}, g1, - ::Union{GI.LineTrait, GI.LineStringTrait, GI.LinearRingTrait}, g2, +# Polygons cannot covered by any curves +_coveredby( + ::GI.PolygonTrait, g1, + ::GI.AbstractCurveTrait, g2, ) = false + +# # Geometries coveredby multi-geometry/geometry collections + +#= Geometry is covered by a multi-geometry or a collection if one of the elements +of the collection cover the geometry. =# +function _coveredby( + ::GI.AbstractGeometryTrait, g1 + ::Union{ + GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g2 +) + for sub_g2 in GI.getgeom(g2) + coveredby(g1, sub_g2) && return true + end + return false +end + +# # Multi-geometry/geometry collections coveredby geometries + +#= Multi-geometry or a geometry collection is covered by a geometry if all +elements of the collection are covered by the geometry. =# +function _coveredby( + ::Union{ + GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g1, + ::GI.AbstractGeometryTrait, g2 +) + for sub_g1 in GI.getgeom(g1) + !coveredby(sub_g1, g2) && return false + end + return true +end + diff --git a/src/methods/geom_relations/covers.jl b/src/methods/geom_relations/covers.jl index 398c3817f..a9afb778f 100644 --- a/src/methods/geom_relations/covers.jl +++ b/src/methods/geom_relations/covers.jl @@ -17,11 +17,17 @@ using GeometryOps.GeometryBasics using Makie using CairoMakie +p1 = Point(0.0, 0.0) +p2 = Point(1.0, 1.0) +l1 = Line(p1, p2) +f, a, p = lines([p1, p2]) +scatter!(p1, color = :red) ``` ```@example cshape -covers(l1, l2) # returns true +covers(l1, p1) # returns true +covers(p1, l1) # returns false ``` ## Implementation @@ -46,8 +52,12 @@ intersect. ```jldoctest import GeometryOps as GO, GeoInterface as GI -line = GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)]) +l1 = GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)]) +l2 = GI.LineString([(1, 1), (1, 2)]) +GO.covers(l1, l2) +# output +true ``` """ -covers(g1, g2)::Bool = coveredby(g2, g1) +covers(g1, g2)::Bool = GeometryOps.coveredby(g2, g1) diff --git a/src/methods/geom_relations/crosses.jl b/src/methods/geom_relations/crosses.jl index 57ab54827..d767b43dc 100644 --- a/src/methods/geom_relations/crosses.jl +++ b/src/methods/geom_relations/crosses.jl @@ -7,13 +7,13 @@ export crosses 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 -one of the geometries has a smaller dimensionality than the other geometry. -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. +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. Note that points can't cross any geometries, despite different dimension, due to -their inability to be both crosses and exterior to any other shape. +their inability to be both interior and exterior to any other shape. To provide an example, consider these two lines: ```@example cshape @@ -22,11 +22,15 @@ using GeometryOps.GeometryBasics using Makie using CairoMakie +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 cshape - +crosses(l1, l2) # true ``` ## Implementation @@ -36,10 +40,18 @@ 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. - =# """ @@ -54,93 +66,59 @@ must intersect the exterior of the secondary geometry. ```jldoctest setup=:(using GeometryOps, GeometryBasics) import GeometryOps as GO, GeoInterface as GI +l1 = GI.Line([(0.0, 0.0), (1.0, 0.0)]) +l2 = GI.Line([(0.5, 1.0), (0.5, -1.0)]) - +GO.crosses(l1, l2) # output - +true ``` """ -crosses(g1, g2) = crosses(trait(g1), g1, trait(g2), g2) -crosses(::GI.FeatureTrait, g1, ::Any, g2) = crosses(GI.geometry(g1), g2) -crosses(::Any, g1, t2::GI.FeatureTrait, g2) = crosses(g1, GI.geometry(g2)) +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)) -""" -crosses(::GI.AbstractTrait, g1, ::GI.AbstractTrait, g2) = false -# Lines crosses geometries -""" - crosses(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool +# # Non-specified geometries -A line string is crosses another linestring if the vertices and edges of the -first linestring are crosses the second linestring, including the first and last -vertex. Return true if those conditions are met, else false. -""" -crosses( - ::GI.LineStringTrait, g1, - ::GI.LineStringTrait, g2, -) = _line_curve_process( - g1, g2; - over_allow = false, cross_allow = true, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = false, - closed_curve = false, - ) - -crosses( - ::GI.LineStringTrait, g1, - ::GI.LinearRingTrait, g2, -) = _line_curve_process( - g1, g2; - over_allow = false, cross_allow = true, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = false, - closed_curve = true, - ) - -crosses( - ::GI.LinearRingTrait, g1, - ::GI.LineStringTrait, g2, -) = _line_curve_process( - g1, g2; - over_allow = false, cross_allow = true, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = true, - closed_curve = false, - ) - -crosses( - ::GI.LinearRingTrait, g1, - ::GI.LinearRingTrait, g2, -) = _line_curve_process( - g1, g2; - over_allow = false, cross_allow = true, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = true, - closed_curve = true, - ) +# Points and geometries with the same dimensions D where D ≂̸ 1 default to false +_crosses(::GI.AbstractGeometryTrait, g1, ::GI.AbstractGeometryTrait, g2) = false -""" - crosses(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool -A line string is crosses a linear ring if the vertices and edges of the -linestring are crosses the linear ring. Return true if those conditions are met, -else 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; + over_allow = false, cross_allow = true, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = false, +) -""" - crosses(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool +#= 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; + over_allow = false, cross_allow = true, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = false, + closed_curve = true, +) -A line string is crosses a polygon if the vertices and edges of the -linestring are crosses the polygon. Points of the linestring can be on the -polygon edges, but at least one point must be in the polygon interior. The -linestring also cannot cross through a hole. Return true if those conditions are -met, else false. -""" -crosses( - ::GI.LineStringTrait, g1, +#= 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; @@ -149,95 +127,81 @@ crosses( 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) -""" - crosses(geom1, geom2)::Bool +#= 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; + over_allow = false, cross_allow = true, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = true, + closed_curve = true, +) -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. +#= 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; + in_allow = false, on_allow = true, out_allow = true, + in_require = true, on_require = false, out_require = true, + closed_line = true, +) -TODO: broken -## Examples -```julia -import GeoInterface as GI, GeometryOps as GO +# # Polygons cross geometries -line1 = GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)]) -line2 = GI.LineString([(-2, 2), (4, 2)]) +#= 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) -GO.crosses(line1, line2) -# output -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 - -# # return int_point && ext_point -# # end - -# function line_crosses_line(line1, line2) -# np2 = GI.npoint(line2) -# if 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) -# te(p, (pa, pb); exclude_boundary) && return true -# end -# end -# end -# return false -# end - -# function line_crosses_poly(line, poly) -# for l in flatten(AbstractCurveTrait, poly) -# intersects(line, l) && return true -# end -# return false -# end - -# function multipoint_crosses_poly(mp, poly) -# int_point = false -# ext_point = false - -# for p in GI.getpoint(mp) -# if point_in_polygon(p, poly) -# int_point = true -# else -# ext_point = true -# end -# int_point && ext_point && return true -# end -# return false -# end + +# # 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( + ::GI.AbstractGeometryTrait, g1 + ::Union{ + GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g2 +) + for sub_g2 in GI.getgeom(g2) + crosses(g1, sub_g2) && return true + end + return false +end + +# # 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.MultiCurveTrait, + 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 \ No newline at end of file diff --git a/src/methods/geom_relations/disjoint.jl b/src/methods/geom_relations/disjoint.jl index 3bca4a0fd..83076e35e 100644 --- a/src/methods/geom_relations/disjoint.jl +++ b/src/methods/geom_relations/disjoint.jl @@ -34,23 +34,26 @@ This is the GeoInterface-compatible implementation. First, we implement a wrapper method that dispatches to the correct implementation based on the geometry trait. -For a point, other points are disjoint if they are not equal to the first point. -than a point can be within a point. For all other geometries, we identify that -the first point of the geometry is outside of the geometry and then make sure -that the two geometries do not intersect. If these conditions are met, the two -geometries are disjoint. - -The code for the specific implementations is in the geom_geom_processors file, -which has generalized code for the within and disjoint functions with a keyword -argument `process`, which is specified to be the `disjoint_process` for the -below functions. +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 +`disjoint` function and arguments g1 and g2, this criteria is as follows: + - points of g1 are not allowed to be in the interior of g2 + - points of g1 are not allowed to be on the boundary of g2 + - points of g1 are allowed to be in the exterior of g2 + - no points required to be in the interior of g2 + - no points of g1 are required to be on the boundary of g2 + - no points of g1 are required to be in the exterior of g2 + +The code for the specific implementations is in the geom_geom_processors file. =# """ disjoint(geom1, geom2)::Bool Return `true` if the first geometry is disjoint from the second geometry. -The interiors and boundaries of both geometries must not intersect. + +Return `true` if the first geometry is disjoint from the second geometry. The +interiors and boundaries of both geometries must not intersect. ## Examples ```jldoctest setup=:(using GeometryOps, GeometryBasics) @@ -64,45 +67,33 @@ GO.disjoint(point, line) true ``` """ -disjoint(g1, g2)::Bool = disjoint(trait(g1), g1, trait(g2), g2) +disjoint(g1, g2)::Bool = _disjoint(trait(g1), g1, trait(g2), g2) + +# # Convert features to geometries disjoint(::FeatureTrait, g1, ::Any, g2)::Bool = disjoint(GI.geometry(g1), g2) disjoint(::Any, g1, t2::FeatureTrait, g2)::Bool = disjoint(g1, geometry(g2)) + # Point disjoint geometries -""" - disjoint(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool -If a point is disjoint from another point, those points must not be equal. If -they are equal then they are not disjoint and return false. -""" -disjoint( +# Point is disjoint from another point if the points are not equal. +_disjoint( ::GI.PointTrait, g1, ::GI.PointTrait, g2, ) = !equals(g1, g2) -""" - disjoint(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool - -If a point is disjoint from a linestring then it is not on any of the -linestring's edges or vertices. If these conditions are met, return true, else -false. -""" -disjoint( +# Point is disjoint from a linestring if it is not on the line's edges/vertices. +_disjoint( ::GI.PointTrait, g1, - ::GI.LineStringTrait, g2, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _point_curve_process( g1, g2; in_allow = false, on_allow = false, out_allow = true, repeated_last_coord = false, ) -""" - disjoint(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool - -If a point is disjoint from a linear ring then it is not on any of the -ring's edges or vertices. If these conditions are met, return true, else false. -""" -disjoint( +# Point is disjoint from a linearring if it is not on the ring's edges/vertices. +_disjoint( ::GI.PointTrait, g1, ::GI.LinearRingTrait, g2, ) = _point_curve_process( @@ -111,14 +102,9 @@ disjoint( repeated_last_coord = true, ) -""" - disjoint(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool - -A point is disjoint from a polygon if it is outside of that polygon. This means -it is not on any edges, vertices, or within the interior. The point can be -within a hole. Return true if those conditions are met, else false. -""" -disjoint( +#= Point is disjoint from a polygon if it is not on any edges, vertices, or +within the polygon's interior. =# +_disjoint( ::GI.PointTrait, g1, ::GI.PolygonTrait, g2, ) = _point_polygon_process( @@ -126,27 +112,21 @@ disjoint( in_allow = false, on_allow = false, out_allow = true, ) -""" - disjoint(trait1::GI.AbstractTrait, g1, trait2::GI.PointTrait, g2)::Bool - -To check if a geometry is disjoint from a point, switch the order of the -arguments to take advantage of point-geometry disjoint methods. -""" -disjoint( - trait1::GI.AbstractTrait, g1, +#= Geometry is disjoint from a point if the point is not in the interior or on +the boundary of the geometry. =# +_disjoint( + trait1::GI.AbstractGeometryTrait, g1, trait2::GI.PointTrait, g2, -) = disjoint(trait2, g2, trait1, g1) +) = _disjoint(trait2, g2, trait1, g1) -# Lines disjoint from geometries -""" - disjoint(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool -Two linestrings are disjoint if they do not share any edges or vertices and if -they do not intersect. If these conditions are met, return true, else false. -""" -disjoint( - ::GI.LineStringTrait, g1, - ::GI.LineStringTrait, g2, +# # Lines disjoint geometries + +#= Linestring is disjoint from another line if they do not share any interior +edge/vertex points or boundary points. =# +_disjoint( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( g1, g2; over_allow = false, cross_allow = false, on_allow = false, out_allow = true, @@ -155,15 +135,10 @@ disjoint( closed_curve = false, ) -""" - disjoint(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool - -A linestring and a linear ring are disjoint if they do not share any edges or -vertices and if they do not intersect. If these conditions are met, return true, -else false. -""" -disjoint( - ::GI.LineStringTrait, g1, +#= Linestring is disjoint from a linearring if they do not share any interior +edge/vertex points or boundary points. =# +_disjoint( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; @@ -173,16 +148,10 @@ disjoint( closed_curve = true, ) -""" - disjoint(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool - -A linestring and a polygon are disjoint if they do not share any edges or -vertices and if the linestring does not pass through the interior of the -polygon, excluding any holes. If these conditions are met, return true, else -false. -""" -disjoint( - ::GI.LineStringTrait, g1, +#= Linestring is disjoint from a polygon if the interior and boundary points of +the line are not in the polygon's interior or on the polygon's boundary. =# +_disjoint( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; @@ -191,37 +160,19 @@ disjoint( closed_line = false, ) -""" - disjoint(trait1::GI.AbstractTrait, g1, trait2::GI.LineStringTrait, g2)::Bool - -To check if a geometry is disjoint from a linestring, switch the order of the -arguments to take advantage of linestring-geometry disjoint methods. -""" -disjoint( - trait1::GI.AbstractTrait, g1, - trait2::GI.LineStringTrait, g2, -) = disjoint(trait2, g2, trait1, g1) - -# Rings disjoint from geometries -""" - disjoint(::GI.LinearRingTrait, g1, ::GI.LineStringTrait, g2)::Bool +#= Geometry is disjoint from a linestring if the line's interior and boundary +points don't intersect with the geometrie's interior and boundary points. =# +_disjoint( + trait1::GI.AbstractGeometryTrait, g1, + trait2::Union{GI.LineTrait, GI.LineStringTrait}, g2, +) = _disjoint(trait2, g2, trait1, g1) -A linear ring and a linestring are disjoint if they do not share any edges or -vertices and if they do not intersect. If these conditions are met, return true, -else false. -""" -disjoint( - trait1::GI.LinearRingTrait, g1, - trait2::GI.LineStringTrait, g2, -) = disjoint(trait2, g2, trait1, g1) -""" - disjoint(::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2)::Bool +# # Rings disjoint geometries -Two linear rings are disjoint if they do not share any edges or vertices and if -they do not intersect. If these conditions are met, return true, else false. -""" -disjoint( +#= Linearrings is disjoint from another linearring if they do not share any +interior edge/vertex points or boundary points.=# +_disjoint( ::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2, ) = _line_curve_process( @@ -232,15 +183,9 @@ disjoint( closed_curve = true, ) -""" - disjoint(::GI.LinearRingTrait, g1, ::GI.PolygonTrait, g2)::Bool - -A linear ring and a polygon are disjoint if they do not share any edges or -vertices and if the linear ring does not pass through the interior of the -polygon, excluding any holes. If these conditions are met, return true, else -false. -""" -disjoint( +#= Linearring is disjoint from a polygon if the interior and boundary points of +the ring are not in the polygon's interior or on the polygon's boundary. =# +_disjoint( ::GI.LinearRingTrait, g1, ::GI.PolygonTrait, g2, ) = _line_polygon_process( @@ -250,86 +195,58 @@ disjoint( closed_line = true, ) -""" - disjoint(trait1::GI.AbstractTrait, g1, trait2::GI.LinearRingTrait, g2)::Bool - -To check if a geometry is disjoint from a linear ring, switch the order of the -arguments to take advantage of linear ring-geometry disjoint methods. -""" -disjoint( - trait1::GI.AbstractTrait, g1, +#= Geometry is disjoint from a linearring if the ring's interior and boundary +points don't intersect with the geometrie's interior and boundary points. =# +_disjoint( + trait1::GI.AbstractGeometryTrait, g1, trait2::GI.LinearRingTrait, g2, -) = disjoint(trait2, g2, trait1, g1) +) = _disjoint(trait2, g2, trait1, g1) -# Polygon disjoint from geometries -""" - disjoint(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool -Two polygons are disjoint if they do not share any edges or vertices and if -their interiors do not intersect, excluding any holes. If these conditions are -met, return true, else false. -""" -function disjoint( +# # Polygon disjoint geometries + +#= Polygon is disjoint from another polygon if they do not share any edges or +vertices and if their interiors do not intersect, excluding any holes. =# +_disjoint( ::GI.PolygonTrait, g1, - ::GI.PolygonTrait, g2; + ::GI.PolygonTrait, g2, +) = _polygon_polygon_process( + g1, g2; + in_allow = false, on_allow = false, out_allow = true, + in_require = false, on_require = false, out_require = false, ) - ext1 = GI.getexterior(g1) - e1_in_e2, e1_on_e2, e1_out_e2 = _line_filled_curve_interactions( - ext1, GI.getexterior(g2); - closed_line = true, - ) - e1_on_e2 && return false - !e1_in_e2 && return true - - for h2 in GI.gethole(g2) - if e1_in_e2 # h2 could be outside of e1, but inside of e2 - h2_in_e1, h2_on_e1, h2_out_e1 = _line_filled_curve_interactions( - h2, ext1; - closed_line = true, - ) - (h2_in_e1 || h2_on_e1) && return false - - if h2_out_e1 - c1_val = point_filled_curve_orientation(centroid(ext1), h2) - c1_val == point_in && return true # e1 is within h2 - end - end - end - return false -end -# Geometries within multipolygons -""" - disjoint(::GI.AbstractTrait, g1, ::GI.MultiPolygonTrait, g2)::Bool -A geometry is disjoint from a multipolygon if it is disjoint from all of the -polygons that make up the multipolygon. Return true if these conditions are met, -else false. -""" -function disjoint(::GI.AbstractTrait, g1, ::GI.MultiPolygonTrait, g2) - for poly in GI.getpolygon(g2) - if !disjoint(g1, poly) - return false - end +# # Geometries disjoint multi-geometry/geometry collections + +#= Geometry is disjoint from a multi-geometry or a collection if all of the +elements of the collection are disjoint from the geometry. =# +function _disjoint( + ::GI.AbstractGeometryTrait, g1 + ::Union{ + GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g2 +) + for sub_g2 in GI.getgeom(g2) + !disjoint(g1, sub_g2) && return false end return true end -""" - disjoint(::GI.MultiPolygonTrait, g1, ::GI.MultiPolygonTrait, g2)::Bool +# # Multi-geometry/geometry collections coveredby geometries -A multipolygon is disjoint from a multipolygon if every polygon in the first -multipolygon is disjoint from all of the polygons in the second multipolygon. -Return true if these conditions are met, else false. -""" -function disjoint(::GI.MultiPolygonTrait, g1, ::GI.MultiPolygonTrait, g2) - for poly1 in GI.getpolygon(g1) - for poly2 in GI.getpolygon(g2) - if !disjoint(poly1, poly2) - return false - end - end +#= Multi-geometry or a geometry collection is covered by a geometry if all +elements of the collection are covered by the geometry. =# +function _disjoint( + ::Union{ + GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g1, + ::GI.AbstractGeometryTrait, g2 +) + for sub_g1 in GI.getgeom(g1) + !disjoint(sub_g1, g2) && return false end return true end - diff --git a/src/methods/geom_relations/intersects.jl b/src/methods/geom_relations/intersects.jl index e9b74eb53..3c46107a8 100644 --- a/src/methods/geom_relations/intersects.jl +++ b/src/methods/geom_relations/intersects.jl @@ -286,8 +286,8 @@ Calculates the intersection between two line segments. Return nothing if there isn't one. """ function intersection( - trait_a::GI.AbstractTrait, geom_a, - trait_b::GI.AbstractTrait, geom_b, + trait_a::GI.AbstractGeometryTrait, geom_a, + trait_b::GI.AbstractGeometryTrait, geom_b, ) @assert( false, @@ -326,7 +326,7 @@ line segments, line strings, linear rings, polygons, and multipolygons. If no intersection points were possible given geometry extents, return nothing. If none are found, return an empty list. """ -function intersection_points(::GI.AbstractTrait, a, ::GI.AbstractTrait, b) +function intersection_points(::GI.AbstractGeometryTrait, a, ::GI.AbstractGeometryTrait, b) # Check if the geometries extents even overlap Extents.intersects(GI.extent(a), GI.extent(b)) || return nothing # Create a list of edges from the two input geometries diff --git a/src/methods/geom_relations/overlaps.jl b/src/methods/geom_relations/overlaps.jl index 64258f83d..03746c8f9 100644 --- a/src/methods/geom_relations/overlaps.jl +++ b/src/methods/geom_relations/overlaps.jl @@ -80,7 +80,7 @@ overlaps(geom1, geom2)::Bool = overlaps( For any non-specified pair, all have non-matching dimensions, return false. """ -overlaps(::GI.AbstractTrait, geom1, ::GI.AbstractTrait, geom2) = false +overlaps(::GI.AbstractGeometryTrait, geom1, ::GI.AbstractGeometryTrait, geom2) = false """ overlaps( diff --git a/src/methods/geom_relations/touches.jl b/src/methods/geom_relations/touches.jl index 137745e1e..a5d3bf0a5 100644 --- a/src/methods/geom_relations/touches.jl +++ b/src/methods/geom_relations/touches.jl @@ -64,7 +64,7 @@ To check if a geometry is touches by a point, switch the order of the arguments to take advantage of point-geometry touches methods. """ touches( - trait1::GI.AbstractTrait, g1, + trait1::GI.AbstractGeometryTrait, g1, trait2::GI.PointTrait, g2, ) = touches(trait2, g2, trait1, g1) diff --git a/src/methods/geom_relations/within.jl b/src/methods/geom_relations/within.jl index b492e4737..e9f184b91 100644 --- a/src/methods/geom_relations/within.jl +++ b/src/methods/geom_relations/within.jl @@ -82,7 +82,7 @@ within(::Any, g1, t2::GI.FeatureTrait, g2) = within(g1, GI.geometry(g2)) For any non-specified pair, g1 cannot be within g2 as g2 is of a higher dimension than g1. Return false. """ -within(::GI.AbstractTrait, g1, ::GI.AbstractTrait, g2) = false +within(::GI.AbstractGeometryTrait, g1, ::GI.AbstractGeometryTrait, g2) = false # Points within geometries """ @@ -322,7 +322,7 @@ within( A geometry is within a multipolygon if it is within one of the polygons that make up the multipolygon. Return true if these conditions are met, else false. """ -function within(::GI.AbstractTrait, g1, ::GI.MultiPolygonTrait, g2) +function within(::GI.AbstractGeometryTrait, g1, ::GI.MultiPolygonTrait, g2) for poly in GI.getpolygon(g2) if within(g1, poly) return true From 380a3a9b93c87f620e31da5633a402def5857970 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Fri, 29 Dec 2023 14:51:09 -0800 Subject: [PATCH 30/33] Done with source code other than overlaps --- Project.toml | 2 + src/methods/geom_relations/contains.jl | 2 +- src/methods/geom_relations/coveredby.jl | 50 +-- src/methods/geom_relations/covers.jl | 4 +- src/methods/geom_relations/crosses.jl | 42 ++- src/methods/geom_relations/disjoint.jl | 57 ++- src/methods/geom_relations/equals.jl | 4 +- .../geom_relations/geom_geom_processors.jl | 1 - src/methods/geom_relations/intersects.jl | 143 +------- src/methods/geom_relations/overlaps.jl | 108 ++++-- src/methods/geom_relations/touches.jl | 258 ++++++++++---- src/methods/geom_relations/within.jl | 334 +++++++----------- test/methods/bools.jl | 29 +- test/methods/geom_relations/contains.jl | 116 ++++-- test/methods/geom_relations/coveredby.jl | 103 +++++- 15 files changed, 694 insertions(+), 559 deletions(-) diff --git a/Project.toml b/Project.toml index 35b23ebe6..196b887ea 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,8 @@ authors = ["Anshul Singhvi and contributors"] version = "0.0.1-DEV" [deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +Cthulhu = "f68482b8-f384-11e8-15f7-abe071a5a75f" ExactPredicates = "429591f6-91af-11e9-00e2-59fbe8cec110" GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f" GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" diff --git a/src/methods/geom_relations/contains.jl b/src/methods/geom_relations/contains.jl index 08146e280..2bd6a391a 100644 --- a/src/methods/geom_relations/contains.jl +++ b/src/methods/geom_relations/contains.jl @@ -61,4 +61,4 @@ GO.contains(line, point) true ``` """ -contains(g1, g2)::Bool = GeometryOps.within(g2, g1) +contains(g1, g2) = GeometryOps.within(g2, g1) diff --git a/src/methods/geom_relations/coveredby.jl b/src/methods/geom_relations/coveredby.jl index ccbcea539..7fd48f679 100644 --- a/src/methods/geom_relations/coveredby.jl +++ b/src/methods/geom_relations/coveredby.jl @@ -50,6 +50,10 @@ this file determine if the given geometries meet a set of criteria. For the The code for the specific implementations is in the geom_geom_processors file. =# +const COVEREDBY_ALLOWS = (in_allow = true, on_allow = true, out_allow = false) +const COVEREDBY_CURVE_ALLOWS = (over_allow = true, cross_allow = true, on_allow = true, out_allow = false) +const COVEREDBY_CURVE_REQUIRES = (in_require = false, on_require = false, out_require = false) +const COVEREDBY_POLYGON_REQUIRES = (in_require = true, on_require = false, out_require = false,) """ coveredby(g1, g2)::Bool @@ -94,7 +98,7 @@ _coveredby( ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _point_curve_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + COVEREDBY_ALLOWS..., repeated_last_coord = false, ) @@ -104,7 +108,7 @@ _coveredby( ::GI.LinearRingTrait, g2, ) = _point_curve_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + COVEREDBY_ALLOWS..., repeated_last_coord = true, ) @@ -114,12 +118,12 @@ _coveredby( ::GI.PolygonTrait, g2, ) = _point_polygon_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, + COVEREDBY_ALLOWS..., ) # Points cannot cover any geometry other than points _coveredby( - ::GI.AbstractGeometryTrait, g1, + ::Union{GI.AbstractCurveTrait, GI.PolygonTrait}, g1, ::GI.PointTrait, g2, ) = false @@ -133,8 +137,8 @@ _coveredby( ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( g1, g2; - over_allow = true, cross_allow = true, on_allow = true, out_allow = false, - in_require = false, on_require = false, out_require = false, + COVEREDBY_CURVE_ALLOWS..., + COVEREDBY_CURVE_REQUIRES..., closed_line = false, closed_curve = false, ) @@ -146,8 +150,8 @@ _coveredby( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - over_allow = true, cross_allow = true, on_allow = true, out_allow = false, - in_require = false, on_require = false, out_require = false, + COVEREDBY_CURVE_ALLOWS..., + COVEREDBY_CURVE_REQUIRES..., closed_line = false, closed_curve = true, ) @@ -159,8 +163,8 @@ _coveredby( ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, - in_require = false, on_require = false, out_require = false, + COVEREDBY_ALLOWS..., + COVEREDBY_CURVE_REQUIRES..., closed_line = false, ) @@ -173,8 +177,8 @@ _coveredby( ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( g1, g2; - over_allow = true, cross_allow = true, on_allow = true, out_allow = false, - in_require = false, on_require = false, out_require = false, + COVEREDBY_CURVE_ALLOWS..., + COVEREDBY_CURVE_REQUIRES..., closed_line = true, closed_curve = false, ) @@ -186,8 +190,8 @@ _coveredby( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - over_allow = true, cross_allow = true, on_allow = true, out_allow = false, - in_require = false, on_require = false, out_require = false, + COVEREDBY_CURVE_ALLOWS..., + COVEREDBY_CURVE_REQUIRES..., closed_line = true, closed_curve = true, ) @@ -199,8 +203,8 @@ _coveredby( ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, - in_require = true, on_require = false, out_require = false, + COVEREDBY_ALLOWS..., + COVEREDBY_CURVE_REQUIRES..., closed_line = true, ) @@ -215,8 +219,8 @@ _coveredby( ::GI.PolygonTrait, g2, ) = _polygon_polygon_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, - in_require = true, on_require = false, out_require = false, + COVEREDBY_ALLOWS..., + COVEREDBY_POLYGON_REQUIRES..., ) # Polygons cannot covered by any curves @@ -231,11 +235,11 @@ _coveredby( #= Geometry is covered by a multi-geometry or a collection if one of the elements of the collection cover the geometry. =# function _coveredby( - ::GI.AbstractGeometryTrait, g1 + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1, ::Union{ - GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, GI.MultiPolygonTrait, GI.GeometryCollectionTrait, - }, g2 + }, g2, ) for sub_g2 in GI.getgeom(g2) coveredby(g1, sub_g2) && return true @@ -249,10 +253,10 @@ end elements of the collection are covered by the geometry. =# function _coveredby( ::Union{ - GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, GI.MultiPolygonTrait, GI.GeometryCollectionTrait, }, g1, - ::GI.AbstractGeometryTrait, g2 + ::GI.AbstractGeometryTrait, g2, ) for sub_g1 in GI.getgeom(g1) !coveredby(sub_g1, g2) && return false diff --git a/src/methods/geom_relations/covers.jl b/src/methods/geom_relations/covers.jl index a9afb778f..714d14643 100644 --- a/src/methods/geom_relations/covers.jl +++ b/src/methods/geom_relations/covers.jl @@ -11,7 +11,7 @@ boundaries must be covered by the "covering" geometry's interior and boundaries. The interiors do not need to overlap. To provide an example, consider these two lines: -```@example cshape +```@example covers using GeometryOps using GeometryOps.GeometryBasics using Makie @@ -25,7 +25,7 @@ f, a, p = lines([p1, p2]) scatter!(p1, color = :red) ``` -```@example cshape +```@example covers covers(l1, p1) # returns true covers(p1, l1) # returns false ``` diff --git a/src/methods/geom_relations/crosses.jl b/src/methods/geom_relations/crosses.jl index d767b43dc..8ff585333 100644 --- a/src/methods/geom_relations/crosses.jl +++ b/src/methods/geom_relations/crosses.jl @@ -16,7 +16,7 @@ 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. To provide an example, consider these two lines: -```@example cshape +```@example crosses using GeometryOps using GeometryOps.GeometryBasics using Makie @@ -29,7 +29,7 @@ f, a, p = lines(l1) lines!(l2) ``` We can see that these two lines cross at their midpoints. -```@example cshape +```@example crosses crosses(l1, l2) # true ``` @@ -54,6 +54,10 @@ this file determine if the given geometries meet a set of criteria. For the 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 = false, on_allow = true, out_allow = true) +const CROSSES_REQUIRES = (in_require = true, on_require = false, out_require = true) + """ crosses(geom1, geom2)::Bool @@ -84,7 +88,10 @@ _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(::GI.AbstractGeometryTrait, g1, ::GI.AbstractGeometryTrait, g2) = false +_crosses( + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1, + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g2, +) = false # # Lines cross geometries @@ -96,7 +103,7 @@ _crosses( ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( g1, g2; - over_allow = false, cross_allow = true, on_allow = true, out_allow = true, + CROSSES_CURVE_ALLOWS..., in_require = true, on_require = false, out_require = true, closed_line = false, closed_curve = false, @@ -109,8 +116,8 @@ _crosses( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - over_allow = false, cross_allow = true, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, + CROSSES_CURVE_ALLOWS..., + CROSSES_REQUIRES..., closed_line = false, closed_curve = true, ) @@ -122,11 +129,12 @@ _crosses( ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - in_allow = false, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, + CROSSES_POLYGON_ALLOWS..., + CROSSES_REQUIRES..., closed_line = false, ) + # # Rings cross geometries #= Linearring crosses a linestring if the intersection of the line and ring is @@ -143,8 +151,8 @@ _crosses( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - over_allow = false, cross_allow = true, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, + CROSSES_CURVE_ALLOWS..., + CROSSES_REQUIRES..., closed_line = true, closed_curve = true, ) @@ -156,8 +164,8 @@ _crosses( ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - in_allow = false, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, + CROSSES_POLYGON_ALLOWS..., + CROSSES_REQUIRES..., closed_line = true, ) @@ -177,11 +185,11 @@ _crosses( #= Geometry crosses a multi-geometry or a collection if the geometry crosses one of the elements of the collection. =# function _crosses( - ::GI.AbstractGeometryTrait, g1 + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1, ::Union{ - GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, GI.MultiPolygonTrait, GI.GeometryCollectionTrait, - }, g2 + }, g2, ) for sub_g2 in GI.getgeom(g2) crosses(g1, sub_g2) && return true @@ -195,10 +203,10 @@ end the collection crosses the geometry. =# function _crosses( ::Union{ - GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, GI.MultiPolygonTrait, GI.GeometryCollectionTrait, }, g1, - ::GI.AbstractGeometryTrait, g2 + ::GI.AbstractGeometryTrait, g2, ) for sub_g1 in GI.getgeom(g1) crosses(sub_g1, g2) && return true diff --git a/src/methods/geom_relations/disjoint.jl b/src/methods/geom_relations/disjoint.jl index 83076e35e..ad923d7c7 100644 --- a/src/methods/geom_relations/disjoint.jl +++ b/src/methods/geom_relations/disjoint.jl @@ -8,7 +8,7 @@ The disjoint function checks if one geometry is outside of another geometry, without sharing any boundaries or interiors. To provide an example, consider these two lines: -```@example cshape +```@example disjoint using GeometryOps using GeometryOps.GeometryBasics using Makie @@ -23,7 +23,7 @@ scatter!(GI.getpoint(l2), color = :orange) ``` We can see that none of the edges or vertices of l1 interact with l2 so they are disjoint. -```@example cshape +```@example disjoint disjoint(l1, l2) # returns true ``` @@ -47,6 +47,9 @@ this file determine if the given geometries meet a set of criteria. For the The code for the specific implementations is in the geom_geom_processors file. =# +const DISJOINT_ALLOWS = (in_allow = false, on_allow = false, out_allow = true) +const DISJOINT_CURVE_ALLOWS = (over_allow = false, cross_allow = false, on_allow = false, out_allow = true) +const DISJOINT_REQUIRES = (in_require = false, on_require = false, out_require = false) """ disjoint(geom1, geom2)::Bool @@ -74,7 +77,7 @@ disjoint(::FeatureTrait, g1, ::Any, g2)::Bool = disjoint(GI.geometry(g1), g2) disjoint(::Any, g1, t2::FeatureTrait, g2)::Bool = disjoint(g1, geometry(g2)) -# Point disjoint geometries +# # Point disjoint geometries # Point is disjoint from another point if the points are not equal. _disjoint( @@ -88,7 +91,7 @@ _disjoint( ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _point_curve_process( g1, g2; - in_allow = false, on_allow = false, out_allow = true, + DISJOINT_ALLOWS..., repeated_last_coord = false, ) @@ -98,7 +101,7 @@ _disjoint( ::GI.LinearRingTrait, g2, ) = _point_curve_process( g1, g2; - in_allow = false, on_allow = false, out_allow = true, + DISJOINT_ALLOWS..., repeated_last_coord = true, ) @@ -109,13 +112,13 @@ _disjoint( ::GI.PolygonTrait, g2, ) = _point_polygon_process( g1, g2; - in_allow = false, on_allow = false, out_allow = true, + DISJOINT_ALLOWS..., ) #= Geometry is disjoint from a point if the point is not in the interior or on the boundary of the geometry. =# _disjoint( - trait1::GI.AbstractGeometryTrait, g1, + trait1::Union{GI.AbstractCurveTrait, GI.PolygonTrait}, g1, trait2::GI.PointTrait, g2, ) = _disjoint(trait2, g2, trait1, g1) @@ -129,8 +132,8 @@ _disjoint( ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( g1, g2; - over_allow = false, cross_allow = false, on_allow = false, out_allow = true, - in_require = false, on_require = false, out_require = false, + DISJOINT_CURVE_ALLOWS..., + DISJOINT_REQUIRES..., closed_line = false, closed_curve = false, ) @@ -142,8 +145,8 @@ _disjoint( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - over_allow = false, cross_allow = false, on_allow = false, out_allow = true, - in_require = false, on_require = false, out_require = false, + DISJOINT_CURVE_ALLOWS..., + DISJOINT_REQUIRES..., closed_line = false, closed_curve = true, ) @@ -155,15 +158,15 @@ _disjoint( ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - in_allow = false, on_allow = false, out_allow = true, - in_require = false, on_require = false, out_require = true, + DISJOINT_ALLOWS..., + DISJOINT_REQUIRES..., closed_line = false, ) #= Geometry is disjoint from a linestring if the line's interior and boundary points don't intersect with the geometrie's interior and boundary points. =# _disjoint( - trait1::GI.AbstractGeometryTrait, g1, + trait1::Union{GI.LinearRingTrait, GI.PolygonTrait}, g1, trait2::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _disjoint(trait2, g2, trait1, g1) @@ -177,8 +180,8 @@ _disjoint( ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - over_allow = false, cross_allow = false, on_allow = false, out_allow = true, - in_require = false, on_require = false, out_require = false, + DISJOINT_CURVE_ALLOWS..., + DISJOINT_REQUIRES..., closed_line = true, closed_curve = true, ) @@ -190,19 +193,11 @@ _disjoint( ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - in_allow = false, on_allow = false, out_allow = true, - in_require = false, on_require = false, out_require = true, + DISJOINT_ALLOWS..., + DISJOINT_REQUIRES..., closed_line = true, ) -#= Geometry is disjoint from a linearring if the ring's interior and boundary -points don't intersect with the geometrie's interior and boundary points. =# -_disjoint( - trait1::GI.AbstractGeometryTrait, g1, - trait2::GI.LinearRingTrait, g2, -) = _disjoint(trait2, g2, trait1, g1) - - # # Polygon disjoint geometries #= Polygon is disjoint from another polygon if they do not share any edges or @@ -212,8 +207,8 @@ _disjoint( ::GI.PolygonTrait, g2, ) = _polygon_polygon_process( g1, g2; - in_allow = false, on_allow = false, out_allow = true, - in_require = false, on_require = false, out_require = false, + DISJOINT_ALLOWS..., + DISJOINT_REQUIRES..., ) @@ -222,9 +217,9 @@ _disjoint( #= Geometry is disjoint from a multi-geometry or a collection if all of the elements of the collection are disjoint from the geometry. =# function _disjoint( - ::GI.AbstractGeometryTrait, g1 + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1 ::Union{ - GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, GI.MultiPolygonTrait, GI.GeometryCollectionTrait, }, g2 ) @@ -240,7 +235,7 @@ end elements of the collection are covered by the geometry. =# function _disjoint( ::Union{ - GI.MultiPointTrait, GI.MultiCurveTrait, + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, GI.MultiPolygonTrait, GI.GeometryCollectionTrait, }, g1, ::GI.AbstractGeometryTrait, g2 diff --git a/src/methods/geom_relations/equals.jl b/src/methods/geom_relations/equals.jl index f4b710b81..c797432d5 100644 --- a/src/methods/geom_relations/equals.jl +++ b/src/methods/geom_relations/equals.jl @@ -9,7 +9,7 @@ The equals function checks if two geometries are equal. They are equal if they share the same set of points and edges to define the same shape. To provide an example, consider these two lines: -```@example cshape +```@example equals using GeometryOps using GeometryOps.GeometryBasics using Makie @@ -24,7 +24,7 @@ scatter!(GI.getpoint(l2), color = :orange) ``` We can see that the two lines do not share a commen set of points and edges in the plot, so they are not equal: -```@example cshape +```@example equals equals(l1, l2) # returns false ``` diff --git a/src/methods/geom_relations/geom_geom_processors.jl b/src/methods/geom_relations/geom_geom_processors.jl index c4d5092d0..ae13f1d55 100644 --- a/src/methods/geom_relations/geom_geom_processors.jl +++ b/src/methods/geom_relations/geom_geom_processors.jl @@ -678,7 +678,6 @@ function _polygon_polygon_process( return in_req_met && on_req_met && out_req_met end - function _point_in_extent(p, extent::Extents.Extent) (x1, x2), (y1, y2) = extent.X, extent.Y return x1 ≤ GI.x(p) ≤ x2 && y1 ≤ GI.y(p) ≤ y2 diff --git a/src/methods/geom_relations/intersects.jl b/src/methods/geom_relations/intersects.jl index 3c46107a8..834745a33 100644 --- a/src/methods/geom_relations/intersects.jl +++ b/src/methods/geom_relations/intersects.jl @@ -5,7 +5,10 @@ export intersects, intersection, intersection_points #= ## What is `intersects` vs `intersection` vs `intersection_points`? -The `intersects` methods check whether two geometries intersect with each other. +The intersects function checks if a given geometry intersects with another +geometry, or in other words, the either the interiors or boundaries of the two +geometries intersect. + The `intersection` methods return the geometry intersection between the two input geometries. The `intersection_points` method returns a list of intersection points between two geometries. @@ -44,18 +47,16 @@ f This is the GeoInterface-compatible implementation. -First, we implement a wrapper method for intersects, intersection, and -intersection_points that dispatches to the correct implementation based on the -geometry trait. The two underlying helper functions that are widely used in all -geometry dispatches are _line_intersects, which determines if two line segments -intersect and _intersection_point which determines the intersection point -between two line segments. +Given that intersects is the exact opposite of disjoint, we simply pass the two +inputs variables, swapped in order, to disjoint. =# """ intersects(geom1, geom2)::Bool -Check if two geometries intersect, returning true if so and false otherwise. +Return true if the interiors or boundaries of the two geometries interact. + +`intersects` returns the exact opposite result of `disjoint`. ## Example @@ -70,135 +71,9 @@ GO.intersects(line1, line2) true ``` """ -# intersects(geom1, geom2) = intersects( -# GI.trait(geom1), -# geom1, -# GI.trait(geom2), -# geom2 -# ) intersects(geom1, geom2) = !disjoint(geom1, geom2) -# Points intersects geometries -""" - intersects(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool - -If a point is intersects another point, then those points must be equal. If they are -not equal, then they are not intersects and return false. -""" -# intersects( -# ::GI.PointTrait, g1, -# ::GI.PointTrait, g2, -# ) = equals(g1, g2) - - -""" - intersects(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool - -A point is intersects a line string if it is on a vertex or an edge of that -linestring, excluding the start and end vertex if the linestring is not closed. -Return true if those conditions are met, else false. -""" -# intersects( -# ::GI.PointTrait, g1, -# ::GI.LineStringTrait, g2, -# ) = _point_curve_process( -# g1, g2; -# in_allow = true, on_allow = true, out_allow = false, -# repeated_last_coord = false, -# ) - -""" - intersects(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool - -A point is intersects a linear ring if it is on a vertex or an edge of that -linear ring. Return true if those conditions are met, else false. -""" -# intersects( -# ::GI.PointTrait, g1, -# ::GI.LinearRingTrait, g2, -# ) = _point_curve_process( -# g1, g2; -# in_allow = true, on_allow = true, out_allow = false, -# repeated_last_coord = true, -# ) - -""" - intersects(trait1::GI.AbstractTrait, g1, trait2::GI.PointTrait, g2)::Bool - -To check if a geometry intersects with a point, switch the order of the -arguments to take advantage of point-geometry intersects methods. -""" -# intersects( -# trait1::GI.AbstractTrait, g1, -# trait2::GI.PointTrait, g2, -# ) = intersects(trait2, g2, trait1, g1) - -""" - intersects(::GI.LineTrait, a, ::GI.LineTrait, b)::Bool - -Returns true if two line segments intersect and false otherwise. -""" -# function intersects(::GI.LineTrait, a, ::GI.LineTrait, b) -# a1 = _tuple_point(GI.getpoint(a, 1)) -# a2 = _tuple_point(GI.getpoint(a, 2)) -# b1 = _tuple_point(GI.getpoint(b, 1)) -# b2 = _tuple_point(GI.getpoint(b, 2)) -# meet_type = ExactPredicates.meet(a1, a2, b1, b2) -# return meet_type == 0 || meet_type == 1 -# end - -""" - intersects(::GI.AbstractTrait, a, ::GI.AbstractTrait, b)::Bool - -Returns true if two geometries intersect with one another and false -otherwise. For all geometries but lines, convert the geometry to a list of edges -and cross compare the edges for intersections. -""" -# function intersects( -# trait_a::GI.AbstractTrait, a_geom, -# trait_b::GI.AbstractTrait, b_geom, -# ) edges_a, edges_b = map(sort! ∘ to_edges, (a_geom, b_geom)) -# return _line_intersects(edges_a, edges_b) || -# within(trait_a, a_geom, trait_b, b_geom) || -# within(trait_b, b_geom, trait_a, a_geom) -# end - -""" - _line_intersects( - edges_a::Vector{Edge}, - edges_b::Vector{Edge} - )::Bool - -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 - 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 - -""" - _line_intersects( - edge_a::Edge, - edge_b::Edge, - )::Bool - -Returns true if there is at least one intersection between two edges. -""" -function _line_intersects(edge_a::Edge, edge_b::Edge) - meet_type = ExactPredicates.meet(edge_a..., edge_b...) - return meet_type == 0 || meet_type == 1 -end - """ intersection(geom_a, geom_b)::Union{Tuple{::Real, ::Real}, ::Nothing} diff --git a/src/methods/geom_relations/overlaps.jl b/src/methods/geom_relations/overlaps.jl index 03746c8f9..ad49d6314 100644 --- a/src/methods/geom_relations/overlaps.jl +++ b/src/methods/geom_relations/overlaps.jl @@ -5,16 +5,17 @@ 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 cshape +```@example overlaps using GeometryOps using GeometryOps.GeometryBasics using Makie @@ -28,7 +29,7 @@ lines!(GI.getpoint(l2), color = :orange) scatter!(GI.getpoint(l2), color = :orange) ``` We can see that the two lines overlap in the plot: -```@example cshape +```@example overlaps overlap(l1, l2) ``` @@ -37,25 +38,29 @@ 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. =# """ 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. ## Examples ```jldoctest @@ -68,29 +73,32 @@ 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) -""" - overlaps(::GI.AbstractTrait, geom1, ::GI.AbstractTrait, geom2)::Bool -For any non-specified pair, all have non-matching dimensions, return false. -""" -overlaps(::GI.AbstractGeometryTrait, geom1, ::GI.AbstractGeometryTrait, geom2) = false +# # 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 + + +# # Point disjoint geometries + +# Point is disjoint from another point if the points are not equal. +_disjoint( + ::GI.PointTrait, g1, + ::GI.PointTrait, g2, +) = !equals(g1, g2) -""" - 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, @@ -343,3 +351,25 @@ function _overlaps( b2_in = point_segment_orientation(b2, a1, a2) == point_in return (a1_in ⊻ a2_in) && (b1_in ⊻ b2_in) end + + +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 \ No newline at end of file diff --git a/src/methods/geom_relations/touches.jl b/src/methods/geom_relations/touches.jl index a5d3bf0a5..ab7e556a2 100644 --- a/src/methods/geom_relations/touches.jl +++ b/src/methods/geom_relations/touches.jl @@ -1,29 +1,98 @@ -""" -they have at least one point in common, but their interiors do not intersect. -""" -touches(g1, g2)::Bool = touches(trait(g1), g1, trait(g2), g2) +# # Touches + +export touches + +#= +## What is touches? + +The touches function checks if one geometry touches another geometry. In other +words, the interiors of the two geometries don't interact, but one of the +geometries must have a boundary point that interacts with either the other +geometies interior or boundary. + + +To provide an example, consider these two lines: +```@example touches +using GeometryOps +using GeometryOps.GeometryBasics +using Makie +using CairoMakie + +l1 = Line([Point(0.0, 0.0), Point(1.0, 0.0)]) +l2 = Line([Point(1.0, 0.0), Point(1.0, -1.0)]) + +f, a, p = lines(l1) +lines!(l2) +``` +We can see that these two lines touch only at their endpoints. +```@example touches +touches(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 +`touches` function and arguments g1 and g2, this criteria is as follows: + - points of g1 are not 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 + - no points of g1 are required to be in the interior of g2 + - at least one point of g1 is required to be on the boundary of g2 + - no points 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 TOUCHES_POINT_ALLOWED = (in_allow = false, on_allow = true, out_allow = false) +const TOUCHES_CURVE_ALLOWED = (over_allow = false, cross_allow = false, on_allow = true, out_allow = true) +const TOUCHES_POLYGON_ALLOWS = (in_allow = false, on_allow = true, out_allow = true) +const TOUCHES_REQUIRED = (in_require = false, on_require = true, out_require = false) """ - touches(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool + touches(geom1, geom2)::Bool + +Return `true` if the first geometry touches the second geometry. In other words, +the two interiors cannot interact, but one of the geometries must have a +boundary point that interacts with either the other geometies interior or +boundary. -Two points cannot touch. If they are the same point then their interiors -intersect and if they are different points then they don't share any points. +## Examples +```jldoctest setup=:(using GeometryOps, GeometryBasics) +import GeometryOps as GO, GeoInterface as GI + +l1 = GI.Line([(0.0, 0.0), (1.0, 0.0)]) +l2 = GI.Line([(0.5, 1.0), (0.5, -1.0)]) + +GO.crosses(l1, l2) +# output +true +``` """ -touches( +touches(g1, g2)::Bool = _touches(trait(g1), g1, trait(g2), g2) + +# # Convert features to geometries +_touches(::GI.FeatureTrait, g1, ::Any, g2) = touches(GI.geometry(g1), g2) +_touches(::Any, g1, t2::GI.FeatureTrait, g2) = touches(g1, GI.geometry(g2)) + + +# # Point touches geometries + +# Point cannot touch another point as if they are equal, interiors interact +_touches( ::GI.PointTrait, g1, ::GI.PointTrait, g2, ) = false -""" - touches(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool - -If a point touches a linestring if it equal to either the first of last point of -the linestring, which make up the linestrings boundaries. If the first and last -point are equal, closing the linestring, then no point can touch the linestring. -""" -function touches( +# Point touches a linestring if it equal to the first of last point of the line +function _touches( ::GI.PointTrait, g1, - ::GI.LineStringTrait, g2, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) n = GI.npoint(g2) p1 = GI.getpoint(g2, 1) @@ -32,74 +101,139 @@ function touches( return equals(g1, p1) || equals(g1, pn) end -""" - touches(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool - -If a point cannot 'touch' a linear ring given that the linear ring has no -boundary points. Since the whole ring is "interior", a point cannot touch it. -""" -touches( +# Point cannot 'touch' a linearring given that the ring has no boundary points +_touches( ::GI.PointTrait, g1, ::GI.LinearRingTrait, g2, ) = false -""" - touches(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool - -A point touches a polygon if it is on the boundary of that polygon. -Return true if those conditions are met, else false. -""" -touches( +# Point touches a polygon if it is on the boundary of that polygon +_touches( ::GI.PointTrait, g1, ::GI.PolygonTrait, g2, ) = _point_polygon_process( g1, g2; - in_allow = false, on_allow = true, out_allow = false, + TOUCHES_POINT_ALLOWED..., ) -""" - touches(trait1::GI.AbstractTrait, g1, trait2::GI.PointTrait, g2)::Bool - -To check if a geometry is touches by a point, switch the order of the -arguments to take advantage of point-geometry touches methods. -""" -touches( - trait1::GI.AbstractGeometryTrait, g1, +#= Geometry touches a point if the point is on the geometry boundary. =# +_touches( + trait1::Union{GI.AbstractCurveTrait, GI.PolygonTrait}, g1, trait2::GI.PointTrait, g2, -) = touches(trait2, g2, trait1, g1) +) = _touches(trait2, g2, trait1, g1) -# Lines touching geometries -""" - touches(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool -A line string touches another linestring only if at least one endpoints -(boundary point) of one of the linestrings intersects with the other linestring -""" -touches( - ::GI.LineStringTrait, g1, - ::GI.LineStringTrait, g2, +# # Lines touching geometries + +#= Linestring touches another line if at least one bounday point interacts with +the bounday of interior of the other line, but the interiors don't interact. =# +_touches( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( g1, g2; - over_allow = false, cross_allow = false, on_allow = true, out_allow = true, - in_require = false, on_require = true, out_require = false, + TOUCHES_CURVE_ALLOWED..., + TOUCHES_REQUIRED..., closed_line = false, closed_curve = false, ) -""" - touches(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool -A linestring touches a linear ring if the vertices and edges of the -linestring are touches the linear ring. Return true if those conditions are met, -else false. -""" -touches( - ::GI.LineStringTrait, g1, +#= Linestring touches a linearring if at least one of the boundary points of the +line interacts with the linear ring, but their interiors can't interact. =# +_touches( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - over_allow = false, cross_allow = false, on_allow = true, out_allow = true, - in_require = false, on_require = true, out_require = false, + TOUCHES_CURVE_ALLOWED..., + TOUCHES_REQUIRED..., closed_line = false, closed_curve = true, ) + +#= Linestring touches a polygon if at least one of the boundary points of the +line interacts with the boundary of the polygon. =# +_touches( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::GI.PolygonTrait, g2, +) = _line_polygon_process( + g1, g2; + TOUCHES_POLYGON_ALLOWS..., + TOUCHES_REQUIRES..., + closed_line = false, +) + + +# # Rings touch geometries + +#= Linearring touches a linestring if at least one of the boundary points of the +line interacts with the linear ring, but their interiors can't interact. =# +_touches( + trait1::GI.LinearRingTrait, g1, + trait2::Union{GI.LineTrait, GI.LineStringTrait}, g2, +) = _touches(trait2, g2, trait1, g1) + +#= Linearring cannot touch another linear ring since they are both exclusively +made up of interior points and no bounday points =# +_touches( + ::GI.LinearRingTrait, g1, + ::GI.LinearRingTrait, g2, +) = false + +#= Linearring touches a polygon if at least one of the points of the ring +interact with the polygon bounday and non are in the polygon interior. =# +_touches( + ::GI.LinearRingTrait, g1, + ::GI.PolygonTrait, g2, +) = _line_polygon_process( + g1, g2; + TOUCHES_POLYGON_ALLOWS..., + TOUCHES_REQUIRES..., + closed_line = true, +) + + +# # Polygons touch geometries + +#= Polygon touches a curve if at least one of the curve bounday points interacts +with the polygon's bounday and no curve points interact with the interior.=# +_touches( + trait1::GI.PolygonTrait, g1, + trait2::GI.AbstractCurveTrait, g2 +) = _touches(trait2, g2, trait1, g1) + + +# # Geometries touch multi-geometry/geometry collections + +#= Geometry touch a multi-geometry or a collection if the geometry touches at +least one of the elements of the collection. =# +function _touches( + ::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) + touches(g1, sub_g2) && return true + end + return false +end + +# # Multi-geometry/geometry collections cross geometries + +#= Multi-geometry or a geometry collection touches a geometry if at least one +elements of the collection touches the geometry. =# +function _touches( + ::Union{ + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g1, + ::GI.AbstractGeometryTrait, g2, +) + for sub_g1 in GI.getgeom(g1) + touches(sub_g1, g2) && return true + end + return false +end \ No newline at end of file diff --git a/src/methods/geom_relations/within.jl b/src/methods/geom_relations/within.jl index e9f184b91..bcccdac34 100644 --- a/src/methods/geom_relations/within.jl +++ b/src/methods/geom_relations/within.jl @@ -5,7 +5,9 @@ export within #= ## What is within? -The within function checks if one geometry is inside another geometry. +The within function checks if one geometry is inside another geometry. This +requires that the two interiors intersect and that the interior and +boundary of the first geometry is not in the exterior of the second geometry. To provide an example, consider these two lines: ```@example cshape @@ -35,23 +37,25 @@ This is the GeoInterface-compatible implementation. First, we implement a wrapper method that dispatches to the correct implementation based on the geometry trait. -The methodology for each geometry pairing is a little different. For a point, -other points can only be inside of it if they are the same point. Nothing other -than a point can be within a point. For line string and linear rings, a point is -within if it is on a vertex or a line. For a line/ring inside of another -line/ring, we need all vertices and edges to be within the other line/ring's -edges. Polygons cannot be within a line/ring. Then for polygons, we need -lines/rings to be either on the edges (but with at least one point within the -polygon) or within the polygon, but not in any holes. Then for polygons within -polygons, they must be inside of the interior, including edges, but again not in -any holes. - -The code for the specific implementations is in the geom_geom_processors file, -which has generalized code for the within and disjoint functions with a keyword -argument `process`, which is specified to be the `within_process` for the below -functions. +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 +`within` function and arguments g1 and g2, this criteria is as follows: + - points of g1 are allowed to be in the interior of g2 (either through + overlap or crossing for lines) + - points of g1 are allowed to be on the boundary of g2 + - points of g1 are not allowed to be in the exterior of g2 + - at least one point of g1 is required to be in the interior of g2 + - no points of g1 are required to be on the boundary of g2 + - no points 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 WITHIN_POINT_ALLOWS = (in_allow = true, on_allow = false, out_allow = false) +const WITHIN_CURVE_ALLOWS = (over_allow = true, cross_allow = true, on_allow = true, out_allow = false) +const WITHIN_POLYGON_ALLOWS = (in_allow = true, on_allow = true, out_allow = false) +const WITHIN_REQUIRES = (in_require = true, on_require = false, out_require = false) + """ within(geom1, geom2)::Bool @@ -74,280 +78,192 @@ GO.within(point, line) true ``` """ -within(g1, g2) = within(trait(g1), g1, trait(g2), g2) -within(::GI.FeatureTrait, g1, ::Any, g2) = within(GI.geometry(g1), g2) -within(::Any, g1, t2::GI.FeatureTrait, g2) = within(g1, GI.geometry(g2)) +within(g1, g2) = _within(trait(g1), g1, trait(g2), g2) -""" -For any non-specified pair, g1 cannot be within g2 as g2 is of a higher -dimension than g1. Return false. -""" -within(::GI.AbstractGeometryTrait, g1, ::GI.AbstractGeometryTrait, g2) = false +# # Convert features to geometries +_within(::GI.FeatureTrait, g1, ::Any, g2) = within(GI.geometry(g1), g2) +_within(::Any, g1, t2::GI.FeatureTrait, g2) = within(g1, GI.geometry(g2)) -# Points within geometries -""" - within(::GI.PointTrait, g1, ::GI.PointTrait, g2)::Bool -If a point is within another point, then those points must be equal. If they are -not equal, then they are not within and return false. -""" -within( +# # Points within geometries + +# Point is within another point if those points are equal. +_within( ::GI.PointTrait, g1, ::GI.PointTrait, g2, ) = equals(g1, g2) - -""" - within(::GI.PointTrait, g1, ::GI.LineStringTrait, g2)::Bool - -A point is within a line string if it is on a vertex or an edge of that -linestring, excluding the start and end vertex if the linestring is not closed. -Return true if those conditions are met, else false. -""" -within( +#= Point is within a linestring if it is on a vertex or an edge of that line, +excluding the start and end vertex if the line is not closed. =# +_within( ::GI.PointTrait, g1, - ::GI.LineStringTrait, g2, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _point_curve_process( g1, g2; - in_allow = true, on_allow = false, out_allow = false, + WITHIN_POINT_ALLOWS..., repeated_last_coord = false, ) -""" - within(::GI.PointTrait, g1, ::GI.LinearRingTrait, g2)::Bool - -A point is within a linear ring if it is on a vertex or an edge of that -linear ring. Return true if those conditions are met, else false. -""" -within( +# Point is within a linearring if it is on a vertex or an edge of that ring. +_within( ::GI.PointTrait, g1, ::GI.LinearRingTrait, g2, ) = _point_curve_process( g1, g2; - in_allow = true, on_allow = false, out_allow = false, + WITHIN_POINT_ALLOWS..., repeated_last_coord = true, ) -""" - within(::GI.PointTrait, g1, ::GI.PolygonTrait, g2)::Bool - -A point is within a polygon if it is inside of that polygon, excluding edges, -vertices, and holes. Return true if those conditions are met, else false. -""" -within( +#= Point is within a polygon if it is inside of that polygon, excluding edges, +vertices, and holes. =# +_within( ::GI.PointTrait, g1, ::GI.PolygonTrait, g2, ) = _point_polygon_process( g1, g2; - in_allow = true, on_allow = false, out_allow = false, + WITHIN_POINT_ALLOWS..., ) -# Lines within geometries -""" - within(::GI.LineStringTrait, g1, ::GI.LineStringTrait, g2)::Bool +# No geometries other than points can be within points +_within( + ::Union{GI.AbstractCurveTrait, GI.PolygonTrait}, g1, + ::GI.PointTrait, g2, +) = false -A line string is within another linestring if the vertices and edges of the -first linestring are within the second linestring, including the first and last -vertex. Return true if those conditions are met, else false. -""" -within( - ::GI.LineStringTrait, g1, - ::GI.LineStringTrait, g2, + +# # Lines within geometries + +#= Linestring is within another linestring if their interiors intersect and no +points of the first line are in the exterior of the second line. =# +_within( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( g1, g2; - over_allow = true, cross_allow = true, on_allow = true, out_allow = false, - in_require = true, on_require = false, out_require = false, + WITHIN_CURVE_ALLOWS..., + WITHIN_REQUIRES..., closed_line = false, closed_curve = false, ) -""" - within(::GI.LineStringTrait, g1, ::GI.LinearRingTrait, g2)::Bool - -A line string is within a linear ring if the vertices and edges of the -linestring are within the linear ring. Return true if those conditions are met, -else false. -""" -within( - ::GI.LineStringTrait, g1, +#= Linestring is within a linear ring if their interiors intersect and no points +of the line are in the exterior of the ring. =# +_within( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - over_allow = true, cross_allow = true, on_allow = true, out_allow = false, - in_require = true, on_require = false, out_require = false, + WITHIN_CURVE_ALLOWS..., + WITHIN_REQUIRES..., closed_line = false, closed_curve = true, ) -""" - within(::GI.LineStringTrait, g1, ::GI.PolygonTrait, g2)::Bool - -A line string is within a polygon if the vertices and edges of the -linestring are within the polygon. Points of the linestring can be on the -polygon edges, but at least one point must be in the polygon interior. The -linestring also cannot cross through a hole. Return true if those conditions are -met, else false. -""" -within( - ::GI.LineStringTrait, g1, +#= Linestring is within a polygon if their interiors intersect and no points of +the line are in the exterior of the polygon, although they can be on an edge. =# +_within( + ::Union{GI.LineTrait, GI.LineStringTrait}, g1, ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, - in_require = true, on_require = false, out_require = false, + WITHIN_POLYGON_ALLOWS..., + WITHIN_REQUIRES..., closed_line = false, ) -# Rings within geometries -""" - within(::GI.LinearRingTrait, g1, ::GI.LineStringTrait, g2)::Bool -A linear ring is within a linestring if the vertices and edges of the -linear ring are within the edges/vertices of the linear ring. Return true if -those conditions are met, else false. -""" -within( +# # Rings covered by geometries + +#= Linearring is within a linestring if their interiors intersect and no points +of the ring are in the exterior of the line. =# +_within( ::GI.LinearRingTrait, g1, - ::GI.LineStringTrait, g2, + ::Union{GI.LineTrait, GI.LineStringTrait}, g2, ) = _line_curve_process( g1, g2; - over_allow = true, cross_allow = true, on_allow = true, out_allow = false, - in_require = true, on_require = false, out_require = false, + WITHIN_CURVE_ALLOWS..., + WITHIN_REQUIRES..., closed_line = true, closed_curve = false, ) -""" - within(::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2)::Bool - -A linear ring is within another linear ring if the vertices and edges of the -first linear ring are within the edges/vertices of the second linear ring. -Return true if those conditions are met, else false. -""" -within( +#= Linearring is within another linearring if their interiors intersect and no +points of the first ring are in the exterior of the second ring. =# +_within( ::GI.LinearRingTrait, g1, ::GI.LinearRingTrait, g2, ) = _line_curve_process( g1, g2; - over_allow = true, cross_allow = true, on_allow = true, out_allow = false, - in_require = true, on_require = false, out_require = false, + WITHIN_CURVE_ALLOWS..., + WITHIN_REQUIRES..., closed_line = true, closed_curve = true, ) -""" - within(::GI.LinearRingTrait, g1, ::GI.PolygonTrait, g2)::Bool - -A linear ring is within a polygon if the vertices and edges of the linear ring -are within the polygon. Points of the linestring can be on the polygon edges, -but at least one point must be in the polygon interior. The linear ring also -cannot cross through a hole. Return true if those conditions are met, else -false. -""" -within( +#= Linearring is within a polygon if their interiors intersect and no points of +the ring are in the exterior of the polygon, although they can be on an edge. =# +_within( ::GI.LinearRingTrait, g1, ::GI.PolygonTrait, g2, ) = _line_polygon_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, - in_require = true, on_require = false, out_require = false, + WITHIN_POLYGON_ALLOWS..., + WITHIN_REQUIRES..., closed_line = true, ) -# Polygons within polygons -""" - within(::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2)::Bool -A polygon is within another polygon if the interior of the first polygon is -inside of the second, including edges, and does not intersect with any holes of -the second polygon. If these conditions are met, return true, else false. -""" -within( +# # Polygons within geometries + +#= Polygon is within another polygon if the interior of the first polygon +intersects with the interior of the second and no points of the first polygon +are outside of the second polygon. =# +_within( ::GI.PolygonTrait, g1, ::GI.PolygonTrait, g2, ) = _polygon_polygon_process( g1, g2; - in_allow = true, on_allow = true, out_allow = false, - in_require = true, on_require = false, out_require = false, + WITHIN_POLYGON_ALLOWS..., + WITHIN_REQUIRES..., ) -# function within( -# ::GI.PolygonTrait, g1, -# ::GI.PolygonTrait, g2; -# ) -# ext1 = GI.getexterior(g1) -# e1_in_e2, _, e1_out_e2 = _line_filled_curve_interactions( -# ext1, GI.getexterior(g2); -# closed_line = true, -# ) -# e1_out_e2 && return false - -# for h2 in GI.gethole(g2) -# if e1_in_e2 # h2 could be outside of e1, but inside of e2 -# h2_in_e1, h2_on_e1, _ = _line_filled_curve_interactions( -# h2, ext1; -# closed_line = true, -# ) -# # h2 is inside of e1 and cannot be excluded by a hole since it touches the boundary -# h2_on_e1 && h2_in_e1 && return false -# if !h2_in_e1 # is h2 disjoint from e1, or is e1 within h2? -# c1_val = point_filled_curve_orientation(centroid(ext1), h2) -# c1_val == point_in && return false # e1 is within h2 -# break # e1 is disjoint from h2 -# end -# end -# # h2 is within e1, but is it within a hole of g1? -# h2_in_e1 = true -# for h1 in GI.gethole(g1) -# _, h2_on_h1, h2_out_h1 = _line_filled_curve_interactions( -# h2, h1; -# closed_line = true, -# ) -# # h2 is outside of h1 and cannot be excluded by another hole since it touches the boundary -# h2_on_h1 && h2_out_h1 && return false -# if !h2_out_h1 #h2 is within bounds of h1, so not in e1 -# h2_in_e1 = false -# break -# end -# end -# h2_in_e1 && return false -# end -# return true -# end - -# Geometries within multipolygons -""" - within(::GI.AbstractTrait, g1, ::GI.MultiPolygonTrait, g2)::Bool +# Polygons cannot be within any curves +_within( + ::GI.PolygonTrait, g1, + ::GI.AbstractCurveTrait, g2, +) = false -A geometry is within a multipolygon if it is within one of the polygons that -make up the multipolygon. Return true if these conditions are met, else false. -""" -function within(::GI.AbstractGeometryTrait, g1, ::GI.MultiPolygonTrait, g2) - for poly in GI.getpolygon(g2) - if within(g1, poly) - return true - end + +# # Geometries within multi-geometry/geometry collections + +#= Geometry is within a multi-geometry or a collection if the geometry is within +at least one of the collection elements. =# +function _within( + ::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) + within(g1, sub_g2) && return true end return false end -""" - within(::GI.MultiPolygonTrait, g1, ::GI.MultiPolygonTrait, g2)::Bool +# # Multi-geometry/geometry collections within geometries -A multipolygon is within a multipolygon if every polygon in the first -multipolygon is within one of the polygons in the second multipolygon. Return -true if these conditions are met, else false. -""" -function within(::GI.MultiPolygonTrait, g1, ::GI.MultiPolygonTrait, g2) - for poly1 in GI.getpolygon(g1) - poly1_within = false - for poly2 in GI.getpolygon(g2) - if within(poly1, poly2) - poly1_within = true - break - end - end - !poly1_within && return false +#= Multi-geometry or a geometry collection is within a geometry if all +elements of the collection are within the geometry. =# +function _within( + ::Union{ + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g1, + ::GI.AbstractGeometryTrait, g2, +) + for sub_g1 in GI.getgeom(g1) + !within(sub_g1, g2) && return false end return true -end \ No newline at end of file +end diff --git a/test/methods/bools.jl b/test/methods/bools.jl index 45b8ef50d..54fe509f4 100644 --- a/test/methods/bools.jl +++ b/test/methods/bools.jl @@ -18,13 +18,38 @@ l8 = LG.LineString([[0.0, 0.0], [0.5, 0.0], [0.5, 0.5], [1.0, -0.5]]) l9 = LG.LineString([[0.0, 1.0], [0.0, -1.0], [1.0, 1.0]]) l10 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) l11 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0], [-1.0, 0.0]]) +l12 = LG.LineString([[0.6, 0.5], [0.6, 0.9]]) +l13 = LG.LineString([[2.0, 2.0], [3.0, 3.0]]) +l14 = LG.LineString([[0.6, 0.25], [0.6, 0.35]]) +l15 = LG.LineString([[0.0, 3.0], [4.0, 3.0]]) r1 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) r2 = LG.LinearRing([[0.5, 0.2], [0.6, 0.4], [0.7, 0.2], [0.5, 0.2]]) r3 = LG.LinearRing([[0.2, 0.7], [0.4, 0.9], [0.5, 0.6], [0.2, 0.7]]) - -p1 = LG.Polygon(r1, [r2, r3]) +r4 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]) +r5 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [2.0, 1.0], [1.0, 2.0], [1.0, 1.0], [0.0, 0.0]]) +r6 = LG.LinearRing([[0.0, 0.0], [-1.0, 0.0], [-1.0, 1.0], [0.0, -1.0], [0.0, 0.0]]) +r7 = LG.LinearRing([[0.5, 0.5], [1.5, 0.5], [1.5, 1.5], [0.5, 1.5], [0.5, 0.5]]) +r8 = LG.LinearRing([[0.1, 0.1], [0.2, 0.1], [0.2, 0.2], [0.1, 0.2], [0.1, 0.1]]) +r9 = LG.LinearRing([[1.0, -0.5], [0.5, -1.0], [1.5, -1.0], [1.0, -0.5]]) +r10 = LG.LinearRing([[0.5, 0.2], [0.6, 0.4], [0.3, 0.3], [0.5, 0.2]]) +r11 = LG.LinearRing([[0.55, 0.21], [0.55, 0.23], [0.65, 0.23], [0.66, 0.21], [0.55, 0.21]]) + +p1 = LG.Polygon(r4, [r2, r3]) +p2 = LG.Polygon(r4) +p3 = LG.Polygon(r2) +p4 = LG.Polygon(r6) +p5 = LG.Polygon(r9) +p6 = LG.Polygon(r11) +p7 = LG.Polygon(r7) +p8 = LG.Polygon([[[0.0, 0.0], [0.0, 3.0], [1.0, 2.0], [2.0, 3.0], [3.0, 2.0], [4.0, 3.0], [4.0, 0.0], [0.0, 0.0]]]) +p9 = LG.Polygon([[[0.0, 0.0], [0.0, 3.0], [1.0, 4.0], [2.0, 3.0], [3.0, 4.0], [4.0, 3.0], [4.0, 0.0], [0.0, 0.0]]]) +p10 = LG.Polygon([ + [[0.1, 0.5], [0.1, 0.99], [0.6, 0.99], [0.6, 0.5], [0.1, 0.5]], + [[0.15, 0.55], [0.15, 0.95], [0.55, 0.95], [0.55, 0.55], [0.15, 0.55]] +]) +p11 = LG.Polygon(r3) @testset "Contains" begin include("geom_relations/contains.jl") end @testset "Covered By" begin include("geom_relations/coveredby.jl") end diff --git a/test/methods/geom_relations/contains.jl b/test/methods/geom_relations/contains.jl index 763b5eede..9ae97e0a0 100644 --- a/test/methods/geom_relations/contains.jl +++ b/test/methods/geom_relations/contains.jl @@ -4,6 +4,7 @@ @test GO.contains(pt1, pt1) == LG.contains(pt1, pt1) == true # Different point -> doesn't contain @test GO.contains(pt1, pt2) == LG.contains(pt1, pt2) == false + # Point on line endpoint -> does not contain @test GO.contains(l1, pt1) == LG.contains(l1, pt1) == false # Point outside line -> does not contain @@ -14,6 +15,7 @@ @test GO.contains(l1, pt4) == LG.contains(l1, pt4) == true # Point cannot contain a line -> doesn't contain @test GO.contains(pt3, l1) == LG.contains(pt3, l1) == false + # Point on ring endpoint -> contains @test GO.contains(r1, pt1) == LG.contains(r1, pt1) == true # Point outside ring -> does not contain @@ -24,6 +26,7 @@ @test GO.contains(r1, pt4) == LG.contains(r1, pt4) == true # Point cannot contain a ring -> doesn't contain @test GO.contains(pt3, r1) == LG.contains(pt3, r1) == false + # Point on vertex of polygon --> doesn't contain @test GO.contains(p1, pt1) == LG.contains(p1, pt1) == false # Point outside of polygon's external ring -> doesn't contain @@ -59,6 +62,7 @@ @test GO.contains(l1, l8) == LG.contains(l1, l8) == false # Line segments cross and overlap on endpoint -> doesn't contain @test GO.contains(l1, l9) == LG.contains(l1, l9) == false + # Line is within linear ring -> doesn't contain @test GO.contains(l1, r1) == LG.contains(l1, r1) == false # Line covers one edge of linera ring and has segment outside -> doesn't contain @@ -76,6 +80,25 @@ # Line covers linear ring and then has extra segment -> contain @test GO.contains(l11, r1) == LG.contains(l11, r1) == true +# Line on polygon edge -> doesn't contain +@test GO.contains(p1, l1) == LG.contains(p1, l1) == false +# Line on polygon edge and extending beyond polygon edge -> doesn't contain +@test GO.contains(p1, l3) == LG.contains(p1, l3) == false +# Line outside polygon connected by an vertex -> doesn't contain +@test GO.contains(p1, l5) == LG.contains(p1, l5) == false +# Line through polygon cutting to outside -> doesn't contain +@test GO.contains(p1, l7) == LG.contains(p1, l7) == false +# Line inside of polygon -> contains +@test GO.contains(p1, l12) == LG.contains(p1, l12) == true +# Line outside of polygon -> doesn't contain +@test GO.contains(p1, l13) == LG.contains(p1, l13) == false +# Line in polygon hole -> doesn't contain +@test GO.contains(p1, l14) == LG.contains(p1, l14) == false +# Line outside crown polygon but touching edges -> doesn't contains +@test GO.contains(p8, l15) == LG.contains(p8, l15) == false +# Line within crown polygon but touching edges -> contains +@test GO.contains(p9, l15) == LG.contains(p9, l15) == true + # # Ring and Geometry # Line is within linear ring -> contains @@ -95,36 +118,73 @@ # Line covers linear ring and then has extra segment -> doesn't contain @test GO.contains(r1, l11) == LG.contains(r1, l11) == false +# Same ring -> contains +@test GO.contains(r1, r1) == LG.contains(r1, r1) == true +# Disjoint ring with one "inside" of hole created => doesn't contain +@test GO.contains(r1, r2) == LG.contains(r1, r2) == false +# Disjoint ring with one "outside" of hole created => doesn't contain +@test GO.contains(r1, r3) == LG.contains(r1, r3) == false +# Rings share two sides and rest of sides don't touch -> doesn't contain +@test GO.contains(r1, r4) == LG.contains(r1, r4) == false +# Ring shares all edges with other ring, plus an extra loop -> contains +@test GO.contains(r5, r1) == LG.contains(r5, r1) == true +# Rings share just one vertex -> doesn't contain +@test GO.contains(r6, r1) == LG.contains(r6, r1) == false +# Rings cross over one another -> doesn't contain +@test GO.contains(r7, r1) == LG.contains(r7, r1) == false +# Ring on bounday of polygon -> doesn't contain +@test GO.contains(p1, r4) == LG.contains(p1, r4) == false +# Ring on boundary and cutting through polygon -> contains +@test GO.contains(p1, r1) == LG.contains(p1, r1) == true +# Ring on hole boundary -> doesn't contain +@test GO.contains(p1, r2) == LG.contains(p1, r2) == false +# Ring touches polygon at one vertex -> doesn't contain +@test GO.contains(p1, r6) == LG.contains(p1, r6) == false +# Ring crosses through polygon -> doesn't contain +@test GO.contains(p1, r7) == LG.contains(p1, r7) == false +# Ring inside polygon -> contains +@test GO.contains(p1, r8) == LG.contains(p1, r8) == true +# Ring outside -> doesn't contain +@test GO.contains(p1, r9) == LG.contains(p1, r9) == false +# Ring inside polygon and shares holes edge -> contains +@test GO.contains(p1, r10) == LG.contains(p1, r10) == true +# Ring inside of polygon hole -> doesn't contain +@test GO.contains(p1, r11) == LG.contains(p1, r11) == false -# p1 = LG.Point([0.0, 0.0]) -# p2 = LG.Point([0.0, 0.1]) -# p3 = LG.Point([1.0, 0.0]) - -# l1 = LG.LineString([[0.0, 1.0]]) -# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) - -# # Point and point -# @test GO.contains(p1, p1) == LG.contains(p1, p1) -# @test GO.contains(p1, p2) == LG.contains(p1, p2) +# # Polygon and Geometry -# # Point and line -# @test GO.contains(l1, p1) == LG.contains(l1, p1) -# @test GO.contains(l1, p2) == LG.contains(l1, p2) -# @test GO.contains(l1, p3) == LG.contains(l1, p3) -# @test GO.contains(l2, p1) == LG.contains(l2, p1) -# @test GO.contains(l2, p2) == LG.contains(l2, p2) -# @test GO.contains(l2, p3) == LG.contains(l2, p3) +# Polygon with holes in polygon without holes -> contains +@test GO.contains(p2, p1) == LG.contains(p2, p1) == true +# Polygon without holes in polygon with holes -> doesn't contain +@test GO.contains(p1, p2) == LG.contains(p1, p2) == false +# Polygon is the same as other poylgon hole -> doesn't contain +@test GO.contains(p1, p3) == LG.contains(p1, p3) == false +# Polygon touches other polygon by vertex -> doesn't contain +@test GO.contains(p1, p4) == LG.contains(p1, p4) == false +# Polygon outside of other polygon -> doesn't contain +@test GO.contains(p1, p5) == LG.contains(p1, p5) == false +# Polygon inside of hole -> doesn't contain +@test GO.contains(p1, p6) == LG.contains(p1, p6) == false +# Polygon overlaps other polygon -> doesn't contain +@test GO.contains(p1, p7) == LG.contains(p1, p7) == false +# Polygon with hole inside polygon with hole (holes nested) -> contains +@test GO.contains(p1, p10) == LG.contains(p1, p10) == true -# # Line and line -# @test GO.contains(l1, l1) == LG.contains(l1, l1) -# @test GO.contains(l1, l2) == LG.contains(l1, l2) -# @test GO.contains(l1, l3) == LG.contains(l1, l3) -# @test GO.contains(l1, l4) == LG.contains(l1, l4) -# @test GO.contains(l1, l5) == LG.contains(l1, l5) +# # Multi-geometries and collections -# @test GO.contais(l1, l1) == LG.contains(l1, l1) -# @test GO.contais(l2, l1) == LG.contains(l2, l1) -# @test GO.contais(l3, l1) == LG.contains(l3, l1) -# @test GO.contais(l4, l1) == LG.contains(l4, l1) -# @test GO.contais(l5, l1) == LG.contains(l5, l1) \ No newline at end of file +mp1 = LG.MultiPolygon([p1, p3, p11]) +mp2 = LG.MultiPolygon([p2, p5]) +c1 = LG.GeometryCollection([r1, r8, pt5]) +# Multipolygon plugs all holes -> doesn't contain +@test GO.contains(mp1, p2) == LG.contains(mp1, p2) == false +# Polygon is one of the multipolygons -> contains +@test GO.contains(mp2, p2) == LG.contains(mp2, p2) == true +# Polygon touches one of the multipolygons -> doesn't contain +@test GO.contains(mp2, p4) == LG.contains(mp2, p4) == false +# Polygon contains all multipolygon elements +@test GO.contains(p9, mp1) == LG.contains(p9, mp1) == true +# Polygon contains all collection elements +@test GO.contains(p1, c1) == LG.contains(p1, c1) == true +# Collection doesn't contain all multipolygon elements +@test GO.contains(c1, mp1) == LG.contains(c1, mp1) == false \ No newline at end of file diff --git a/test/methods/geom_relations/coveredby.jl b/test/methods/geom_relations/coveredby.jl index 919958f3d..a4ab8fe09 100644 --- a/test/methods/geom_relations/coveredby.jl +++ b/test/methods/geom_relations/coveredby.jl @@ -4,6 +4,7 @@ @test GO.coveredby(pt1, pt1) == LG.coveredby(pt1, pt1) == true # Different point -> not covered by @test GO.coveredby(pt1, pt2) == LG.coveredby(pt1, pt2) == false + # Point on line endpoint -> covered by @test GO.coveredby(pt1, l1) == LG.coveredby(pt1, l1) == true # Point outside line -> not covered by @@ -14,6 +15,7 @@ @test GO.coveredby(pt4, l1) == LG.coveredby(pt4, l1) == true # line cannot be covered by a point -> not covered by @test GO.coveredby(l1, pt3) == LG.coveredby(l1, pt3) == false + # Point on ring endpoint -> covered by @test GO.coveredby(pt1, r1) == LG.coveredby(pt1, r1) == true # Point outside ring -> isn't covered by @@ -24,6 +26,7 @@ @test GO.coveredby(pt4, r1) == LG.coveredby(pt4, r1) == true # Ring cannot be covered by a point -> isn't covered by @test GO.coveredby(r1, pt3) == LG.coveredby(r1, pt3) == false + # Point on vertex of polygon --> covered @test GO.coveredby(pt1, p1) == LG.coveredby(pt1, p1) == true # Point outside of polygon's external ring -> not covered by @@ -59,6 +62,7 @@ @test GO.coveredby(l8, l1) == LG.coveredby(l8, l1) == false # Line segments cross and overlap on endpoint -> isn't covered by @test GO.coveredby(l9, l1) == LG.coveredby(l9, l1) == false + # Line is within linear ring -> covered by @test GO.coveredby(l1, r1) == LG.coveredby(l1, r1) == true # Line covers one edge of linera ring and has segment outside -> isn't covered by @@ -76,21 +80,104 @@ # Line covers linear ring and then has extra segment -> isn't covered by @test GO.coveredby(l11, r1) == LG.coveredby(l11, r1) == false +# Line on polygon edge -> coveredby +@test GO.coveredby(l1, p1) == LG.coveredby(l1, p1) == true +# Line on polygon edge and extending beyond polygon edge -> not coveredby +@test GO.coveredby(l3, p1) == LG.coveredby(l3, p1) == false +# Line outside polygon connected by an vertex -> not coveredby +@test GO.coveredby(l5, p1) == LG.coveredby(l5, p1) == false +# Line through polygon cutting to outside -> not coveredby +@test GO.coveredby(l7, p1) == LG.coveredby(l7, p1) == false +# Line inside of polygon -> coveredby +@test GO.coveredby(l12, p1) == LG.coveredby(l12, p1) == true +# Line outside of polygon -> not coveredby +@test GO.coveredby(l13, p1) == LG.coveredby(l13, p1) == false +# Line in polygon hole -> not coveredby +@test GO.coveredby(l14, p1) == LG.coveredby(l14, p1) == false +# Line outside crown polygon but touching edges -> not coveredby +@test GO.coveredby(l15, p8) == LG.coveredby(l15, p8) == false +# Line within crown polygon but touching edges -> not coveredby +@test GO.coveredby(l15, p9) == LG.coveredby(l15, p9) == true + # # Ring and Geometry -# Line is within linear ring -> isn't covered by +# Line is within linear ring -> not coveredby @test GO.coveredby(r1, l1) == LG.coveredby(r1, l1) == false -# Line covers one edge of linera ring and has segment outside -> isn't covered by +# Line covers one edge of linera ring and has segment outside -> not coveredby @test GO.coveredby(r1, l3) == LG.coveredby(r1, l3) == false -# Line and linear ring are only connected at vertex -> isn't covered by +# Line and linear ring are only connected at vertex -> not coveredby @test GO.coveredby(r1, l5) == LG.coveredby(r1, l5) == false -# Line and linear ring are disjoint -> isn't covered by +# Line and linear ring are disjoint -> not coveredby @test GO.coveredby(r1, l6) == LG.coveredby(r1, l6) == false -# Line crosses through two ring edges -> isn't covered by +# Line crosses through two ring edges -> not coveredby @test GO.coveredby(r1, l7) == LG.coveredby(r1, l7) == false -# Line crosses through two ring edges and touches third edge -> isn't covered by +# Line crosses through two ring edges and touches third edge -> not coveredby @test GO.coveredby(r1, l8) == LG.coveredby(r1, l8) == false -# Line is equal to linear ring -> covered by +# Line is equal to linear ring -> coveredby @test GO.coveredby(r1, l10) == LG.coveredby(r1, l10) == true -# Line covers linear ring and then has extra segment -> covered by +# Line covers linear ring and then has extra segment -> coveredby @test GO.coveredby(r1, l11) == LG.coveredby(r1, l11) == true + +# Same ring -> coveredby +@test GO.coveredby(r1, r1) == LG.coveredby(r1, r1) == true +# Disjoint ring with one "inside" of hole created => not coveredby +@test GO.coveredby(r2, r1) == LG.coveredby(r2, r1) == false +# Disjoint ring with one "outside" of hole created => not coveredby +@test GO.coveredby(r3, r1) == LG.coveredby(r3, r1) == false +# Rings share two sides and rest of sides don't touch -> not coveredby +@test GO.coveredby(r4, r1) == LG.coveredby(r4, r1) == false +# Ring shares all edges with other ring, plus an extra loop -> coveredby +@test GO.coveredby(r1, r5) == LG.coveredby(r1, r5) == true +# Rings share just one vertex -> not coveredby +@test GO.coveredby(r1, r6) == LG.coveredby(r1, r6) == false +# Rings cross over one another -> not coveredby +@test GO.coveredby(r1, r7) == LG.coveredby(r1, r7) == false + +# Ring on bounday of polygon -> coveredby +@test GO.coveredby(r4, p1) == LG.coveredby(r4, p1) == true +# Ring on boundary and cutting through polygon -> coveredby +@test GO.coveredby(r1, p1) == LG.coveredby(r1, p1) == true +# Ring on hole boundary -> coveredby +@test GO.coveredby(r2, p1) == LG.coveredby(r2, p1) == true +# Ring touches polygon at one vertex -> not coveredby +@test GO.coveredby(r6, p1) == LG.coveredby(r6, p1) == false +# Ring crosses through polygon -> not coveredby +@test GO.coveredby(r7, p1) == LG.coveredby(r7, p1) == false +# Ring inside polygon -> coveredby +@test GO.coveredby(r8, p1) == LG.coveredby(r8, p1) == true +# Ring outside -> not coveredby +@test GO.coveredby(r9, p1) == LG.coveredby(r9, p1) == false +# Ring inside polygon and shares holes edge -> coveredby +@test GO.coveredby(r10, p1) == LG.coveredby(r10, p1) == true +# Ring inside of polygon hole -> not coveredby +@test GO.coveredby(r11, p1) == LG.coveredby(r11, p1) == false + +# # Polygon and Geometry + +# Polygon with holes in polygon without holes -> coveredby +@test GO.coveredby(p1, p2) == LG.coveredby(p1, p2) == true +# Polygon without holes in polygon with holes -> not coveredby +@test GO.coveredby(p2, p1) == LG.coveredby(p2, p1) == false +# Polygon is the same as other poylgon hole -> not coveredby +@test GO.coveredby(p3, p1) == LG.coveredby(p3, p1) == false +# Polygon touches other polygon by vertex -> not coveredby +@test GO.coveredby(p4, p1) == LG.coveredby(p4, p1) == false +# Polygon outside of other polygon -> not coveredby +@test GO.coveredby(p5, p1) == LG.coveredby(p5, p1) == false +# Polygon inside of hole -> not coveredby +@test GO.coveredby(p6, p1) == LG.coveredby(p6, p1) == false +# Polygon overlaps other polygon -> not coveredby +@test GO.coveredby(p7, p1) == LG.coveredby(p7, p1) == false +# Polygon with hole inside polygon with hole (holes nested) -> coveredby +@test GO.coveredby(p10, p1) == LG.coveredby(p10, p1) == true + +# # Multi-geometries and collections +mpt1 = LG.MultiPoint([pt1, pt4, pt5]) +ml1 = LG.MultiLineString([l1, l2, l3]) +c1 = LG.GeometryCollection([l1, pt1, ]) +# Three points all in polygon +@test GO.coveredby(mpt1, p1) == LG.coveredby(mpt1, p1) == true +# Polygon can't be covered by multipoints +@test GO.coveredby(p1, mpt1) == LG.coveredby(p1, mpt1) +# Three lines not all covered by line +@test GO.coveredby(ml1, l1) == LG.coveredby(ml1, l1) == false From e88b4cac6e01aaf6288513cb10d499a44d607d04 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 2 Jan 2024 00:13:11 -0800 Subject: [PATCH 31/33] Fixed all source code --- src/methods/geom_relations/crosses.jl | 4 +- src/methods/geom_relations/disjoint.jl | 6 +- .../geom_relations/geom_geom_processors.jl | 37 +- src/methods/geom_relations/overlaps.jl | 366 ++++++------------ src/methods/geom_relations/touches.jl | 25 +- test/methods/bools.jl | 64 --- test/methods/geom_relations.jl | 177 +++++++++ test/runtests.jl | 1 + 8 files changed, 345 insertions(+), 335 deletions(-) create mode 100644 test/methods/geom_relations.jl diff --git a/src/methods/geom_relations/crosses.jl b/src/methods/geom_relations/crosses.jl index 8ff585333..40468d6d0 100644 --- a/src/methods/geom_relations/crosses.jl +++ b/src/methods/geom_relations/crosses.jl @@ -55,7 +55,7 @@ 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 = false, 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) """ @@ -104,7 +104,7 @@ _crosses( ) = _line_curve_process( g1, g2; CROSSES_CURVE_ALLOWS..., - in_require = true, on_require = false, out_require = true, + CROSSES_REQUIRES..., closed_line = false, closed_curve = false, ) diff --git a/src/methods/geom_relations/disjoint.jl b/src/methods/geom_relations/disjoint.jl index ad923d7c7..50cee76a1 100644 --- a/src/methods/geom_relations/disjoint.jl +++ b/src/methods/geom_relations/disjoint.jl @@ -217,11 +217,11 @@ _disjoint( #= Geometry is disjoint from a multi-geometry or a collection if all of the elements of the collection are disjoint from the geometry. =# function _disjoint( - ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1 + ::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1, ::Union{ GI.MultiPointTrait, GI.AbstractMultiCurveTrait, GI.MultiPolygonTrait, GI.GeometryCollectionTrait, - }, g2 + }, g2, ) for sub_g2 in GI.getgeom(g2) !disjoint(g1, sub_g2) && return false @@ -238,7 +238,7 @@ function _disjoint( GI.MultiPointTrait, GI.AbstractMultiCurveTrait, GI.MultiPolygonTrait, GI.GeometryCollectionTrait, }, g1, - ::GI.AbstractGeometryTrait, g2 + ::GI.AbstractGeometryTrait, g2, ) for sub_g1 in GI.getgeom(g1) !disjoint(sub_g1, g2) && return false diff --git a/src/methods/geom_relations/geom_geom_processors.jl b/src/methods/geom_relations/geom_geom_processors.jl index ae13f1d55..ee685f46b 100644 --- a/src/methods/geom_relations/geom_geom_processors.jl +++ b/src/methods/geom_relations/geom_geom_processors.jl @@ -227,7 +227,7 @@ end function _line_curve_process( line, curve; - over_allow, cross_allow, on_allow, out_allow, # TODO: seperate crosses and overlaps (?) + over_allow, cross_allow, on_allow, out_allow, in_require, on_require, out_require, closed_line = false, closed_curve = false, @@ -312,6 +312,7 @@ function _line_curve_process( (1, 1) end end + if ( (β == 0 && !closed_curve && j == 2) || (β == 1 && !closed_curve && j == nc) || @@ -321,11 +322,34 @@ function _line_curve_process( !on_allow && return false on_req_met = true else - !cross_allow && return false in_req_met = true + if (!cross_allow || !over_allow) && α != 0 && β != 0 + l, c = if β == 1 + if α == 1 + ( + (l_end, GI.getpoint(line, i + 1)), + (c_end, GI.getpoint(curve, j + 1)) + ) + else + ( + (l_start, l_end), + (c_end, GI.getpoint(curve, j + 1)) + ) + end + else # β ≠ 1 and α == 1 + ( + (l_end, GI.getpoint(line, i + 1)), + (c_start, c_end) + ) + end + if _segment_segment_orientation(l, c) == line_hinge + !cross_allow && return false + else + !over_allow && return false + end + end end end - # no overlap for a give segment if j == nc !out_allow && return false @@ -616,12 +640,7 @@ function _polygon_polygon_process( !out_allow && return false out_req_met = true end - #= - If exterior of poly1 isn't in poly2 and poly2 isn't within poly1 (checked - with centroid), polygon interiors do not interact and there is nothing left - to check. - =# - c2 = centroid(poly2) + if !e1_in_p2 _, _, e2_out_e1 = _line_filled_curve_interactions( ext2, ext1; diff --git a/src/methods/geom_relations/overlaps.jl b/src/methods/geom_relations/overlaps.jl index ad49d6314..9133abf63 100644 --- a/src/methods/geom_relations/overlaps.jl +++ b/src/methods/geom_relations/overlaps.jl @@ -55,12 +55,17 @@ this file determine if the given geometries meet a set of criteria. For the 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 interiors interact, but they both also have interior points exterior to the other -geometry. +geometry. Lines crossing doesn't count for interiors interacting as overlaps +of curves must be of dimension one. ## Examples ```jldoctest @@ -90,278 +95,107 @@ _overlaps( ) = false -# # Point disjoint geometries - -# Point is disjoint from another point if the points are not equal. -_disjoint( - ::GI.PointTrait, g1, - ::GI.PointTrait, g2, -) = !equals(g1, g2) +# # Lines cross curves - -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 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 -# function overlaps( -# ::GI.LineStringTrait, g1, -# ::GI.LineStringTrait, g2, -# ) -# cross, overlap = _line_curve_crosses_overlap_interactions( -# g1, g2; -# closed_line = false, closed_curve = false, -# ) -# return !cross && overlap -# end - -# function overlaps( -# ::GI.LineStringTrait, g1, -# ::GI.LinearRingTrait, g2, -# ) -# cross, overlap = _line_curve_crosses_overlap_interactions( -# g1, g2; -# closed_line = true, closed_curve = false, -# ) -# return !cross && overlap -# end -# function overlaps( -# ::GI.LinearRingTrait, g1, -# ::GI.LineStringTrait, g2, -# ) -# cross, overlap = _line_curve_crosses_overlap_interactions( -# g1, g2; -# closed_line = false, closed_curve = true, -# ) -# return !cross && overlap -# end -# function overlaps( -# ::GI.LinearRingTrait, g1, -# ::GI.LinearRingTrait, g2, -# ) -# cross, overlap = _line_curve_crosses_overlap_interactions( -# g1, g2; -# closed_line = true, closed_curve = true, -# ) -# return !cross && overlap -# end - -overlaps( - ::GI.LineStringTrait, g1, - ::GI.LineStringTrait, g2, +#= 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; - over_allow = true, cross_allow = false, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, + 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, - ) && _line_curve_process( - g2, g1; - over_allow = true, cross_allow = false, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = false, - closed_curve = false, - ) - -overlaps( - ::GI.LineStringTrait, g1, + ) + +#= 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; - over_allow = true, cross_allow = false, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = false, - closed_curve = true, - ) && _line_curve_process( - g2, g1; - over_allow = true, cross_allow = false, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = true, - closed_curve = false, - ) - -overlaps( - ::GI.LinearRingTrait, g1, - ::GI.LineStringTrait, g2, -) = _line_curve_process( - g1, g2; - over_allow = true, cross_allow = false, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, + 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, - ) && _line_curve_process( - g2, g1; - over_allow = true, cross_allow = false, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = false, - closed_curve = true, - ) - -overlaps( + ) + + +# # 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; - over_allow = true, cross_allow = false, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, + 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, - ) && _line_curve_process( - g2, g1; - over_allow = true, cross_allow = false, on_allow = true, out_allow = true, - in_require = true, on_require = false, out_require = true, - closed_line = true, - closed_curve = true, - ) - -""" - 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 +# # Polygons cross polygons -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, +#= 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..., ) - for poly1 in GI.getgeom(polys1) - overlaps(poly1, polys2) && return true - end - return false -end -""" - _overlaps( - (a1, a2)::Edge, - (b1, b2)::Edge - )::Bool +# # Geometries disjoint multi-geometry/geometry collections -If the edges overlap, meaning that they are colinear but each have one endpoint -outside of the other edge, return true. Else false. -""" +# Multipoints overlap with other multipoints if only some sub-points are shared function _overlaps( - (a1, a2)::Edge, - (b1, b2)::Edge -) - # meets in more than one point or at endpoints - on_top = ExactPredicates.meet(a1, a2, b1, b2) == 0 - on_top || return false - # check that one endpoint of each edge is within other edge - a1_in = point_segment_orientation(a1, b1, b2) == point_in - a2_in = point_segment_orientation(a2, b1, b2) == point_in - b1_in = point_segment_orientation(b1, a1, a2) == point_in - b2_in = point_segment_orientation(b2, a1, a2) == point_in - return (a1_in ⊻ a2_in) && (b1_in ⊻ b2_in) -end - - -function overlaps( - ::GI.MultiPointTrait, points1, - ::GI.MultiPointTrait, points2, + ::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 @@ -372,4 +206,36 @@ function overlaps( one_same && one_diff && return true end return false -end \ No newline at end of file +end + +#= 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 sub_g2 in GI.getgeom(g2) + overlaps(g1, sub_g2) && return true + end + return false +end + +# # Multi-geometry/geometry collections cross geometries + +#= Multi-geometry or a geometry collection overlaps a geometry if at least one +elements of the collection overlaps the geometry. =# +function _overlaps( + ::Union{ + GI.MultiPointTrait, GI.AbstractMultiCurveTrait, + GI.MultiPolygonTrait, GI.GeometryCollectionTrait, + }, g1, + ::GI.AbstractGeometryTrait, g2, +) + for sub_g1 in GI.getgeom(g1) + overlaps(sub_g1, g2) && return true + end + return false +end diff --git a/src/methods/geom_relations/touches.jl b/src/methods/geom_relations/touches.jl index ab7e556a2..24bf4b2ea 100644 --- a/src/methods/geom_relations/touches.jl +++ b/src/methods/geom_relations/touches.jl @@ -52,7 +52,7 @@ The code for the specific implementations is in the geom_geom_processors file. const TOUCHES_POINT_ALLOWED = (in_allow = false, on_allow = true, out_allow = false) const TOUCHES_CURVE_ALLOWED = (over_allow = false, cross_allow = false, on_allow = true, out_allow = true) const TOUCHES_POLYGON_ALLOWS = (in_allow = false, on_allow = true, out_allow = true) -const TOUCHES_REQUIRED = (in_require = false, on_require = true, out_require = false) +const TOUCHES_REQUIRES = (in_require = false, on_require = true, out_require = false) """ touches(geom1, geom2)::Bool @@ -133,7 +133,7 @@ _touches( ) = _line_curve_process( g1, g2; TOUCHES_CURVE_ALLOWED..., - TOUCHES_REQUIRED..., + TOUCHES_REQUIRES..., closed_line = false, closed_curve = false, ) @@ -147,7 +147,7 @@ _touches( ) = _line_curve_process( g1, g2; TOUCHES_CURVE_ALLOWED..., - TOUCHES_REQUIRED..., + TOUCHES_REQUIRES..., closed_line = false, closed_curve = true, ) @@ -204,6 +204,17 @@ _touches( ) = _touches(trait2, g2, trait1, g1) +#= Polygon touches another polygon if they share at least one boundary point and +no interior points. =# +_touches( + ::GI.PolygonTrait, g1, + ::GI.PolygonTrait, g2, +) = _polygon_polygon_process( + g1, g2; + TOUCHES_POLYGON_ALLOWS..., + TOUCHES_REQUIRES..., +) + # # Geometries touch multi-geometry/geometry collections #= Geometry touch a multi-geometry or a collection if the geometry touches at @@ -216,9 +227,9 @@ function _touches( }, g2, ) for sub_g2 in GI.getgeom(g2) - touches(g1, sub_g2) && return true + !touches(g1, sub_g2) && return false end - return false + return true end # # Multi-geometry/geometry collections cross geometries @@ -233,7 +244,7 @@ function _touches( ::GI.AbstractGeometryTrait, g2, ) for sub_g1 in GI.getgeom(g1) - touches(sub_g1, g2) && return true + !touches(sub_g1, g2) && return false end - return false + return true end \ No newline at end of file diff --git a/test/methods/bools.jl b/test/methods/bools.jl index 54fe509f4..26c7f396b 100644 --- a/test/methods/bools.jl +++ b/test/methods/bools.jl @@ -1,68 +1,4 @@ -pt1 = LG.Point([0.0, 0.0]) -pt2 = LG.Point([5.0, 5.0]) -pt3 = LG.Point([1.0, 0.0]) -pt4 = LG.Point([0.5, 0.0]) -pt5 = LG.Point([0.5, 0.25]) -pt6 = LG.Point([0.6, 0.4]) -pt7 = LG.Point([0.4, 0.8]) - -l1 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]) -l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0]]) -l3 = LG.LineString([[-1.0, 0.0], [0.0, 0.0], [1.0, 0.0]]) -l4 = LG.LineString([[0.5, 0.0], [1.0, 0.0], [1.0, 0.5]]) -l5 = LG.LineString([[0.0, 0.0], [-1.0, -1.0]]) -l6 = LG.LineString([[2.0, 2.0], [0.0, 1.0]]) -l7 = LG.LineString([[0.5, 1.0], [0.5, -1.0]]) -l8 = LG.LineString([[0.0, 0.0], [0.5, 0.0], [0.5, 0.5], [1.0, -0.5]]) -l9 = LG.LineString([[0.0, 1.0], [0.0, -1.0], [1.0, 1.0]]) -l10 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) -l11 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0], [-1.0, 0.0]]) -l12 = LG.LineString([[0.6, 0.5], [0.6, 0.9]]) -l13 = LG.LineString([[2.0, 2.0], [3.0, 3.0]]) -l14 = LG.LineString([[0.6, 0.25], [0.6, 0.35]]) -l15 = LG.LineString([[0.0, 3.0], [4.0, 3.0]]) - - -r1 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) -r2 = LG.LinearRing([[0.5, 0.2], [0.6, 0.4], [0.7, 0.2], [0.5, 0.2]]) -r3 = LG.LinearRing([[0.2, 0.7], [0.4, 0.9], [0.5, 0.6], [0.2, 0.7]]) -r4 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]) -r5 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [2.0, 1.0], [1.0, 2.0], [1.0, 1.0], [0.0, 0.0]]) -r6 = LG.LinearRing([[0.0, 0.0], [-1.0, 0.0], [-1.0, 1.0], [0.0, -1.0], [0.0, 0.0]]) -r7 = LG.LinearRing([[0.5, 0.5], [1.5, 0.5], [1.5, 1.5], [0.5, 1.5], [0.5, 0.5]]) -r8 = LG.LinearRing([[0.1, 0.1], [0.2, 0.1], [0.2, 0.2], [0.1, 0.2], [0.1, 0.1]]) -r9 = LG.LinearRing([[1.0, -0.5], [0.5, -1.0], [1.5, -1.0], [1.0, -0.5]]) -r10 = LG.LinearRing([[0.5, 0.2], [0.6, 0.4], [0.3, 0.3], [0.5, 0.2]]) -r11 = LG.LinearRing([[0.55, 0.21], [0.55, 0.23], [0.65, 0.23], [0.66, 0.21], [0.55, 0.21]]) - -p1 = LG.Polygon(r4, [r2, r3]) -p2 = LG.Polygon(r4) -p3 = LG.Polygon(r2) -p4 = LG.Polygon(r6) -p5 = LG.Polygon(r9) -p6 = LG.Polygon(r11) -p7 = LG.Polygon(r7) -p8 = LG.Polygon([[[0.0, 0.0], [0.0, 3.0], [1.0, 2.0], [2.0, 3.0], [3.0, 2.0], [4.0, 3.0], [4.0, 0.0], [0.0, 0.0]]]) -p9 = LG.Polygon([[[0.0, 0.0], [0.0, 3.0], [1.0, 4.0], [2.0, 3.0], [3.0, 4.0], [4.0, 3.0], [4.0, 0.0], [0.0, 0.0]]]) -p10 = LG.Polygon([ - [[0.1, 0.5], [0.1, 0.99], [0.6, 0.99], [0.6, 0.5], [0.1, 0.5]], - [[0.15, 0.55], [0.15, 0.95], [0.55, 0.95], [0.55, 0.55], [0.15, 0.55]] -]) -p11 = LG.Polygon(r3) - -@testset "Contains" begin include("geom_relations/contains.jl") end -@testset "Covered By" begin include("geom_relations/coveredby.jl") end -@testset "Covers" begin include("geom_relations/covers.jl") end -@testset "Crosses" begin include("geom_relations/crosses.jl") end -@testset "Disjoint" begin include("geom_relations/disjoint.jl") end -@testset "Equals" begin include("geom_relations/equals.jl") end -@testset "Touches" begin include("geom_relations/touches.jl") end -@testset "Overlaps" begin include("geom_relations/overlaps.jl") end -@testset "Intersect" begin include("geom_relations/intersects.jl") end -@testset "Within" begin include("geom_relations/within.jl") end - - # @testset "booleans" begin # line1 = GI.LineString([[9.170356, 45.477985], [9.164434, 45.482551], [9.166644, 45.484003]]) # line2 = GI.LineString([[9.169356, 45.477985], [9.163434, 45.482551], [9.165644, 45.484003]]) diff --git a/test/methods/geom_relations.jl b/test/methods/geom_relations.jl new file mode 100644 index 000000000..65c8ec82e --- /dev/null +++ b/test/methods/geom_relations.jl @@ -0,0 +1,177 @@ +pt1 = LG.Point([0.0, 0.0]) +pt2 = LG.Point([5.0, 5.0]) +pt3 = LG.Point([1.0, 0.0]) +pt4 = LG.Point([0.5, 0.0]) +pt5 = LG.Point([0.5, 0.25]) +pt6 = LG.Point([0.6, 0.4]) +pt7 = LG.Point([0.4, 0.8]) + +l1 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]) +l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0]]) +l3 = LG.LineString([[-1.0, 0.0], [0.0, 0.0], [1.0, 0.0]]) +l4 = LG.LineString([[0.5, 0.0], [1.0, 0.0], [1.0, 0.5]]) +l5 = LG.LineString([[0.0, 0.0], [-1.0, -1.0]]) +l6 = LG.LineString([[2.0, 2.0], [0.0, 1.0]]) +l7 = LG.LineString([[0.5, 1.0], [0.5, -1.0]]) +l8 = LG.LineString([[0.0, 0.0], [0.5, 0.0], [0.5, 0.5], [1.0, -0.5]]) +l9 = LG.LineString([[0.0, 1.0], [0.0, -1.0], [1.0, 1.0]]) +l10 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) +l11 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0], [-1.0, 0.0]]) +l12 = LG.LineString([[0.6, 0.5], [0.6, 0.9]]) +l13 = LG.LineString([[2.0, 2.0], [3.0, 3.0]]) +l14 = LG.LineString([[0.6, 0.25], [0.6, 0.35]]) +l15 = LG.LineString([[0.0, 3.0], [4.0, 3.0]]) +l16 = LG.LineString([[0.3, -0.7], [1.0, 0.0], [3.0, 0.6]]) + +r1 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]) +r2 = LG.LinearRing([[0.5, 0.2], [0.6, 0.4], [0.7, 0.2], [0.5, 0.2]]) +r3 = LG.LinearRing([[0.2, 0.7], [0.4, 0.9], [0.5, 0.6], [0.2, 0.7]]) +r4 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]) +r5 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [2.0, 1.0], [1.0, 2.0], [1.0, 1.0], [0.0, 0.0]]) +r6 = LG.LinearRing([[0.0, 0.0], [-1.0, 0.0], [-1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]) +r7 = LG.LinearRing([[0.5, 0.5], [1.5, 0.5], [1.5, 1.5], [0.5, 1.5], [0.5, 0.5]]) +r8 = LG.LinearRing([[0.1, 0.1], [0.2, 0.1], [0.2, 0.2], [0.1, 0.2], [0.1, 0.1]]) +r9 = LG.LinearRing([[1.0, -0.5], [0.5, -1.0], [1.5, -1.0], [1.0, -0.5]]) +r10 = LG.LinearRing([[0.5, 0.2], [0.6, 0.4], [0.3, 0.3], [0.5, 0.2]]) +r11 = LG.LinearRing([[0.55, 0.21], [0.55, 0.23], [0.65, 0.23], [0.66, 0.21], [0.55, 0.21]]) + +p1 = LG.Polygon(r4, [r2, r3]) +p2 = LG.Polygon(r4) +p3 = LG.Polygon(r2) +p4 = LG.Polygon(r6) +p5 = LG.Polygon(r9) +p6 = LG.Polygon(r11) +p7 = LG.Polygon(r7) +p8 = LG.Polygon([[[0.0, 0.0], [0.0, 3.0], [1.0, 2.0], [2.0, 3.0], [3.0, 2.0], [4.0, 3.0], [4.0, 0.0], [0.0, 0.0]]]) +p9 = LG.Polygon([[[0.0, 0.0], [0.0, 3.0], [1.0, 4.0], [2.0, 3.0], [3.0, 4.0], [4.0, 3.0], [4.0, 0.0], [0.0, 0.0]]]) +p10 = LG.Polygon([ + [[0.1, 0.5], [0.1, 0.99], [0.6, 0.99], [0.6, 0.5], [0.1, 0.5]], + [[0.15, 0.55], [0.15, 0.95], [0.55, 0.95], [0.55, 0.55], [0.15, 0.55]] +]) +p11 = LG.Polygon(r3) + +mpt1 = LG.MultiPoint([pt1, pt2]) +mpt2 = LG.MultiPoint([pt2, pt3]) +mpt3 = LG.MultiPoint([pt4, pt5]) +ml1 = LG.MultiLineString([l5, l6, l7]) +ml2 = LG.MultiLineString([l1]) +mp1 = LG.MultiPolygon([p1]) +mp2 = LG.MultiPolygon([p6, p7]) +gc1 = LG.GeometryCollection([pt1, l5, p6]) + +test_pairs = [ + # Points and geometries + (pt1, pt1, "pt1", "pt1"), # Same point + (pt1, pt2, "pt1", "pt2"), # Different point + (pt1, l1, "pt1", "l1"), # Point on line endpoint + (pt2, l1, "pt2", "l1"), # Point outside line + (pt3, l1, "pt3", "l1"), # Point on line segment + (pt4, l1, "pt4", "l1"), # Point on line vertex between segments + (l1, pt3, "l1", "pt3"), # oint on line segment (order swapped) + (pt1, r1, "pt1", "r1"), # Point on ring "endpoint" + (pt2, r1, "pt2", "r1"), # Point outside ring + (pt3, r1, "pt3", "r1"), # Point on ring segment + (pt4, r1, "pt4", "r1"), # Point on ring vertex between segments + (r1, pt3, "r1", "pt3"), # Point on ring segment (order swapped) + (p1, pt1, "p1", "pt1"), # Point on vertex of polygon + (pt2, p1, "pt2", "p1"), # Point outside of polygon's external ring + (pt4, p1, "pt4", "p1"), # Point on polygon's edge + (pt5, p1, "pt5", "p1"), # Point inside of polygon + (pt6, p1, "pt6", "p1"), # Point on hole edge + (pt7, p1, "pt7", "p1"), # Point inside of polygon hole + (p1, pt5, "p1", "pt5"), # Point inside of polygon (order swapped) + # # Lines and geometries + (l1, l1, "l1", "l1"), # Same line + (l2, l1, "l2", "l1"), # L2 is one segment of l1 + (l3, l1, "l3", "l1"), # L3 shares one segment with l1 and has one segment outside + (l4, l1, "l4", "l1"), # L4 shares half of each of l1 segments + (l5, l1, "l5", "l1"), # L5 shares one endpoint with l1 but not segments + (l6, l1, "l6", "l1"), # Lines are disjoint + (l7, l1, "l7", "l1"), # L7 crosses through one of l1's segments + (l8, l1, "l8", "l1"), # Overlaps one segment and crosses through another segment + (l9, l1, "l9", "l1"), # Two segments touch and two segments crosses + (l16, l1, "l16", "l1"), # L16 bounces off of l1's corner + (l1, r1, "l1", "r1"), # Line inside of ring + (l3, r1, "l3", "r1"), # Line covers one edge of linear ring and has segment outside + (l5, r1, "l5", "r1"), # Line and linear ring are only covered by vertex + (l6, r1, "l6", "r1"), # Line and ring are disjoint + (l7, r1, "l7", "r1"), # Line crosses through two ring edges + (l8, r1, "l8", "r1"), # Line crosses through two ring edges and touches third edge + (l10, r1, "l10", "r1"), # Line is equal to linear ring + (l11, r1, "l11", "r1"), # Line covers linear ring and then has extra segment + (l1, p1, "l1", "p1"), # Line on polygon edge + (l3, p1, "l3", "p1"), # Line on polygon edge and extending beyond polygon edge + (l5, p1, "l5", "p1"), # Line outside polygon connected by a vertex + (l7, p1, "l7", "p1"), # Line through polygon cutting to the outside + (l12, p1, "l12", "p1"), # Line inside polygon + (l13, p1, "l13", "p1"), # Line outside of polygon + (l14, p1, "l14", "p1"), # Line in polygon hole + (l15, p8, "l15", "p8"), # Line outside crown-shaped polygon but touching edges + (l15, p9, "l15", "p9"), # Line within crown-shaped polygon but touching edges + # Ring and geometries + (r1, l1, "r1", "l1"), # Line is within linear ring + (r1, l3, "r1", "l3"), # Line covers one edge of linear ring and has segment outside + (r1, l5, "r1", "l5"), # Line and linear ring are only connected at vertex + (r1, l6, "r1", "l6"), # Line and linear ring are disjoint + (r1, l7, "r1", "l7"), # Line crosses though two ring edges + (r1, l8, "r1", "l8"), # Line crosses through two ring edges and touches third edge + (r1, l10, "r1", "l10"), # Line is equal to linear ring + (r1, l11, "r1", "l11"), # Line covers linear ring and then has extra segment + (r1, r1, "r1", "r1"), # Same rings + (r2, r1, "r2", "r1"), # Disjoint ring with one "inside" of hole created + (r3, r1, "r3", "r1"), # Disjoint ring with one "outside" of hole created + (r4, r1, "r4", "r1"), # Rings share two sides and rest of sides dont touch + (r1, r5, "r1", "r5"), # Ring shares all edges with other ring, plus an extra loop + (r1, r6, "r1", "r6"), # Rings share just one vertex + (r1, r7, "r1", "r7"), # Rings cross one another + (r4, p1, "r4", "p1"), # Ring on boundary of polygon + (r1, p1, "r1", "p1"), # Ring on boundary and cutting through polygon + (r2, p1, "r2", "p1"), # Ring on hole bounday + (r6, p1, "r6", "p1"), # Ring touches polygon at one vertex + (r7, p1, "r7", "p1"), # Ring crosses through polygon + (r8, p1, "r8", "p1"), # Ring inside of polygon + (r9, p1, "r9", "p1"), # Ring outside of polygon + (r10, p1, "r10", "p1"), # Ring inside of polygon and shares hole's edge + (r11, p1, "r11", "p1"), # Ring inside of polygon hole + # Polygon and geometries + (p1, p1, "p1", "p1"), # Same polygons + (p1, p2, "p1", "p2"), # P1 and p2 are the same but p1 has holes + (p2, p1, "p2", "p1"), # P1 and p2 are the same but p1 has holes (order swapped) + (p3, p1, "p3", "p1"), # P3 is equal to one of p1's holes + (p4, p1, "p4", "p1"), # Polygon's share just one vertex + (p5, p1, "p5", "p1"), # Polygon outside of other polygon + (p6, p1, "p6", "p1"), # Polygon inside of other polygon's hole + (p7, p1, "p7", "p1"), # Polygons overlap + (p10, p1, "p10", "p1"), # Polygon's with nested holes + # Multigeometries + (mpt1, mpt1, "mpt1", "mpt1"), # Same set of points for multipoints + (mpt1, mpt2, "mpt1", "mpt2"), # Some point matches, others are different + (mpt1, mpt3, "mpt1", "mpt3"), # No shared points + (ml1, ml2, "ml1", "ml2"), # Lines in ml1 cross and touch ml2 + (mp1, mp2, "mp1", "mp2"), # Polygons in mp1 are inside hole and overlap + (gc1, ml1, "gc1", "ml1"), # Make sure collection works with multi-geom +] + +function test_geom_relation(GO_f, LG_f, f_name; swap_points = false) + for (g1, g2, sg1, sg2) in test_pairs + if swap_points + g1, g2 = g2, g1 + sg1, sg2 = sg2, sg1 + end + go_val = GO_f(g1, g2) + lg_val = LG_f(g1, g2) + @test go_val == lg_val + go_val != lg_val && println("\n" * "↑ TEST INFO: " * sg1 * " " * f_name * " " * sg2 * "\n\n") + end +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" begin test_geom_relation(GO.crosses, LG.crosses, "crosses") end +@testset "Disjoint" begin test_geom_relation(GO.disjoint, LG.disjoint, "disjoint")end +@testset "Equals" begin include("geom_relations/equals.jl") 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 \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 56b79a738..08d6b43c3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,6 +17,7 @@ const GO = GeometryOps # Methods @testset "Barycentric coordinate operations" begin include("methods/barycentric.jl") end @testset "Centroid" begin include("methods/centroid.jl") end + @testset "Geometry Relations" begin include("methods/geom_relations.jl") end @testset "Bools" begin include("methods/bools.jl") end @testset "Signed Area" begin include("methods/signed_area.jl") end From 053e3bc2ed7c889713e007840105a57a6d0f532f Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 2 Jan 2024 00:55:06 -0800 Subject: [PATCH 32/33] Cleanup and comment more --- src/GeometryOps.jl | 4 +- src/methods/{geom_relations => }/bools.jl | 0 src/methods/{geom_relations => }/equals.jl | 0 .../geom_relations/geom_geom_processors.jl | 866 +++++++++--------- .../geom_relations/geom_in_out_geom.jl | 737 --------------- test/methods/bools.jl | 81 +- test/methods/{geom_relations => }/equals.jl | 0 test/methods/geom_in_geom.jl | 174 ---- test/methods/geom_on_geom.jl | 17 - test/methods/geom_relations.jl | 1 - test/methods/geom_relations/contains.jl | 190 ---- test/methods/geom_relations/coveredby.jl | 183 ---- test/methods/geom_relations/covers.jl | 128 --- test/methods/geom_relations/crosses.jl | 86 -- test/methods/geom_relations/disjoint.jl | 266 ------ test/methods/geom_relations/intersects.jl | 201 ---- test/methods/geom_relations/overlaps.jl | 151 --- test/methods/geom_relations/touches.jl | 61 -- test/methods/geom_relations/within.jl | 306 ------- test/runtests.jl | 2 +- 20 files changed, 452 insertions(+), 3002 deletions(-) rename src/methods/{geom_relations => }/bools.jl (100%) rename src/methods/{geom_relations => }/equals.jl (100%) delete mode 100644 src/methods/geom_relations/geom_in_out_geom.jl rename test/methods/{geom_relations => }/equals.jl (100%) delete mode 100644 test/methods/geom_in_geom.jl delete mode 100644 test/methods/geom_on_geom.jl delete mode 100644 test/methods/geom_relations/contains.jl delete mode 100644 test/methods/geom_relations/coveredby.jl delete mode 100644 test/methods/geom_relations/covers.jl delete mode 100644 test/methods/geom_relations/crosses.jl delete mode 100644 test/methods/geom_relations/disjoint.jl delete mode 100644 test/methods/geom_relations/intersects.jl delete mode 100644 test/methods/geom_relations/overlaps.jl delete mode 100644 test/methods/geom_relations/touches.jl delete mode 100644 test/methods/geom_relations/within.jl diff --git a/src/GeometryOps.jl b/src/GeometryOps.jl index 821ee9f9d..6f5a1247c 100644 --- a/src/GeometryOps.jl +++ b/src/GeometryOps.jl @@ -19,7 +19,7 @@ const Edge = Tuple{TuplePoint,TuplePoint} include("primitives.jl") include("utils.jl") -include("methods/geom_relations/bools.jl") +include("methods//bools.jl") include("methods/signed_distance.jl") include("methods/signed_area.jl") include("methods/centroid.jl") @@ -33,7 +33,7 @@ include("methods/geom_relations/overlaps.jl") include("methods/geom_relations/within.jl") include("methods/polygonize.jl") include("methods/barycentric.jl") -include("methods/geom_relations/equals.jl") +include("methods/equals.jl") include("methods/geom_relations/geom_geom_processors.jl") include("methods/orientation.jl") include("methods/geom_relations/touches.jl") diff --git a/src/methods/geom_relations/bools.jl b/src/methods/bools.jl similarity index 100% rename from src/methods/geom_relations/bools.jl rename to src/methods/bools.jl diff --git a/src/methods/geom_relations/equals.jl b/src/methods/equals.jl similarity index 100% rename from src/methods/geom_relations/equals.jl rename to src/methods/equals.jl diff --git a/src/methods/geom_relations/geom_geom_processors.jl b/src/methods/geom_relations/geom_geom_processors.jl index ee685f46b..ef3ae70c9 100644 --- a/src/methods/geom_relations/geom_geom_processors.jl +++ b/src/methods/geom_relations/geom_geom_processors.jl @@ -1,46 +1,42 @@ +#= Code is based off of DE-9IM Standards (https://en.wikipedia.org/wiki/DE-9IM) +and attempts a standardized solution for most of the functions. +=# + @enum PointOrientation point_in=1 point_on=2 point_out=3 @enum LineOrientation line_cross=1 line_hinge=2 line_over=3 line_out=4 -""" - _point_curve_process( - point, curve; - process::ProcessType = within_process, - exclude_boundaries = false, - repeated_last_coord = false, - )::Bool - -Determines if a point meets the given process checks with respect to a curve. -This curve includes just the line segments that make up the curve. Even if the -curve has a repeated last point that "closes" the curve, this function does not -include the space within the closed curve as a part of the geometry. Point -should be an object of Point trait and curve should be an object with a line -string or a linear ring trait. - -If checking within, then the point must be on a segment of the line and if -checking disjoint the point must not be on any segment of the line. - -Beyond specifying the process type, user can also specify if the geometry -boundaries should be included in the checks and if the curve should be closed -with repeated a repeated last coordinate matching the first coordinate. -""" + +#= +Determines if a point meets the given checks with respect to a curve. + +If in_allow is true, the point can be on the curve interior. +If on_allow is true, the point can be on the curve boundary. +If out_allow is true, the point can be disjoint from the curve. + +If the point is in an "allowed" location, return true. Else, return false. + +If closed_curve is true, curve is treated as a closed curve where the first and +last point are connected by a segment. +=# function _point_curve_process( point, curve; in_allow, on_allow, out_allow, - repeated_last_coord = false, + closed_curve = false, ) + # Determine if curve is closed n = GI.npoint(curve) first_last_equal = equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) - repeated_last_coord |= first_last_equal + closed_curve |= first_last_equal n -= first_last_equal ? 1 : 0 # Loop through all curve segments - p_start = GI.getpoint(curve, repeated_last_coord ? n : 1) - @inbounds for i in (repeated_last_coord ? 1 : 2):n + p_start = GI.getpoint(curve, closed_curve ? n : 1) + @inbounds for i in (closed_curve ? 1 : 2):n p_end = GI.getpoint(curve, i) - seg_val = point_segment_orientation(point, p_start, p_end) + seg_val = _point_segment_orientation(point, p_start, p_end) seg_val == point_in && return in_allow if seg_val == point_on - if !repeated_last_coord + if !closed_curve # if point is on curve endpoints, it is "on" i == 2 && equals(point, p_start) && return on_allow i == n && equals(point, p_end) && return on_allow end @@ -51,130 +47,21 @@ function _point_curve_process( return out_allow end -""" - point_segment_orientation( - point::Point, start::Point, stop::Point; - in::T = 1, on::T = -1, out::T = 0, - )::T where {T} - -Determines if a point is in, on, or out of a segment. If the point is 'on' the -segment it is on one of the segments endpoints. If it is 'in', it is on any -other point of the segment. If the point is not on any part of the segment, it -is 'out' of the segment. -""" -function point_segment_orientation( - point, start, stop; - in::T = point_in, on::T = point_on, out::T = point_out, -) where {T} - # 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) - Δx_seg = x2 - x1 - Δy_seg = y2 - y1 - Δx_pt = x - x1 - Δy_pt = y - y1 - if (Δx_pt == 0 && Δy_pt == 0) || (Δx_pt == Δx_seg && Δy_pt == Δy_seg) - # If point is equal to the segment start or end points - return on - else - #= - Determine if the point is on the segment -> see if vector from segment - start to point is parallel to segment and if point is between the - segment endpoints - =# - on_line = _isparallel(Δx_seg, Δy_seg, Δx_pt, Δy_pt) - !on_line && return out - between_endpoints = - (x2 > x1 ? x1 <= x <= x2 : x2 <= x <= x1) && - (y2 > y1 ? y1 <= y <= y2 : y2 <= y <= y1) - !between_endpoints && return out - end - return in -end - -""" - point_filled_curve_orientation( - point, curve; - in::T = 1, on::T = -1, out::T = 0, - )::T where {T} - -Determine if point is in, on, or out of a closed curve, which includes the space -enclosed by the closed curve. Point should be an object of Point trait and -curve should be an object with a line string or linear ring trait, that is -assumed to be closed, regardless of repeated last point. - -Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -`In` means the point is within the closed curve (excluding edges and vertices). -`On` means the point is on an edge or a vertex of the closed curve. -`Out` means the point is outside of the closed curve. +#= +Determines if a point meets the given checks with respect to a polygon. -Note that this uses the Algorithm by Hao and Sun (2018): -https://doi.org/10.3390/sym10100477 -Paper seperates orientation of point and edge into 26 cases. For each case, it -is either a case where the point is on the edge (returns on), where a ray from -the point (x, y) to infinity along the line y = y cut through the edge (k += 1), -or the ray does not pass through the edge (do nothing and continue). If the ray -passes through an odd number of edges, it is within the curve, else outside of -of the curve if it didn't return 'on'. -See paper for more information on cases denoted in comments. -""" -function point_filled_curve_orientation( - point, curve; - in::T = point_in, on::T = point_on, out::T = point_out, -) where {T} - x, y = GI.x(point), GI.y(point) - n = GI.npoint(curve) - n -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) ? 1 : 0 - k = 0 # counter for ray crossings - p_start = GI.getpoint(curve, n) - @inbounds for i in 1:n - p_end = GI.getpoint(curve, i) - v1 = GI.y(p_start) - y - v2 = GI.y(p_end) - y - if !((v1 < 0 && v2 < 0) || (v1 > 0 && v2 > 0)) # if not cases 11 or 26 - u1 = GI.x(p_start) - x - u2 = GI.x(p_end) - x - c1 = u1 * v2 # first element of cross product summation - c2 = u2 * v1 # second element of cross product summation - f = c1 - c2 - if v2 > 0 && v1 ≤ 0 # Case 3, 9, 16, 21, 13, or 24 - (c1 ≈ c2) && return on # Case 16 or 21 - f > 0 && (k += 1) # Case 3 or 9 - elseif v1 > 0 && v2 ≤ 0 # Case 4, 10, 19, 20, 12, or 25 - (c1 ≈ c2) && return on # Case 19 or 20 - f < 0 && (k += 1) # Case 4 or 10 - elseif v2 == 0 && v1 < 0 # Case 7, 14, or 17 - (c1 ≈ c2) && return on # Case 17 - elseif v1 == 0 && v2 < 0 # Case 8, 15, or 18 - (c1 ≈ c2) && return on # Case 18 - elseif v1 == 0 && v2 == 0 # Case 1, 2, 5, 6, 22, or 23 - u2 ≤ 0 && u1 ≥ 0 && return on # Case 1 - u1 ≤ 0 && u2 ≥ 0 && return on # Case 2 - end - end - p_start = p_end - end - return iseven(k) ? out : in -end +If in_allow is true, the point can be within the polygon interior +If on_allow is true, the point can be on the polygon boundary. +If out_allow is true, the point can be disjoint from the polygon. -""" - _point_polygon_process( - point, polygon; - process::ProcessType = within_process, - exclude_boundaries = false, - )::Bool - -Determines if a point meets the given process checks with respect to a polygon, -which excludes any holes specified by the polygon. Point should be an -object of Point trait and polygon should an object with a Polygon trait. -""" +If the point is in an "allowed" location, return true. Else, return false. +=# function _point_polygon_process( point, polygon; in_allow, on_allow, out_allow, ) # Check interaction of geom with polygon's exterior boundary - ext_val = point_filled_curve_orientation(point, GI.getexterior(polygon)) + ext_val = _point_filled_curve_orientation(point, GI.getexterior(polygon)) # If a point is outside, it isn't interacting with any holes ext_val == point_out && return out_allow # if a point is on an external boundary, it isn't interacting with any holes @@ -182,7 +69,7 @@ function _point_polygon_process( # If geom is within the polygon, need to check interactions with holes for hole in GI.gethole(polygon) - hole_val = point_filled_curve_orientation(point, hole) + hole_val = _point_filled_curve_orientation(point, hole) # If a point in in a hole, it is outside of the polygon hole_val == point_in && return out_allow # If a point in on a hole edge, it is on the edge of the polygon @@ -193,38 +80,28 @@ function _point_polygon_process( return in_allow end -function _segment_segment_orientation( - (a_point, b_point), (c_point, d_point); - cross::T = line_cross, hinge::T = line_hinge, - over::T = line_over, out::T = line_out, -) where T - (ax, ay) = _tuple_point(a_point) - (bx, by) = _tuple_point(b_point) - (cx, cy) = _tuple_point(c_point) - (dx, dy) = _tuple_point(d_point) - meet_type = ExactPredicates.meet((ax, ay), (bx, by), (cx, cy), (dx, dy)) - # Lines meet at one point within open segments - meet_type == 1 && return cross - # Lines don't meet at any points - meet_type == -1 && return out - # Lines meet at one or more points within closed segments - if _isparallel(((ax, ay), (bx, by)), ((cx, cy), (dx, dy))) - min_x, max_x = cx < dx ? (cx, dx) : (dx, cx) - min_y, max_y = cy < dy ? (cy, dy) : (dy, cy) - if ( - ((ax ≤ min_x && bx ≤ min_x) || (ax ≥ max_x && bx ≥ max_x)) && - ((ay ≤ min_y && by ≤ min_y) || (ay ≥ max_y && by ≥ max_y)) - ) - # a_point and b_point are on the same side of segment, don't overlap - return hinge - else - return over - end - end - # if lines aren't parallel then they must hinge - return hinge -end +#= +Determines if a line meets the given checks with respect to a curve. + +If over_allow is true, segments of the line and curve can be co-linear. +If cross_allow is true, segments of the line and curve can cross. +If on_allow is true, endpoints of either the line or curve can intersect a + segment of the other geometry. +If cross_allow is true, segments of the line and curve can be disjoint. + +If in_require is true, the interiors of the line and curve must meet in at least + one point. +If on_require is true, the bounday of one of the two geometries can meet the + interior or boundary of the other geometry in at least one point. +If out_require is true, there must be at least one point of the given line that + is exterior of the curve. + +If the point is in an "allowed" location and meets all requirments, return true. +Else, return false. +If closed_line is true, line is treated as a closed line where the first and +last point are connected by a segment. Same with closed_curve. +=# function _line_curve_process( line, curve; over_allow, cross_allow, on_allow, out_allow, @@ -232,6 +109,7 @@ function _line_curve_process( closed_line = false, closed_curve = false, ) + # Set up requirments in_req_met = !in_require on_req_met = !on_require out_req_met = !out_require @@ -258,30 +136,31 @@ function _line_curve_process( (l_start, l_end), (c_start, c_end), ) - # if segments are touching + # If segments are co-linear if seg_val == line_over !over_allow && return false # at least one point in, meets requirments in_req_met = true - point_val = point_segment_orientation( + point_val = _point_segment_orientation( l_start, c_start, c_end, ) + # If entire segment isn't covered, consider remaining section if point_val != point_out - if point_segment_orientation( + if _point_segment_orientation( l_end, c_start, c_end, ) != point_out l_start = l_end i += 1 break - elseif point_segment_orientation( + elseif _point_segment_orientation( c_start, l_start, l_end, ) != point_out && !equals(l_start, c_start) l_start = c_start break - elseif point_segment_orientation( + elseif _point_segment_orientation( c_end, l_start, l_end, ) != point_out && !equals(l_start, c_end) @@ -293,7 +172,8 @@ function _line_curve_process( if seg_val == line_cross !cross_allow && return false in_req_met = true - elseif seg_val == line_hinge + elseif seg_val == line_hinge # could cross or overlap + # Determine location of intersection point on each segment _, fracs = _intersection_point( (_tuple_point(l_start), _tuple_point(l_end)), (_tuple_point(c_start), _tuple_point(c_end)) @@ -313,7 +193,7 @@ function _line_curve_process( end end - if ( + if ( # don't consider edges of curves as they can't cross (β == 0 && !closed_curve && j == 2) || (β == 1 && !closed_curve && j == nc) || (α == 0 && !closed_line && i == 2) || @@ -323,6 +203,7 @@ function _line_curve_process( on_req_met = true else in_req_met = true + # If needed, determine if hinge actually crosses if (!cross_allow || !over_allow) && α != 0 && β != 0 l, c = if β == 1 if α == 1 @@ -366,115 +247,394 @@ function _line_curve_process( return in_req_met && on_req_met && out_req_met end -function _line_curve_crosses_overlap_interactions( - line, curve; - closed_line = false, closed_curve = false, +#= +Determines if a line meets the given checks with respect to a polygon. + +If in_allow is true, segments of the line can be in the polygon interior. +If on_allow is true, segments of the line can be on the polygon's boundary. +If out_allow is true, segments of the line can be outside of the polygon. + +If in_require is true, the interiors of the line and polygon must meet in at + least one point. +If on_require is true, the line must have at least one point on the polygon'same + boundary. +If out_require is true, the line must have at least one point outside of the + polygon. + +If the point is in an "allowed" location and meets all requirments, return true. +Else, return false. + +If closed_line is true, line is treated as a closed line where the first and +last point are connected by a segment. +=# +function _line_polygon_process( + line, polygon; + in_allow, on_allow, out_allow, + in_require, on_require, out_require, + closed_line = false, ) - nl = GI.npoint(line) - nc = GI.npoint(curve) - first_last_equal_line = equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) - first_last_equal_curve = equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) - nl -= first_last_equal_line ? 1 : 0 - nc -= first_last_equal_curve ? 1 : 0 - closed_line |= first_last_equal_line - closed_curve |= first_last_equal_curve - # Loop over each line segment - crosses = false - overlaps = false - out = false - l_start = GI.getpoint(line, closed_line ? nl : 1) - for i in (closed_line ? 1 : 2):nl - l_end = GI.getpoint(line, i) - c_start = GI.getpoint(curve, closed_curve ? nc : 1) - for j in (closed_curve ? 1 : 2):nc - crosses && overlaps && return (crosses, overlaps) - c_end = GI.getpoint(curve, j) - seg_val = _segment_segment_orientation( - (l_start, l_end), - (c_start, c_end), - ) - if seg_val == line_out - out = true - elseif seg_val == line_over - overlaps = true - - elseif seg_val == line_cross - crosses = true - elseif seg_val == line_hinge - out = true - _, fracs = _intersection_point( - (_tuple_point(l_start), _tuple_point(l_end)), - (_tuple_point(c_start), _tuple_point(c_end)) - ) - (α, β) = - if !isnothing(fracs) - fracs - else - if equals(l_start, c_start) - (0, 0) - elseif equals(l_start, c_end) - (0, 1) - elseif equals(l_end, c_start) - (1, 0) - else # equals(l_end, c_end) - (1, 1) - end - end - if ( - !(β == 0) && - !(β == 1 && !closed_curve && j == nc) && - !(α == 0 && !closed_line && i == 2) && - !(α == 1 && !closed_line && i == nl) - ) - crosses = true - end - end - c_start = c_end + in_req_met = !in_require + on_req_met = !on_require + out_req_met = !out_require + # Check interaction of line with polygon's exterior boundary + in_curve, on_curve, out_curve = _line_filled_curve_interactions( + line, GI.getexterior(polygon); + closed_line = closed_line, + ) + if on_curve + !on_allow && return false + on_req_met = true + end + if out_curve + !out_allow && return false + out_req_met = true + end + # If no points within the polygon, the line is disjoint and we are done + !in_curve && return in_req_met && on_req_met && out_req_met + + # Loop over polygon holes + for hole in GI.gethole(polygon) + in_hole, on_hole, out_hole =_line_filled_curve_interactions( + line, hole; + closed_line = closed_line, + ) + if in_hole # line in hole is equivalent to being out of polygon + !out_allow && return false + out_req_met = true end - l_start = l_end + if on_hole # hole bounday is polygon boundary + !on_allow && return false + on_req_met = true + end + if !out_hole # entire line is in/on hole, can't be in/on other holes + in_curve = false + break + end + end + if in_curve # entirely of curve isn't within a hole + !in_allow && return false + in_req_met = true end - return crosses, overlaps + return in_req_met && on_req_met && out_req_met end -function _line_filled_curve_interactions( - line, curve; - closed_line = false, -) - in_curve = false - on_curve = false - out_curve = false +#= +Determines if a polygon meets the given checks with respect to a polygon. - # Determine number of points in curve and line - nl = GI.npoint(line) - nc = GI.npoint(curve) - first_last_equal_line = equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) - first_last_equal_curve = equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) - nl -= first_last_equal_line ? 1 : 0 - nc -= first_last_equal_curve ? 1 : 0 - closed_line |= first_last_equal_line +If in_allow is true, the polygon's interiors must intersect. +If on_allow is true, the one of the polygon's boundaries must either interact + with the other polygon's boundary or interior. +If out_allow is true, the first polygon must have interior regions outside of + the second polygon. - # See if first point is in an acceptable orientation - l_start = GI.getpoint(line, closed_line ? nl : 1) - point_val = point_filled_curve_orientation(l_start, curve) - if point_val == point_in - in_curve = true - elseif point_val == point_on - on_curve = true - else # point_val == point_out - out_curve = true +If in_require is true, the polygon interiors must meet in at least one point. +If on_require is true, one of the polygon's must have at least one boundary + point in or on the other polygon. +If out_require is true, the first polygon must have at least one interior point + outside of the second polygon. + +If the point is in an "allowed" location and meets all requirments, return true. +Else, return false. +=# +function _polygon_polygon_process( + poly1, poly2; + in_allow, on_allow, out_allow, + in_require, on_require, out_require, +) + in_req_met = !in_require + on_req_met = !on_require + out_req_met = !out_require + # Check if exterior of poly1 is within poly2 + ext1 = GI.getexterior(poly1) + ext2 = GI.getexterior(poly2) + # Check if exterior of poly1 is in polygon 2 + e1_in_p2, e1_on_p2, e1_out_p2 = _line_polygon_interactions( + ext1, poly2; + closed_line = true, + ) + if e1_on_p2 + !on_allow && return false + on_req_met = true + end + if e1_out_p2 + !out_allow && return false + out_req_met = true end - # Check for any intersections between line and curve - for i in (closed_line ? 1 : 2):nl - l_end = GI.getpoint(line, i) - c_start = GI.getpoint(curve, nc) - # If already interacted with all regions of curve, can stop - in_curve && on_curve && out_curve && break - # Check next segment of line against curve - for j in 1:nc - c_end = GI.getpoint(curve, j) - # Check if two line and curve segments meet - seg_val = _segment_segment_orientation( + if !e1_in_p2 + # if exterior ring isn't in poly2, check if it surrounds poly2 + _, _, e2_out_e1 = _line_filled_curve_interactions( + ext2, ext1; + closed_line = true, + ) # if they really are disjoint, we are done + e2_out_e1 && return in_req_met && on_req_met && out_req_met + end + # If interiors interact, check if poly2 interacts with any of poly1's holes + for h1 in GI.gethole(poly1) + h1_in_p2, h1_on_p2, h1_out_p2 = _line_polygon_interactions( + h1, poly2; + closed_line = true, + ) + if h1_on_p2 + !on_allow && return false + on_req_met = true + end + if h1_out_p2 + !out_allow && return false + out_req_met = true + end + if !h1_in_p2 + # If hole isn't in poly2, see if poly2 is in hole + _, _, e2_out_h1 = _line_filled_curve_interactions( + ext2, h1; + closed_line = true, + ) + # hole encompasses all of poly2 + !e2_out_h1 && return in_req_met && on_req_met && out_req_met + break + end + end + #= + poly2 isn't outside of poly1 and isn't in a hole, poly1 interior must + interact with poly2 interior + =# + !in_allow && return false + in_req_met = true + + # If any of poly2 holes are within poly1, part of poly1 is exterior to poly2 + for h2 in GI.gethole(poly2) + h2_in_p1, h2_on_p1, _ = _line_polygon_interactions( + h2, poly1; + closed_line = true, + ) + if h2_on_p1 + !on_allow && return false + on_req_met = true + end + if h2_in_p1 + !out_allow && return false + out_req_met = true + end + end + return in_req_met && on_req_met && out_req_met +end + +#= +Determines if a point is in, on, or out of a segment. If the point is `on` the +segment it is on one of the segments endpoints. If it is `in`, it is on any +other point of the segment. If the point is not on any part of the segment, it +is `out` of the segment. + +Point should be an object of point trait and curve should be an object with a +linestring or linearring trait. + +Can provide values of in, on, and out keywords, which determines return values +for each scenario. +=# +function _point_segment_orientation( + point, start, stop; + in::T = point_in, on::T = point_on, out::T = point_out, +) where {T} + # 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) + Δx_seg = x2 - x1 + Δy_seg = y2 - y1 + Δx_pt = x - x1 + Δy_pt = y - y1 + if (Δx_pt == 0 && Δy_pt == 0) || (Δx_pt == Δx_seg && Δy_pt == Δy_seg) + # If point is equal to the segment start or end points + return on + else + #= + Determine if the point is on the segment -> see if vector from segment + start to point is parallel to segment and if point is between the + segment endpoints + =# + on_line = _isparallel(Δx_seg, Δy_seg, Δx_pt, Δy_pt) + !on_line && return out + between_endpoints = + (x2 > x1 ? x1 <= x <= x2 : x2 <= x <= x1) && + (y2 > y1 ? y1 <= y <= y2 : y2 <= y <= y1) + !between_endpoints && return out + end + return in +end + +#= +Determine if point is in, on, or out of a closed curve, which includes the space +enclosed by the closed curve. + +`In` means the point is within the closed curve (excluding edges and vertices). +`On` means the point is on an edge or a vertex of the closed curve. +`Out` means the point is outside of the closed curve. + +Point should be an object of point trait and curve should be an object with a +linestring or linearring trait, that is assumed to be closed, regardless of +repeated last point. + +Can provide values of in, on, and out keywords, which determines return values +for each scenario. + +Note that this uses the Algorithm by Hao and Sun (2018): +https://doi.org/10.3390/sym10100477 +Paper seperates orientation of point and edge into 26 cases. For each case, it +is either a case where the point is on the edge (returns on), where a ray from +the point (x, y) to infinity along the line y = y cut through the edge (k += 1), +or the ray does not pass through the edge (do nothing and continue). If the ray +passes through an odd number of edges, it is within the curve, else outside of +of the curve if it didn't return 'on'. +See paper for more information on cases denoted in comments. +=# +function _point_filled_curve_orientation( + point, curve; + in::T = point_in, on::T = point_on, out::T = point_out, +) where {T} + x, y = GI.x(point), GI.y(point) + n = GI.npoint(curve) + n -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) ? 1 : 0 + k = 0 # counter for ray crossings + p_start = GI.getpoint(curve, n) + @inbounds for i in 1:n + p_end = GI.getpoint(curve, i) + v1 = GI.y(p_start) - y + v2 = GI.y(p_end) - y + if !((v1 < 0 && v2 < 0) || (v1 > 0 && v2 > 0)) # if not cases 11 or 26 + u1 = GI.x(p_start) - x + u2 = GI.x(p_end) - x + c1 = u1 * v2 # first element of cross product summation + c2 = u2 * v1 # second element of cross product summation + f = c1 - c2 + if v2 > 0 && v1 ≤ 0 # Case 3, 9, 16, 21, 13, or 24 + (c1 ≈ c2) && return on # Case 16 or 21 + f > 0 && (k += 1) # Case 3 or 9 + elseif v1 > 0 && v2 ≤ 0 # Case 4, 10, 19, 20, 12, or 25 + (c1 ≈ c2) && return on # Case 19 or 20 + f < 0 && (k += 1) # Case 4 or 10 + elseif v2 == 0 && v1 < 0 # Case 7, 14, or 17 + (c1 ≈ c2) && return on # Case 17 + elseif v1 == 0 && v2 < 0 # Case 8, 15, or 18 + (c1 ≈ c2) && return on # Case 18 + elseif v1 == 0 && v2 == 0 # Case 1, 2, 5, 6, 22, or 23 + u2 ≤ 0 && u1 ≥ 0 && return on # Case 1 + u1 ≤ 0 && u2 ≥ 0 && return on # Case 2 + end + end + p_start = p_end + end + return iseven(k) ? out : in +end + +#= +Determines the type of interaction between two line segments. If the segments +`cross`, this means that they have a single intersection point that isn't on +either of their enpoints. If they form a `hinge`, they meet at one of the +segments endpoints. If they are `over`, then they are co-linear for at least +some of the length of the segments. Finally, if they are `out`, then the +segments are disjoint. + +Point should be an object of point trait and curve should be an object with a +linestring or linearring trait. + +Can provide values of in, on, and out keywords, which determines return values +for each scenario. +=# +function _segment_segment_orientation( + (a_point, b_point), (c_point, d_point); + cross::T = line_cross, hinge::T = line_hinge, + over::T = line_over, out::T = line_out, +) where T + (ax, ay) = _tuple_point(a_point) + (bx, by) = _tuple_point(b_point) + (cx, cy) = _tuple_point(c_point) + (dx, dy) = _tuple_point(d_point) + meet_type = ExactPredicates.meet((ax, ay), (bx, by), (cx, cy), (dx, dy)) + # Lines meet at one point within open segments + meet_type == 1 && return cross + # Lines don't meet at any points + meet_type == -1 && return out + # Lines meet at one or more points within closed segments + if _isparallel(((ax, ay), (bx, by)), ((cx, cy), (dx, dy))) + min_x, max_x = cx < dx ? (cx, dx) : (dx, cx) + min_y, max_y = cy < dy ? (cy, dy) : (dy, cy) + if ( + ((ax ≤ min_x && bx ≤ min_x) || (ax ≥ max_x && bx ≥ max_x)) && + ((ay ≤ min_y && by ≤ min_y) || (ay ≥ max_y && by ≥ max_y)) + ) + # a_point and b_point are on the same side of segment, don't overlap + return hinge + else + return over + end + end + # if lines aren't parallel then they must hinge + return hinge +end + +#= +Determines if a line meets the given checks with respect to a filled-in curve. +By filled-in curve, I am referring to the exterior ring of a poylgon, for +example. + +If over_allow is true, segments of the line and curve can be co-linear. +If cross_allow is true, segments of the line and curve can cross. +If on_allow is true, endpoints of either the line or curve can intersect a + segment of the other geometry. +If cross_allow is true, segments of the line and curve can be disjoint. + +If in_require is true, the interiors of the line and curve must meet in at least + one point. +If on_require is true, the bounday of one of the two geometries can meet the + interior or boundary of the other geometry in at least one point. +If out_require is true, there must be at least one point of the given line that + is exterior of the curve. + +If the point is in an "allowed" location and meets all requirments, return true. +Else, return false. + +If closed_line is true, line is treated as a closed line where the first and +last point are connected by a segment. Same with closed_curve. +=# +function _line_filled_curve_interactions( + line, curve; + closed_line = false, +) + in_curve = false + on_curve = false + out_curve = false + + # Determine number of points in curve and line + nl = GI.npoint(line) + nc = GI.npoint(curve) + first_last_equal_line = equals(GI.getpoint(line, 1), GI.getpoint(line, nl)) + first_last_equal_curve = equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) + nl -= first_last_equal_line ? 1 : 0 + nc -= first_last_equal_curve ? 1 : 0 + closed_line |= first_last_equal_line + + # See if first point is in an acceptable orientation + l_start = GI.getpoint(line, closed_line ? nl : 1) + point_val = _point_filled_curve_orientation(l_start, curve) + if point_val == point_in + in_curve = true + elseif point_val == point_on + on_curve = true + else # point_val == point_out + out_curve = true + end + + # Check for any intersections between line and curve + for i in (closed_line ? 1 : 2):nl + l_end = GI.getpoint(line, i) + c_start = GI.getpoint(curve, nc) + # If already interacted with all regions of curve, can stop + in_curve && on_curve && out_curve && break + # Check next segment of line against curve + for j in 1:nc + c_end = GI.getpoint(curve, j) + # Check if two line and curve segments meet + seg_val = _segment_segment_orientation( (l_start, l_end), (c_start, c_end), ) @@ -487,8 +647,8 @@ function _line_filled_curve_interactions( out_curve = true else if seg_val == line_over - sp = point_segment_orientation(l_start, c_start, c_end) - lp = point_segment_orientation(l_end, c_start, c_end) + sp = _point_segment_orientation(l_start, c_start, c_end) + lp = _point_segment_orientation(l_end, c_start, c_end) if sp != point_in || lp != point_in #= Line crosses over segment endpoint, creating a hinge @@ -515,7 +675,7 @@ function _line_filled_curve_interactions( p_end = i ≤ npoints ? ipoints[i] : _tuple_point(l_end) - mid_val = point_filled_curve_orientation( + mid_val = _point_filled_curve_orientation( (p_start .+ p_end) ./ 2, curve, ) @@ -567,137 +727,7 @@ function _line_polygon_interactions( return in_ext, on_ext, out_ext end -function _line_polygon_process( - line, polygon; - in_allow, on_allow, out_allow, - in_require, on_require, out_require, - closed_line = false, -) - in_req_met = !in_require - on_req_met = !on_require - out_req_met = !out_require - # Check interaction of line with polygon's exterior boundary - in_curve, on_curve, out_curve = _line_filled_curve_interactions( - line, GI.getexterior(polygon); - closed_line = closed_line, - ) - if on_curve - !on_allow && return false - on_req_met = true - end - if out_curve - !out_allow && return false - out_req_met = true - end - !in_curve && return in_req_met && on_req_met && out_req_met - - # Loop over polygon holes - for hole in GI.gethole(polygon) - in_hole, on_hole, out_hole =_line_filled_curve_interactions( - line, hole; - closed_line = closed_line, - ) - if in_hole - !out_allow && return false - out_req_met = true - end - if on_hole - !on_allow && return false - on_req_met = true - end - if !out_hole # entire line is in/on hole, can't be in/on other holes - in_curve = false - break - end - end - if in_curve - !in_allow && return false - in_req_met = true - end - return in_req_met && on_req_met && out_req_met -end - -function _polygon_polygon_process( - poly1, poly2; - in_allow, on_allow, out_allow, - in_require, on_require, out_require, -) - in_req_met = !in_require - on_req_met = !on_require - out_req_met = !out_require - # Check if exterior of poly1 is within poly2 - ext1 = GI.getexterior(poly1) - ext2 = GI.getexterior(poly2) - e1_in_p2, e1_on_p2, e1_out_p2 = _line_polygon_interactions( - ext1, poly2; - closed_line = true, - ) - if e1_on_p2 - !on_allow && return false - on_req_met = true - end - if e1_out_p2 - !out_allow && return false - out_req_met = true - end - - if !e1_in_p2 - _, _, e2_out_e1 = _line_filled_curve_interactions( - ext2, ext1; - closed_line = true, - ) - e2_out_e1 && return in_req_met && on_req_met && out_req_met - end - # If interiors interact, check if poly2 interacts with any of poly1's holes - for h1 in GI.gethole(poly1) - h1_in_p2, h1_on_p2, h1_out_p2 = _line_polygon_interactions( - h1, poly2; - closed_line = true, - ) - if h1_on_p2 - !on_allow && return false - on_req_met = true - end - if h1_out_p2 - !out_allow && return false - out_req_met = true - end - if !h1_in_p2 - _, _, e2_out_h1 = _line_filled_curve_interactions( - ext2, h1; - closed_line = true, - ) - # hole encompasses all of poly2 - !e2_out_h1 && return in_req_met && on_req_met && out_req_met - break - end - end - #= - Poly2 isn't outside of poly1 and isn't in a hole, poly1 interior must - interact with poly2 interior - =# - !in_allow && return false - in_req_met = true - - # If any of poly2 holes are within poly1, if so, poly1 is exterior to poly2 - for h2 in GI.gethole(poly2) - h2_in_p1, h2_on_p1, _ = _line_polygon_interactions( - h2, poly1; - closed_line = true, - ) - if h2_on_p1 - !on_allow && return false - on_req_met = true - end - if h2_in_p1 - !out_allow && return false - out_req_met = true - end - end - return in_req_met && on_req_met && out_req_met -end - function _point_in_extent(p, extent::Extents.Extent) (x1, x2), (y1, y2) = extent.X, extent.Y return x1 ≤ GI.x(p) ≤ x2 && y1 ≤ GI.y(p) ≤ y2 -end +end \ No newline at end of file diff --git a/src/methods/geom_relations/geom_in_out_geom.jl b/src/methods/geom_relations/geom_in_out_geom.jl deleted file mode 100644 index 10ecd0f59..000000000 --- a/src/methods/geom_relations/geom_in_out_geom.jl +++ /dev/null @@ -1,737 +0,0 @@ -# export point_in_geom, point_in_polygon - -# """ -# point_in_geom( -# point, geom; -# in::T = 1, on::T = -1, out::T = 0, -# )::T - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# `In` means the point is within the geometry (excluding edges and vertices). -# `On` means the point is on an edge or a vertex of the geometry. -# `Out` means the point is outside of the geometry. -# """ -# point_in_geom( -# point, geom; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T} = point_in_geom( -# GI.trait(point), point, -# GI.trait(geom), geom; -# in = in, on = on, out = out, -# ) - -# """ -# line_in_geom( -# line, geom; -# in::T = 1, on::T = -1, out::T = 0, -# )::T - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# `In` means the line is within geometry (no segments on edges and vertices). -# `On` means the line has at least one segment on a geometry edge or a vertex. -# `Out` means the line has at least one segment outside of the geometry. -# """ -# line_in_geom( -# line, geom; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T} = line_in_geom( -# GI.trait(line), line, -# GI.trait(geom), geom; -# in = in, on = on, out = out, -# ) - -# # ring_in_geom(ring, geom) = ring_in_geom( -# # GI.trait(ring), ring, -# # GI.trait(geom), geom, -# # ) - -# """ -# point_in_geom( -# ::GI.PointTrait, point, -# ::GI.LineStringTrait, line; -# in::T = 1, on::T = -1, out::T = 0, -# )::T - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# Note that a point can only be within a linestring if the linestring is closed, -# by having an explicilty repeated last point. Even then, this means the point is -# within the ring created by the linestring, not on the linestring itself. -# `In` means the point is within the linestring (excluding edges and vertices). -# `On` means the point is on an edge or a vertex of the linestring. -# `Out` means the point is outside of the linestring. -# """ -# function point_in_geom( -# ::GI.PointTrait, point, -# ::GI.LineStringTrait, line; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T} -# results = if equals( -# GI.getpoint(line, 1), -# GI.getpoint(line, GI.npoint(line)), -# ) -# _point_in_extent(point, GI.extent(line)) || return out -# _point_in_closed_curve(point, line; in = in, on = on, out = out) -# else -# @warn "Linestring isn't closed. Point cannot be 'in' linestring." -# out -# end -# return results -# end - -# """ -# line_in_geom( -# ::GI.LineStringTrait, line1, -# ::GI.LineStringTrait, line2; -# in::T = 1, on::T = -1, out::T = 0, -# ) - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# Note that a linestring can only be within a linestring if that linestring is -# closed by having an explicilty repeated last point. Even then, this means the -# point is within the ring created by the linestring, not on the linestring -# itself. -# `In` means the line is within geometry (no segments on edges and vertices). -# `On` means the line has at least one segment on a geometry edge or a vertex. -# `Out` means the line has at least one segment outside of the geometry. -# """ -# function line_in_geom( -# ::GI.LineStringTrait, line1, -# ::GI.LineStringTrait, line2; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T} -# results = if equals( # if line2 is closed by a repeated last point -# GI.getpoint(line2, 1), -# GI.getpoint(line2, GI.npoint(line2)), -# ) -# Extents.intersects( -# GI.extent(line1), -# GI.extent(line2), -# ) || return out -# _line_in_closed_curve( -# line1, line2; -# close = false, -# ) -# else -# @warn "Linestring isn't closed. Point cannot be 'in' linestring." -# out -# end -# return results -# end - -# # function ring_in_geom(::GI.LinearRingTrait, ring, ::GI.LineStringTrait, line) -# # results = if equals( -# # GI.getpoint(line, 1), -# # GI.getpoint(line, GI.npoint(line)), -# # ) -# # Extents.intersects( -# # GI.extent(ring), -# # GI.extent(line), -# # ) || return (false, false) -# # _line_in_closed_curve(ring, line; close = true) -# # else -# # @warn "Linestring isn't closed. Point cannot be 'in' linestring." -# # (false, false) -# # end -# # return results -# # end -# """ -# point_in_geom( -# ::GI.PointTrait, point, -# ::GI.LinearRingTrait, ring; -# in::T = 1, on::T = -1, out::T = 0 -# )::T - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# `In` means the point is within the linear ring (excluding edges and vertices). -# `On` means the point is on an edge or a vertex of the linear ring. -# `Out` means the point is outside of the linear ring. -# """ -# function point_in_geom( -# ::GI.PointTrait, point, -# ::GI.LinearRingTrait, ring; -# in::T = 1, on::T = -1, out::T = 0 -# ) where {T} -# _point_in_extent(point, GI.extent(ring)) || return out -# return _point_in_closed_curve(point, ring; in = in, on = on, out = out) -# end - -# """ -# line_in_geom( -# ::GI.LineStringTrait, line, -# ::GI.LinearRingTrait, ring; -# in::T = 1, on::T = -1, out::T = 0, -# ) - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# `In` means the line is within the ring (no segments on edges and vertices). -# `On` means the line has at least one segment on a ring edge or a vertex. -# `Out` means the line has at least one segment outside of the ring. -# """ -# function line_in_geom( -# ::GI.LineStringTrait, line, -# ::GI.LinearRingTrait, ring; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T} -# Extents.intersects(GI.extent(line), GI.extent(ring)) || return out -# return _line_in_closed_curve( -# line, ring; -# close = false, -# ) -# end - -# # function ring_in_geom(::GI.LinearRingTrait, ring1, ::GI.LinearRingTrait, ring2) -# # Extents.intersects(GI.extent(ring1), GI.extent(ring2)) || return (false, false) -# # _line_in_closed_curve(ring1, ring2; close = true) -# # end - -# # function polygon_in_geom(::GI.PolygonTrait, poly, ::GI.LinearRingTrait, ring) -# # Extents.intersects(GI.extent(poly), GI.extent(ring)) || return (false, false) -# # return _line_in_closed_curve(GI.getexterior(poly), ring; close = true) -# # end - -# """ -# point_in_geom( -# ::GI.PointTrait, point, -# ::GI.PolygonTrait, poly; -# in::T = 1, on::T = -1, out::T = 0, -# )::T - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# `In` means the point is within polygon (excluding edges, vertices, and holes). -# `On` means the point is on an edge or a vertex of the polygon. -# `Out` means the point is outside of the polygon, including within holes. -# """ -# function point_in_geom( -# ::GI.PointTrait, point, -# ::GI.PolygonTrait, poly; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T} -# _point_in_extent(point, GI.extent(poly)) || return out -# ext_val = _point_in_closed_curve( -# point, GI.getexterior(poly); -# in = in, on = on, out = out, -# ) -# ext_val == on && return ext_val -# in_out_counter = (ext_val == in) ? 1 : 0 -# for ring in GI.gethole(poly) -# hole_val = _point_in_closed_curve( -# point, ring; -# in = in, on = on, out = out, -# ) -# hole_val == on && return hole_val -# in_out_counter += (hole_val == in) ? 1 : 0 -# end -# return iseven(in_out_counter) ? out : in -# end - -# """ -# point_in_polygon( -# point, polygon; -# in::T = 1, on::T = -1, out::T = 0, -# )::T - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# `In` means the point is within polygon (excluding edges, vertices, and holes). -# `On` means the point is on an edge or a vertex of the polygon. -# `Out` means the point is outside of the polygon, including within holes. -# """ -# point_in_polygon( -# point, polygon; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T} = point_in_polygon( -# GI.trait(point), point, -# GI.trait(polygon), polygon; -# in = in, on = on, out = out, -# ) - -# """ -# point_in_polygon( -# trait1::GI.PointTrait, point, -# trait2::GI.PolygonTrait, poly; -# in::T = 1, on::T = -1, out::T = 0, -# )::T - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# `In` means the point is within polygon (excluding edges, vertices, and holes). -# `On` means the point is on an edge or a vertex of the polygon. -# `Out` means the point is outside of the polygon, including within holes. - -# Note that this is the same as point_in_geom dispatched on a polygon. -# """ -# point_in_polygon( -# trait1::GI.PointTrait, point, -# trait2::GI.PolygonTrait, poly; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T}= point_in_geom( -# trait1, point, -# trait2, poly; -# in = in, on = on, out = out, -# ) - -# """ -# point_in_geom( -# ::GI.LineStringTrait, line, -# ::GI.PolygonTrait, poly; -# in::T = 1, on::T = -1, out::T = 0, -# ) - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# `In` means the line is within the polygon (no segments on edges, vertices, or -# holes). -# `On` means the line has at least one segment on a polygon edge or a vertex. -# `Out` means the line has at least one segment outside of the polygon (including -# within a hole). -# """ -# function point_in_geom( -# ::GI.LineStringTrait, line, -# ::GI.PolygonTrait, poly; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T} -# Extents.intersects(GI.extent(line), GI.extent(ring)) || return out -# ext_val = _line_in_closed_curve( -# line, GI.getexterior(poly); -# close = false, -# ) - -# for ring in GI.gethole(poly) -# hole_val = _point_in_closed_curve( -# point, ring; -# in = in, on = on, out = out, -# ) -# hole_val == on && return hole_val -# in_out_counter += (hole_val == in) ? 1 : 0 -# end -# return iseven(in_out_counter) ? out : in -# end - -# # line_in_polygon( -# # line, poly; -# # in::T = 1, on::T = -1, out::T = 0, -# # ) where {T} = line_in_geom( -# # line, GI.trait(line), -# # poly, GI.trait(poly); -# # in = in, on = on, out = out, -# # ) - -# # ring_in_geom(::GI.LinearRingTrait, ring, ::GI.PolygonTrait, poly) = -# # _geom_in_polygon(ring, poly; close = true) - -# # function polygon_in_geom(::GI.PolygonTrait, poly1, ::GI.PolygonTrait, poly2) -# # # Cheaply check that the point is inside the polygon extent -# # Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return (false, false) -# # # Make sure exterior of poly1 is within exterior of poly2 -# # in_ext, some_on_ext = _line_in_closed_curve( -# # GI.getexterior(poly1), GI.getexterior(poly2); -# # close = true, -# # ) -# # # poly1 not within poly2's external ring -# # (in_ext || some_on_ext) || return (false, false) -# # # Check if the geom is in any of the holes -# # outside_hole, some_on_hole = true, false -# # for hole in GI.gethole(poly) -# # outside_hole, some_on_hole = _line_in_closed_curve( -# # geom, hole; -# # close = close, in = false, -# # ) -# # # geom is in a hole -> not in polygon -# # !(outside_hole || some_on_hole) && return (false, false) -# # end -# # return (in_ext && outside_hole, some_on_hole || some_on_ext) # geom is inside of polygon -# # end - -# # function polygon_in_polygon(poly1, poly2) -# # # edges1, edges2 = to_edges(poly1), to_edges(poly2) -# # # extent1, extent2 = to_extent(edges1), to_extent(edges2) -# # # Check the extents intersect -# # Extents.intersects(GI.extent(poly1), GI.extent(poly2)) || return false - -# # # Check all points in poly1 are in poly2 -# # for point in GI.getpoint(poly1) -# # point_in_polygon(point, poly2) || return false -# # end - -# # # Check the line of poly1 does not intersect the line of poly2 -# # intersects(poly1, poly2) && return false - -# # # poly1 must be in poly2 -# # return true -# # end - -# """ -# _point_in_closed_curve( -# point, curve; -# in::T = 1, on::T = -1, out::T = 0, -# )::T - -# Determine if point is in, on, or out of a closed curve. Point should be an -# object of Point trait and curve should be a linearstring or ring, that is -# assumed to be closed, regardless of repeated last point. - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# `In` means the point is within the closed curve (excluding edges and vertices). -# `On` means the point is on an edge or a vertex of the closed curve. -# `Out` means the point is outside of the closed curve. - -# Note that this uses the Algorithm by Hao and Sun (2018): -# https://doi.org/10.3390/sym10100477 -# Paper seperates orientation of point and edge into 26 cases. For each case, it -# is either a case where the point is on the edge (returns on), where a ray from -# the point (x, y) to infinity along the line y = y cut through the edge (k += 1), -# or the ray does not pass through the edge (do nothing and continue). If the ray -# passes through an odd number of edges, it is within the curve, else outside of -# of the curve if it didn't return 'on'. -# See paper for more information on cases denoted in comments. -# """ -# function _point_in_closed_curve( -# point, curve; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T} -# x, y = GI.x(point), GI.y(point) -# n = GI.npoint(curve) -# n -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, n)) ? 1 : 0 -# k = 0 # counter for ray crossings -# p_start = GI.getpoint(curve, n) -# @inbounds for i in 1:n -# p_end = GI.getpoint(curve, i) -# v1 = GI.y(p_start) - y -# v2 = GI.y(p_end) - y -# if !((v1 < 0 && v2 < 0) || (v1 > 0 && v2 > 0)) # if not cases 11 or 26 -# u1 = GI.x(p_start) - x -# u2 = GI.x(p_end) - x -# f = u1 * v2 - u2 * v1 -# if v2 > 0 && v1 ≤ 0 # Case 3, 9, 16, 21, 13, or 24 -# f == 0 && return on # Case 16 or 21 -# f > 0 && (k += 1) # Case 3 or 9 -# elseif v1 > 0 && v2 ≤ 0 # Case 4, 10, 19, 20, 12, or 25 -# f == 0 && return on # Case 19 or 20 -# f < 0 && (k += 1) # Case 4 or 10 -# elseif v2 == 0 && v1 < 0 # Case 7, 14, or 17 -# f == 0 && return on # Case 17 -# elseif v1 == 0 && v2 < 0 # Case 8, 15, or 18 -# f == 0 && return on # Case 18 -# elseif v1 == 0 && v2 == 0 # Case 1, 2, 5, 6, 22, or 23 -# u2 ≤ 0 && u1 ≥ 0 && return on # Case 1 -# u1 ≤ 0 && u2 ≥ 0 && return on # Case 2 -# end -# end -# p_start = p_end -# end -# return iseven(k) ? out : in -# end - -# """ -# line_in_closed_curve( -# line, curve; -# in::T = 1, on::T = -1, out::T = 0, -# close = false, -# ) - -# Determine if line is in, on, or out of a closed curve. Both the line and curve -# should be an object of linestring or linearring trait. The curve is assumed to -# be closed, regardless of repeated last point. - -# Returns a given in, on, or out value (defaults are 1, -1, and 0) of type T. -# `In` means line is within the curve (no segments on edges, vertices, or holes). -# `On` means line has at least one segment on a curve edge or vertex. -# `Out` means the line has at least one segment outside of the curve. - -# This algorithm functions by checking if the first point of the line is within -# the curve. If not, then the line is not within the curve, if so, we check for -# intersections between the line and curve, as this would mean a part of the line -# is outside of the curve. We take special care of intersections through vertices -# as it isn't clearcut if those neccesitate a segment of the line being outside -# of the curve. -# """ -# _line_in_closed_curve(line, curve; -# exclude_boundaries = false, -# close = false, -# ) = _line_in_out_closed_curve( -# line, curve; -# disjoint = false, -# exclude_boundaries = exclude_boundaries, -# close = close, -# ) - -# # function _line_in_closed_curve( -# # line, curve; -# # in::T = 1, on::T = -1, out::T = 0, -# # close = false, -# # ) where {T} -# # # Determine number of points in curve and line -# # nc = GI.npoint(curve) -# # nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 -# # nl = GI.npoint(line) -# # nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 -# # # Check to see if first point in line is within curve -# # point_val = _point_in_closed_curve( -# # GI.getpoint(line, 1), curve; -# # in = in, on = on, out = out, -# # ) -# # # point is outside curve, line can't be within curve -# # point_val == out && return out -# # # Check for any intersections between line and curve -# # line_on_curve = point_val == on # record if line is "on" part of curve -# # l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) -# # for i in (close ? 1 : 2):nl -# # l_end = _tuple_point(GI.getpoint(line, i)) -# # c_start = _tuple_point(GI.getpoint(curve, nc)) -# # for j in 1:nc -# # c_end = _tuple_point(GI.getpoint(curve, j)) -# # # Check if edges intersect --> line is not within curve -# # meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) -# # # open line segments meet in a single point -# # meet_type == 1 && return out -# # #= -# # closed line segments meet in one or several points -> meet at a -# # vertex or on the edge itself (parallel) -# # =# -# # if meet_type == 0 -# # line_on_curve = true -# # # See if segment is parallel and within curve edge -# # p1_on_seg = point_on_segment(l_start, c_start, c_end) -# # p2_on_seg = point_on_segment(l_end, c_start, c_end) -# # # if segment isn't contained within curve edge -# # if !p1_on_seg || !p2_on_seg -# # # Make sure l_start is in or on the segment -# # p1_in_curve = -# # p1_on_seg || -# # _point_in_closed_curve( -# # l_start, curve; -# # in = in, on = on, out = out, -# # ) != out -# # !p1_in_curve && return out -# # # Make sure l_end is in or on the segment -# # p2_in_curve = -# # p2_on_seg || -# # _point_in_closed_curve( -# # l_end, curve; -# # in = in, on = on, out = out, -# # ) != out -# # !p2_in_curve && return out -# # #= -# # If both endpoints are within or on the curve, but not -# # parallel to the edge, make sure that midpoints between the -# # intersections along the segment are within curve -# # =# -# # !_segment_mids_in_curve( -# # l_start, l_end, curve; -# # in = in, on = on, out = out, -# # ) && return out # point of segment is outside of curve -# # # line segment is fully within or on curve -# # break -# # end -# # end -# # c_start = c_end -# # end -# # l_start = l_end -# # end -# # # check if line is on any curve edges or vertcies -# # return line_on_curve ? on : in -# # end - -# """ -# _segment_mids_in_curve( -# l_start, l_end, curve; -# in::T = 1, on::T = -1, out::T = 0, -# ) - -# Given two points defining a line segment (both with point traits) and a -# curve (with a linestring or linearring trait), find the intersection points -# between them and sort them along the segment. Then, make sure that the -# midpoint between pairs of points along the line is within the curve. Returns -# true if all of the midpoints are within or on the curve and false otherwise. - -# Note: This function assumes that both of the endpoints of the line segment -# are on the curve! -# """ -# function _segment_mids_in_curve( -# l_start, l_end, curve; -# in::T = 1, on::T = -1, out::T = 0, -# ) where {T} -# # Find intersection points -# ipoints = intersection_points( -# GI.Line([l_start, l_end]), -# curve -# ) -# npoints = length(ipoints) -# if npoints < 3 # only intersection points are the endpoints -# mid_val = _point_in_closed_curve( -# (l_start .+ l_end) ./ 2, curve; -# in = in, on = on, out = out, -# ) -# mid_val == out && return false -# else # more intersection points than the endpoints -# # sort intersection points along the line -# sort!(ipoints, by = p -> euclid_distance(p, l_start)) -# p_start = ipoints[1] -# for i in 2:npoints -# p_end = ipoints[i] -# # check if midpoint of intersection points is within the curve -# mid_val = _point_in_closed_curve( -# (p_start .+ p_end) ./ 2, curve; -# in = in, on = on, out = out, -# ) -# # if it is out, return false -# mid_val == out && return false -# p_start = p_end -# end -# end -# return true # all intersection point midpoints were in or on the curve -# end - -# function _geom_in_polygon(geom, poly; close = false) -# # Cheaply check that the geom extent is inside the polygon extent -# Extents.intersects(GI.extent(geom), GI.extent(poly)) || return (false, false) -# # Check if geom is inside or on the exterior ring -# in_ext, on_ext = _line_in_closed_curve( -# geom, -# GI.getexterior(poly); -# close = close, -# ) -# (in_ext || on_ext) || return (false, false) # geom isn't in external ring -# # Check if the geom is in any of the holes -# for hole in GI.gethole(poly) -# # out_of_hole, some_on_hole = _line_in_closed_curve( -# # geom, hole; -# # close = close, in = false, -# # ) -# # geom is in a hole -> not in polygon -# !(out_of_hole || some_on_hole) && return (false, false) -# end -# return (in_ext, some_on_hole) # geom is inside of polygon -# end - -# """ -# _point_in_extent(p, extent::Extents.Extent)::Bool - -# Returns true if the point is the bounding box of the extent and false otherwise. -# """ -# function _point_in_extent(p, extent::Extents.Extent) -# (x1, x2), (y1, y2) = extent.X, extent.Y -# return x1 ≤ GI.x(p) ≤ x2 && y1 ≤ GI.y(p) ≤ y2 -# end - -# function _line_in_out_closed_curve( -# line, curve; -# disjoint = false, -# exclude_boundaries = false, -# close = false, -# ) -# #= -# Set variables based off if we are determining within or disjoint. -# If `_point_in_closed_curve` returns `true_orientation` it is on the right -# side of the curve for the check. If it returns `false_orientation`, it is -# on the wrong side of the curve for the check. -# =# -# false_orientation = disjoint ? 1 : 0 # if checking within, want points in -# on = -1 # as used for point in closed curve - -# # Determine number of points in curve and line -# nc = GI.npoint(curve) -# nc -= equals(GI.getpoint(curve, 1), GI.getpoint(curve, nc)) ? 1 : 0 -# nl = GI.npoint(line) -# nl -= (close && equals(GI.getpoint(line, 1), GI.getpoint(line, nl))) ? 1 : 0 - -# # Check to see if first point in line is within curve -# point_val = _point_in_closed_curve(GI.getpoint(line, 1), curve) -# # point is out (for within) or in curve (for disjoint) -> wrong orientation -# point_val == false_orientation && return false -# # point is on boundary and don't want boundary points -> wrong orientation -# exclude_boundaries && point_val == on && return false - -# # Check for any intersections between line and curve -# l_start = _tuple_point(GI.getpoint(line, close ? nl : 1)) -# for i in (close ? 1 : 2):nl -# l_end = _tuple_point(GI.getpoint(line, i)) -# c_start = _tuple_point(GI.getpoint(curve, nc)) -# for j in 1:nc -# c_end = _tuple_point(GI.getpoint(curve, j)) -# # Check if edges intersect --> line crosses --> wrong orientation -# meet_type = ExactPredicates.meet(l_start, l_end, c_start, c_end) -# # open line segments meet in a single point -# meet_type == 1 && return false -# #= -# closed line segments meet in one or several points -> meet at a -# vertex or on the edge itself (parallel) -# =# -# if meet_type == 0 -# # See if segment is parallel and within curve edge -# p1_on_seg = point_on_segment(l_start, c_start, c_end) -# exclude_boundaries && p1_on_seg && return false -# p2_on_seg = point_on_segment(l_end, c_start, c_end) -# exclude_boundaries && p2_on_seg && return false -# # if segment isn't contained within curve edge -# if !p1_on_seg || !p2_on_seg -# # Make sure l_start is in corrent orientation -# p1_val = p1_on_seg ? -# on : -# _point_in_closed_curve(l_start, curve) -# p1_val == false_orientation && return false -# exclude_boundaries && p1_val == on && return false -# # Make sure l_end is in is in corrent orientation -# p2_val = p2_on_seg ? -# on : -# _point_in_closed_curve(l_end, curve) -# p2_val == false_orientation && return false -# exclude_boundaries && p2_val == on && return false -# #= -# If both endpoints are in the correct orientation, but not -# parallel to the edge, make sure that midpoints between the -# intersections along the segment are also in the correct -# orientation -# =# -# !_segment_mids_in_out_curve( -# l_start, l_end, curve; -# disjoint = disjoint, -# exclude_boundaries = exclude_boundaries, -# ) && return false # midpoint on the wrong side of the curve -# # line segment is fully within or on curve -# break -# end -# end -# c_start = c_end -# end -# l_start = l_end -# end -# # check if line is on any curve edges or vertcies -# return true -# end - -# function _segment_mids_in_out_curve( -# l_start, l_end, curve; -# disjoint = false, -# exclude_boundaries = false, -# ) -# false_orientation = disjoint ? 1 : 0 # if checking within, want points in -# on = -1 # as used for point in closed curve -# # Find intersection points -# ipoints = intersection_points( -# GI.Line([l_start, l_end]), -# curve -# ) -# npoints = length(ipoints) -# if npoints < 3 # only intersection points are the endpoints -# mid_val = _point_in_closed_curve( -# (l_start .+ l_end) ./ 2, curve; -# in = in, on = on, out = out, -# ) -# mid_val == false_orientation && return false -# exclude_boundaries && mid_val == on && return false -# else # more intersection points than the endpoints -# # sort intersection points along the line -# sort!(ipoints, by = p -> euclid_distance(p, l_start)) -# p_start = ipoints[1] -# for i in 2:npoints -# p_end = ipoints[i] -# # check if midpoint of intersection points is within the curve -# mid_val = _point_in_closed_curve( -# (p_start .+ p_end) ./ 2, curve; -# in = in, on = on, out = out, -# ) -# # if it is out, return false -# mid_val == false_orientation && return false -# exclude_boundaries && mid_val == on && return false -# end -# end -# return true # all intersection point midpoints were in or on the curve -# end diff --git a/test/methods/bools.jl b/test/methods/bools.jl index 26c7f396b..ef86f6c58 100644 --- a/test/methods/bools.jl +++ b/test/methods/bools.jl @@ -1,4 +1,3 @@ - # @testset "booleans" begin # line1 = GI.LineString([[9.170356, 45.477985], [9.164434, 45.482551], [9.166644, 45.484003]]) # line2 = GI.LineString([[9.169356, 45.477985], [9.163434, 45.482551], [9.165644, 45.484003]]) @@ -28,82 +27,4 @@ # l2 = GI.LineString([[0, 0], [1, 0], [1, 1], [0, 0]]) # @test isclockwise(l1) == true -# @test isclockwise(l2) == false - -# l3 = GI.LineString([[0, 0], [3, 3], [4, 4]]) -# p1 = GI.Point([1,1]) - -# l4 = GI.LineString([[0, 0], [3, 3]]) -# p2 = GI.Point([0, 0]) - -# p3 = GI.Point([20, 20]) -# l5 = GI.LineString([[0, 0], [3, 3], [38.32, 5.96]]) - -# pt = (-77, 44) -# poly = GI.Polygon([[[-81, 41], [-81, 47], [-72, 47], [-72, 41], [-81, 41]]]) - - -# poly3 = GI.Polygon([[(1, 1), (1, 10), (10, 10), (10, 1), (1, 1)]]) -# poly4 = GI.Polygon([[(1, 1), (2, 2), (3, 2), (1, 1)]]) -# line5 = GI.LineString([(1.0, 1.0), (2.0, 3.0), (2.0, 3.5)]) - -# line6 = GI.LineString([(1.0, 1.0), (1.0, 2.0), (1.0, 3.0), (1.0, 4.0)]) -# poly5 = GI.Polygon([[(1.0, 1.0), (1.0, 20.0), (1.0, 3.0), (1.0, 4.0), (1.0, 1.0)]]) -# line7 = GI.LineString([(1.0, 2.0), (1.0, 3.0), (1.0, 3.5)]) - -# # @test GO.contains(poly3, poly4) == true -# # @test GO.contains(poly3, line5) == true -# # @test GO.contains(line6, (1, 2)) == true -# # @test GO.contains(poly3, poly5) == false -# # @test GO.contains(poly3 , line7) == false - -# # @test GO.within(poly4, poly3) == true -# # @test GO.within(line5, poly3) == true -# # @test GO.within(poly5, poly3) == false -# # @test GO.within((1, 2), line6) == true -# # @test GO.within(line7, poly3) == false - -# poly6 = GI.Polygon([[(-11, -12), (-13, -12), (-13, -13), (-11, -13), (-11, -12)]]) -# poly7 = GI.Polygon([[(-1, 2), (3, 2), (3, 3), (-1, 3), (-1, 2)]]) -# poly8 = GI.Polygon([[(-1, 2), (-13, -12), (-13, -13), (-11, -13), (-1, 2)]]) - -# # @test GO.disjoint(poly7, poly6) == true -# # @test GO.disjoint(poly7, (1, 1)) == true -# # @test GO.disjoint(poly7, GI.LineString([(0, 0), (12, 2), (12, 3), (12, 4)])) == true -# # @test GO.disjoint(poly8, poly7) == false - -# line8 = GI.LineString([(124.584961, -12.768946), (126.738281, -17.224758)]) -# line9 = GI.LineString([(123.354492, -15.961329), (127.22168, -14.008696)]) - -# @test all(GO.intersection(line8, line9)[1] .≈ (125.583754, -14.835723)) - -# line10 = GI.LineString([ -# (142.03125, -11.695273), -# (138.691406, -16.804541), -# (136.40625, -14.604847), -# (135.966797, -12.039321), -# (131.308594, -11.436955), -# (128.232422, -15.36895), -# (125.947266, -13.581921), -# (121.816406, -18.729502), -# (117.421875, -20.632784), -# (113.378906, -23.402765), -# (114.169922, -26.667096), -# ]) -# line11 = GI.LineString([ -# (117.861328, -15.029686), -# (122.124023, -24.886436), -# (132.583008, -22.309426), -# (132.890625, -7.754537), -# ]) - -# points = GO.intersection(line10, line11) -# @test all(points[1] .≈ (119.832884, -19.58857)) -# @test all(points[2] .≈ (132.808697, -11.6309378)) - -# # @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, 2), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == true -# # @test GO.crosses(GI.MultiPoint([(1, 0), (12, 12)]), GI.LineString([(1, 1), (1, 2), (1, 3), (1, 4)])) == false -# # @test GO.crosses(GI.LineString([(-2, 2), (-4, 2)]), poly7) == false -# end +# @test isclockwise(l2) == false \ No newline at end of file diff --git a/test/methods/geom_relations/equals.jl b/test/methods/equals.jl similarity index 100% rename from test/methods/geom_relations/equals.jl rename to test/methods/equals.jl diff --git a/test/methods/geom_in_geom.jl b/test/methods/geom_in_geom.jl deleted file mode 100644 index 8300b734a..000000000 --- a/test/methods/geom_in_geom.jl +++ /dev/null @@ -1,174 +0,0 @@ -const in_geom = 1#(true, false) -const on_geom = -1#(false, true) -const not_in_on_geom = 0#(false, false) - -warn_msg = "Linestring isn't closed. Point cannot be 'in' linestring." -open_string = LG.LineString([ - [0.0, 0.0], [1.0, 1.0], [3.0, 1.25], [2.0, 3.0], [-1.0, 2.75] -]) -closed_string = LG.LineString([ - [0.0, 0.0], [1.0, 1.0], [3.0, 1.25], [2.0, 3.0], [-1.0, 2.75], [0.0, 0.0] -]) -rect_ring = LG.LinearRing([ - [-20.0, 0.0], [-20.0, -10.0], [-5.0, -10.0], [-5.0, 0.0], [-20.0, 0.0] -]) -tri_ring = LG.LinearRing([ - [5.0, 0.0], [7.0, 1.995], [9.0, -1.0], [5.0, 0.0] -]) -concave_out_spikes = LG.LinearRing([ - [0.0, 0.0], [0.0, 10.0], [20.0, 10.0], [20.0, 0.0], [15.0, -5.0], - [10.0, 0.0], [5.0, -5.0], [0.0, 0.0], -]) -concave_in_spikes = LG.LinearRing([ - [0.0, 0.0], [0.0, 10.0], [20.0, 10.0], [20.0, 0.0], [15.0, 5.0], - [10.0, 0.0], [5.0, 5.0], [0.0, 0.0], -]) -rect_poly = LG.Polygon([[ - [0.0, 0.0], [0.0, 10.0], [10.0, 10.0], [10.0, 0.0], [0.0, 0.0] -]]) -diamond_poly = LG.Polygon([[ - [0.0, 0.0], [-5.0, 5.0], [0.0, 10.0], [5.0, 5.0], [0.0, 0.0], -]]) -trap_with_hole = LG.Polygon([ - [[-10.0, 0.0], [-8.0, 5.0], [8.0, 5.0], [10.0, 0.0], [-10.0, 0.0]], - [[-5.0, 2.0], [-5.0, 4.0], [-2.0, 4.0], [-2.0, 2.0], [-5.0, 2.0]] -]) -concave_a = GI.Polygon([[ - (1.2938349167338743, -3.175128530227131), - (-2.073885870841754, -1.6247711001754137), - (-5.787437985975053, 0.06570713422599561), - (-2.1308128111898093, 5.426689675486368), - (2.3058074184797244, 6.926652158268195), - (1.2938349167338743, -3.175128530227131), -]]) -concave_b = GI.Polygon([[ - (-2.1902469793743924, -1.9576242117579579), - (-4.726006206053999, 1.3907098941556428), - (-3.165301985923147, 2.847612825874245), - (-2.5529280962099428, 4.395492123980911), - (0.5677700216973937, 6.344638314896882), - (3.982554842356183, 4.853519613487035), - (5.251193948893394, 0.9343031382106848), - (5.53045582244555, -3.0101433691361734), - (-2.1902469793743924, -1.9576242117579579), -]]) - - -@testset "Point in Geom" begin - # Line Strings - @test (@test_logs (:warn, warn_msg) GO.point_in_geom((-12.0, -0.5), open_string)) == not_in_on_geom - - @test GO.point_in_geom((0.5, 0.5), closed_string) == on_geom - @test GO.point_in_geom((2.0, 1.25), closed_string) == in_geom - @test GO.point_in_geom((4.0, 0.0), closed_string) == not_in_on_geom - - # Linear Rings - @test GO.point_in_geom((-12.0, -0.5), rect_ring) == in_geom - @test GO.point_in_geom((20.0, 0.0), rect_ring) == not_in_on_geom - @test GO.point_in_geom((-5.0, -10.0), rect_ring) == on_geom - - @test GO.point_in_geom((6.0, 0.0), tri_ring) == in_geom - @test GO.point_in_geom((6.0, -0.25), tri_ring) == on_geom - @test GO.point_in_geom((7.0, 2.0), tri_ring) == not_in_on_geom - - # Convex polygons - @test GO.point_in_polygon((0.0, 0.0), rect_poly) == on_geom - @test GO.point_in_polygon((0.0, 5.0), rect_poly) == on_geom - @test GO.point_in_polygon((5.0, 10.0), rect_poly) == on_geom - @test GO.point_in_polygon((2.5, 2.5), rect_poly) == in_geom - @test GO.point_in_polygon((9.99, 9.99), rect_poly) == in_geom - @test GO.point_in_polygon((20.0, 20.0), rect_poly) == not_in_on_geom - - @test GO.point_in_polygon((0.0, 0.0), diamond_poly) == on_geom - @test GO.point_in_polygon((-2.5, 2.5), diamond_poly) == on_geom - @test GO.point_in_polygon((2.5, 2.5), diamond_poly) == on_geom - @test GO.point_in_polygon((0.0, 5.0), diamond_poly) == in_geom - @test GO.point_in_polygon((4.99, 5.0), diamond_poly) == in_geom - @test GO.point_in_polygon((20.0, 20.0), diamond_poly) == not_in_on_geom - - @test GO.point_in_polygon((-10.0, 0.0), trap_with_hole) == on_geom - @test GO.point_in_polygon((-5.0, 2.0), trap_with_hole) == on_geom - @test GO.point_in_polygon((-5.0, 3.0), trap_with_hole) == on_geom - @test GO.point_in_polygon((-9.0, 0.01), trap_with_hole) == in_geom - @test GO.point_in_polygon((-4.0, 3.0), trap_with_hole) == not_in_on_geom - @test GO.point_in_polygon((20.0, 20.0), trap_with_hole) == not_in_on_geom - - # Concave polygons - pt = (-2.1902469793743924, -1.9576242117579579) - @test GO.point_in_polygon(pt, concave_a) == not_in_on_geom - @test GO.point_in_polygon(pt, concave_b) == on_geom - @test GO.point_in_polygon((0.0, 0.0), concave_a) == in_geom - @test GO.point_in_polygon((0.0, 0.0), concave_b) == in_geom -end - -@testset "Line in Geom" begin - # Line Strings - @test (@test_logs (:warn, warn_msg) GO.line_in_geom( - LG.LineString([[0.0, 0.0], [0.0, 1.0]]), - open_string, - )) == not_in_on_geom - - # On the edge - @test GO.line_in_geom( - LG.LineString([[0.25, 0.25], [0.5, 0.5]]), - closed_string - ) == on_geom - # Inside - @test GO.line_in_geom( - LG.LineString([[1.0, 1.25], [2, 1.3], [2.9, 1.25]]), - closed_string - ) == in_geom - # Inside to outside - @test GO.line_in_geom( - LG.LineString([[1.0, 0.99], [2, 1.3], [2.9, 1.25]]), - closed_string - ) == not_in_on_geom - # Outside of geom - @test GO.line_in_geom( - LG.LineString([[0.0, 0.0], [3.0, 1.25]]), - closed_string - ) == not_in_on_geom - - # Rings - # Same geometry, sharing all edges - @test GO.line_in_geom( - LG.LineString([[-20.0, 0.0], [-20.0, -10.0], [-5.0, -10.0], [-5.0, 0.0], [-20.0, 0.0]]), - rect_ring - ) == on_geom - - # Same geometry, sharing all but last edge, which is inside - @test GO.line_in_geom( - LG.LineString([[-20.0, 0.0], [-20.0, -10.0], [-5.0, -10.0], [-5.0, 0.0], [-15.0, -5.0]]), - rect_ring - ) == on_geom - # Within - @test GO.line_in_geom( - LG.LineString([[-10.0, -1.0], [-10.0, -9.0], [-7.0, -9.0], [-7.0, -1.0], [-10.0, -1.0]]), - rect_ring - ) == in_geom - # Passing in to out - @test GO.line_in_geom( - LG.LineString([[-20.0, 0.0], [-20.0, -10.0], [-5.0, -10.0], [-5.0, 0.0], [-30.0, 0.0]]), - rect_ring - ) == not_in_on_geom - - horizontal_line = LG.LineString([[0.0, 0.0], [20.0, 0.0]]) - @test GO.line_in_geom(horizontal_line, concave_out_spikes) == on_geom - @test GO.line_in_geom(horizontal_line, concave_in_spikes) == not_in_on_geom - @test GO.line_in_geom( - LG.LineString([[10.0, 0.0], [10.0, -0.0001]]), - concave_out_spikes - ) == not_in_on_geom - - # Polygons - - -end - -@testset "Ring in Geom" begin - -end - -@testset "Polygon in Geom" begin - -end \ No newline at end of file diff --git a/test/methods/geom_on_geom.jl b/test/methods/geom_on_geom.jl deleted file mode 100644 index a95bbab26..000000000 --- a/test/methods/geom_on_geom.jl +++ /dev/null @@ -1,17 +0,0 @@ -@testset "Point on segment" begin - - -end - -# l3 = GI.LineString([[0, 0], [3, 3], [4, 4]]) -# p1 = GI.Point([1,1]) - -# l4 = GI.LineString([[0, 0], [3, 3]]) -# p2 = GI.Point([0, 0]) - -# p3 = GI.Point([20, 20]) -# l5 = GI.LineString([[0, 0], [3, 3], [38.32, 5.96]]) - -# @test GO.point_on_line(p2, l4; ignore_end_vertices=true) == false -# @test GO.point_on_line(p3, l5; ignore_end_vertices=true) == false -# @test GO.point_on_line(p1, l3) == true \ No newline at end of file diff --git a/test/methods/geom_relations.jl b/test/methods/geom_relations.jl index 65c8ec82e..9ff1f003f 100644 --- a/test/methods/geom_relations.jl +++ b/test/methods/geom_relations.jl @@ -170,7 +170,6 @@ end @testset "Covers" begin test_geom_relation(GO.covers, LG.covers, "covers"; swap_points = true) end @testset "Crosses" begin test_geom_relation(GO.crosses, LG.crosses, "crosses") end @testset "Disjoint" begin test_geom_relation(GO.disjoint, LG.disjoint, "disjoint")end -@testset "Equals" begin include("geom_relations/equals.jl") 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 diff --git a/test/methods/geom_relations/contains.jl b/test/methods/geom_relations/contains.jl deleted file mode 100644 index 9ae97e0a0..000000000 --- a/test/methods/geom_relations/contains.jl +++ /dev/null @@ -1,190 +0,0 @@ -# # Point and Geometry - -# Same point -> contains -@test GO.contains(pt1, pt1) == LG.contains(pt1, pt1) == true -# Different point -> doesn't contain -@test GO.contains(pt1, pt2) == LG.contains(pt1, pt2) == false - -# Point on line endpoint -> does not contain -@test GO.contains(l1, pt1) == LG.contains(l1, pt1) == false -# Point outside line -> does not contain -@test GO.contains(l1, pt2) == LG.contains(l1, pt2) == false -# Point on line segment -> contains -@test GO.contains(l1, pt3) == LG.contains(l1, pt3) == true -# Point on line vertex between segments -> contain -@test GO.contains(l1, pt4) == LG.contains(l1, pt4) == true -# Point cannot contain a line -> doesn't contain -@test GO.contains(pt3, l1) == LG.contains(pt3, l1) == false - -# Point on ring endpoint -> contains -@test GO.contains(r1, pt1) == LG.contains(r1, pt1) == true -# Point outside ring -> does not contain -@test GO.contains(r1, pt2) == LG.contains(r1, pt2) == false -# Point on ring segment -> contains -@test GO.contains(r1, pt3) == LG.contains(r1, pt3) == true -# Point on ring vertex between segments -> contain -@test GO.contains(r1, pt4) == LG.contains(r1, pt4) == true -# Point cannot contain a ring -> doesn't contain -@test GO.contains(pt3, r1) == LG.contains(pt3, r1) == false - -# Point on vertex of polygon --> doesn't contain -@test GO.contains(p1, pt1) == LG.contains(p1, pt1) == false -# Point outside of polygon's external ring -> doesn't contain -@test GO.contains(p1, pt2) == LG.contains(p1, pt2) == false -# Point on polygon's edge -> doesn't contain -@test GO.contains(p1, pt4) == LG.contains(p1, pt4) == false -# Point inside of polygon -> contains -@test GO.contains(p1, pt5) == LG.contains(p1, pt5) == true -# Point on hole edge -> doesn't contain -@test GO.contains(p1, pt6) == LG.contains(p1, pt6) == false -# Point inside of polygon hole -> doesn't contain -@test GO.contains(p1, pt7) == LG.contains(p1, pt7) == false -# Point cannot contain a polygon -> doesn't contain -@test GO.contains(pt5, p1) == LG.contains(pt5, p1) == false - -# # Line and Geometry - -# Same line -> contains -@test GO.contains(l1, l1) == LG.contains(l1, l1) == true -# Line overlaps line edge and endpoint -> contains -@test GO.contains(l1, l2) == LG.contains(l1, l2) == true -# Line overlaps with one edge and is outside of other edge -> doesn't contain -@test GO.contains(l1, l3) == LG.contains(l1, l3) == false -# Line segments both within other line segments -> contain -@test GO.contains(l1, l4) == LG.contains(l1, l4) == true -# Line segments connect at endpoint -> doesn't contain -@test GO.contains(l1, l5) == LG.contains(l1, l5) == false -# Line segments don't touch -> doesn't contain -@test GO.contains(l1, l6) == LG.contains(l1, l6) == false -# Line segments cross -> doesn't contain -@test GO.contains(l1, l7) == LG.contains(l1, l7) == false -# Line segments cross and go over and out -> doesn't contain -@test GO.contains(l1, l8) == LG.contains(l1, l8) == false -# Line segments cross and overlap on endpoint -> doesn't contain -@test GO.contains(l1, l9) == LG.contains(l1, l9) == false - -# Line is within linear ring -> doesn't contain -@test GO.contains(l1, r1) == LG.contains(l1, r1) == false -# Line covers one edge of linera ring and has segment outside -> doesn't contain -@test GO.contains(l3, r1) == LG.contains(l3, r1) == false -# Line and linear ring are only connected at vertex -> doesn't contain -@test GO.contains(l5, r1) == LG.contains(l5, r1) == false -# Line and linear ring are disjoint -> doesn't contain -@test GO.contains(l6, r1) == LG.contains(l6, r1) == false -# Line crosses through two ring edges -> doesn't contain -@test GO.contains(l7, r1) == LG.contains(l7, r1) == false -# Line crosses through two ring edges and touches third edge -> doesn't contain -@test GO.contains(l8, r1) == LG.contains(l8, r1) == false -# Line is equal to linear ring -> contain -@test GO.contains(l10, r1) == LG.contains(l10, r1) == true -# Line covers linear ring and then has extra segment -> contain -@test GO.contains(l11, r1) == LG.contains(l11, r1) == true - -# Line on polygon edge -> doesn't contain -@test GO.contains(p1, l1) == LG.contains(p1, l1) == false -# Line on polygon edge and extending beyond polygon edge -> doesn't contain -@test GO.contains(p1, l3) == LG.contains(p1, l3) == false -# Line outside polygon connected by an vertex -> doesn't contain -@test GO.contains(p1, l5) == LG.contains(p1, l5) == false -# Line through polygon cutting to outside -> doesn't contain -@test GO.contains(p1, l7) == LG.contains(p1, l7) == false -# Line inside of polygon -> contains -@test GO.contains(p1, l12) == LG.contains(p1, l12) == true -# Line outside of polygon -> doesn't contain -@test GO.contains(p1, l13) == LG.contains(p1, l13) == false -# Line in polygon hole -> doesn't contain -@test GO.contains(p1, l14) == LG.contains(p1, l14) == false -# Line outside crown polygon but touching edges -> doesn't contains -@test GO.contains(p8, l15) == LG.contains(p8, l15) == false -# Line within crown polygon but touching edges -> contains -@test GO.contains(p9, l15) == LG.contains(p9, l15) == true - -# # Ring and Geometry - -# Line is within linear ring -> contains -@test GO.contains(r1, l1) == LG.contains(r1, l1) == true -# Line covers one edge of linera ring and has segment outside -> doesn't contain -@test GO.contains(r1, l3) == LG.contains(r1, l3) == false -# Line and linear ring are only connected at vertex -> doesn't contain -@test GO.contains(r1, l5) == LG.contains(r1, l5) == false -# Line and linear ring are disjoint -> doesn't contain -@test GO.contains(r1, l6) == LG.contains(r1, l6) == false -# Line crosses through two ring edges -> doesn't contain -@test GO.contains(r1, l7) == LG.contains(r1, l7) == false -# Line crosses through two ring edges and touches third edge -> doesn't contain -@test GO.contains(r1, l8) == LG.contains(r1, l8) == false -# Line is equal to linear ring -> contain -@test GO.contains(r1, l10) == LG.contains(r1, l10) == true -# Line covers linear ring and then has extra segment -> doesn't contain -@test GO.contains(r1, l11) == LG.contains(r1, l11) == false - -# Same ring -> contains -@test GO.contains(r1, r1) == LG.contains(r1, r1) == true -# Disjoint ring with one "inside" of hole created => doesn't contain -@test GO.contains(r1, r2) == LG.contains(r1, r2) == false -# Disjoint ring with one "outside" of hole created => doesn't contain -@test GO.contains(r1, r3) == LG.contains(r1, r3) == false -# Rings share two sides and rest of sides don't touch -> doesn't contain -@test GO.contains(r1, r4) == LG.contains(r1, r4) == false -# Ring shares all edges with other ring, plus an extra loop -> contains -@test GO.contains(r5, r1) == LG.contains(r5, r1) == true -# Rings share just one vertex -> doesn't contain -@test GO.contains(r6, r1) == LG.contains(r6, r1) == false -# Rings cross over one another -> doesn't contain -@test GO.contains(r7, r1) == LG.contains(r7, r1) == false - -# Ring on bounday of polygon -> doesn't contain -@test GO.contains(p1, r4) == LG.contains(p1, r4) == false -# Ring on boundary and cutting through polygon -> contains -@test GO.contains(p1, r1) == LG.contains(p1, r1) == true -# Ring on hole boundary -> doesn't contain -@test GO.contains(p1, r2) == LG.contains(p1, r2) == false -# Ring touches polygon at one vertex -> doesn't contain -@test GO.contains(p1, r6) == LG.contains(p1, r6) == false -# Ring crosses through polygon -> doesn't contain -@test GO.contains(p1, r7) == LG.contains(p1, r7) == false -# Ring inside polygon -> contains -@test GO.contains(p1, r8) == LG.contains(p1, r8) == true -# Ring outside -> doesn't contain -@test GO.contains(p1, r9) == LG.contains(p1, r9) == false -# Ring inside polygon and shares holes edge -> contains -@test GO.contains(p1, r10) == LG.contains(p1, r10) == true -# Ring inside of polygon hole -> doesn't contain -@test GO.contains(p1, r11) == LG.contains(p1, r11) == false - -# # Polygon and Geometry - -# Polygon with holes in polygon without holes -> contains -@test GO.contains(p2, p1) == LG.contains(p2, p1) == true -# Polygon without holes in polygon with holes -> doesn't contain -@test GO.contains(p1, p2) == LG.contains(p1, p2) == false -# Polygon is the same as other poylgon hole -> doesn't contain -@test GO.contains(p1, p3) == LG.contains(p1, p3) == false -# Polygon touches other polygon by vertex -> doesn't contain -@test GO.contains(p1, p4) == LG.contains(p1, p4) == false -# Polygon outside of other polygon -> doesn't contain -@test GO.contains(p1, p5) == LG.contains(p1, p5) == false -# Polygon inside of hole -> doesn't contain -@test GO.contains(p1, p6) == LG.contains(p1, p6) == false -# Polygon overlaps other polygon -> doesn't contain -@test GO.contains(p1, p7) == LG.contains(p1, p7) == false -# Polygon with hole inside polygon with hole (holes nested) -> contains -@test GO.contains(p1, p10) == LG.contains(p1, p10) == true - -# # Multi-geometries and collections - -mp1 = LG.MultiPolygon([p1, p3, p11]) -mp2 = LG.MultiPolygon([p2, p5]) -c1 = LG.GeometryCollection([r1, r8, pt5]) -# Multipolygon plugs all holes -> doesn't contain -@test GO.contains(mp1, p2) == LG.contains(mp1, p2) == false -# Polygon is one of the multipolygons -> contains -@test GO.contains(mp2, p2) == LG.contains(mp2, p2) == true -# Polygon touches one of the multipolygons -> doesn't contain -@test GO.contains(mp2, p4) == LG.contains(mp2, p4) == false -# Polygon contains all multipolygon elements -@test GO.contains(p9, mp1) == LG.contains(p9, mp1) == true -# Polygon contains all collection elements -@test GO.contains(p1, c1) == LG.contains(p1, c1) == true -# Collection doesn't contain all multipolygon elements -@test GO.contains(c1, mp1) == LG.contains(c1, mp1) == false \ No newline at end of file diff --git a/test/methods/geom_relations/coveredby.jl b/test/methods/geom_relations/coveredby.jl deleted file mode 100644 index a4ab8fe09..000000000 --- a/test/methods/geom_relations/coveredby.jl +++ /dev/null @@ -1,183 +0,0 @@ -# # Point and Geometry - -# Same point -> covered by -@test GO.coveredby(pt1, pt1) == LG.coveredby(pt1, pt1) == true -# Different point -> not covered by -@test GO.coveredby(pt1, pt2) == LG.coveredby(pt1, pt2) == false - -# Point on line endpoint -> covered by -@test GO.coveredby(pt1, l1) == LG.coveredby(pt1, l1) == true -# Point outside line -> not covered by -@test GO.coveredby(pt2, l1) == LG.coveredby(pt2, l1) == false -# Point on line segment -> covered by -@test GO.coveredby(pt3, l1) == LG.coveredby(pt3, l1) == true -# Point on line vertex between segments -> covered by -@test GO.coveredby(pt4, l1) == LG.coveredby(pt4, l1) == true -# line cannot be covered by a point -> not covered by -@test GO.coveredby(l1, pt3) == LG.coveredby(l1, pt3) == false - -# Point on ring endpoint -> covered by -@test GO.coveredby(pt1, r1) == LG.coveredby(pt1, r1) == true -# Point outside ring -> isn't covered by -@test GO.coveredby(pt2, r1) == LG.coveredby(pt2, r1) == false -# Point on ring segment -> covered by -@test GO.coveredby(pt3, r1) == LG.coveredby(pt3, r1) == true -# Point on ring vertex between segments -> covered by -@test GO.coveredby(pt4, r1) == LG.coveredby(pt4, r1) == true -# Ring cannot be covered by a point -> isn't covered by -@test GO.coveredby(r1, pt3) == LG.coveredby(r1, pt3) == false - -# Point on vertex of polygon --> covered -@test GO.coveredby(pt1, p1) == LG.coveredby(pt1, p1) == true -# Point outside of polygon's external ring -> not covered by -@test GO.coveredby(pt2, p1) == LG.coveredby(pt2, p1) == false -# Point on polygon's edge -> covered by -@test GO.coveredby(pt4, p1) == LG.coveredby(pt4, p1) == true -# Point inside of polygon -> covered by -@test GO.coveredby(pt5, p1) == LG.coveredby(pt5, p1) == true -# Point on hole edge -> covered by -@test GO.coveredby(pt6, p1) == LG.coveredby(pt6, p1) == true -# Point inside of polygon hole -> not covered by -@test GO.coveredby(pt7, p1) == LG.coveredby(pt7, p1) == false -# Polygon can't be covered by a polygon -> not covered by -@test GO.coveredby(p1, pt5) == LG.coveredby(p1, pt5) == false - -# # Line and Geometry - -# Same line -> covered by -@test GO.coveredby(l1, l1) == LG.coveredby(l1, l1) == true -# Line overlaps line edge and endpoint -> covered by -@test GO.coveredby(l2, l1) == LG.coveredby(l2, l1) == true -# Line overlaps with one edge and is outside of other edge -> isn't covered by -@test GO.coveredby(l3, l1) == LG.coveredby(l3, l1) == false -# Line segments both within other line segments -> covered by -@test GO.coveredby(l4, l1) == LG.coveredby(l4, l1) == true -# Line segments connect at endpoint -> isn't covered by -@test GO.coveredby(l5, l1) == LG.coveredby(l5, l1) == false -# Line segments don't touch -> isn't covered by -@test GO.coveredby(l6, l1) == LG.coveredby(l6, l1) == false -# Line segments cross -> isn't covered by -@test GO.coveredby(l7, l1) == LG.coveredby(l7, l1) == false -# Line segments cross and go over and out -> isn't covered by -@test GO.coveredby(l8, l1) == LG.coveredby(l8, l1) == false -# Line segments cross and overlap on endpoint -> isn't covered by -@test GO.coveredby(l9, l1) == LG.coveredby(l9, l1) == false - -# Line is within linear ring -> covered by -@test GO.coveredby(l1, r1) == LG.coveredby(l1, r1) == true -# Line covers one edge of linera ring and has segment outside -> isn't covered by -@test GO.coveredby(l3, r1) == LG.coveredby(l3, r1) == false -# Line and linear ring are only connected at vertex -> isn't covered by -@test GO.coveredby(l5, r1) == LG.coveredby(l5, r1) == false -# Line and linear ring are disjoint -> isn't covered by -@test GO.coveredby(l6, r1) == LG.coveredby(l6, r1) == false -# Line crosses through two ring edges -> isn't covered by -@test GO.coveredby(l7, r1) == LG.coveredby(l7, r1) == false -# Line crosses through two ring edges and touches third edge -> isn't covered by -@test GO.coveredby(l8, r1) == LG.coveredby(l8, r1) == false -# Line is equal to linear ring -> covered by -@test GO.coveredby(l10, r1) == LG.coveredby(l10, r1) == true -# Line covers linear ring and then has extra segment -> isn't covered by -@test GO.coveredby(l11, r1) == LG.coveredby(l11, r1) == false - -# Line on polygon edge -> coveredby -@test GO.coveredby(l1, p1) == LG.coveredby(l1, p1) == true -# Line on polygon edge and extending beyond polygon edge -> not coveredby -@test GO.coveredby(l3, p1) == LG.coveredby(l3, p1) == false -# Line outside polygon connected by an vertex -> not coveredby -@test GO.coveredby(l5, p1) == LG.coveredby(l5, p1) == false -# Line through polygon cutting to outside -> not coveredby -@test GO.coveredby(l7, p1) == LG.coveredby(l7, p1) == false -# Line inside of polygon -> coveredby -@test GO.coveredby(l12, p1) == LG.coveredby(l12, p1) == true -# Line outside of polygon -> not coveredby -@test GO.coveredby(l13, p1) == LG.coveredby(l13, p1) == false -# Line in polygon hole -> not coveredby -@test GO.coveredby(l14, p1) == LG.coveredby(l14, p1) == false -# Line outside crown polygon but touching edges -> not coveredby -@test GO.coveredby(l15, p8) == LG.coveredby(l15, p8) == false -# Line within crown polygon but touching edges -> not coveredby -@test GO.coveredby(l15, p9) == LG.coveredby(l15, p9) == true - -# # Ring and Geometry - -# Line is within linear ring -> not coveredby -@test GO.coveredby(r1, l1) == LG.coveredby(r1, l1) == false -# Line covers one edge of linera ring and has segment outside -> not coveredby -@test GO.coveredby(r1, l3) == LG.coveredby(r1, l3) == false -# Line and linear ring are only connected at vertex -> not coveredby -@test GO.coveredby(r1, l5) == LG.coveredby(r1, l5) == false -# Line and linear ring are disjoint -> not coveredby -@test GO.coveredby(r1, l6) == LG.coveredby(r1, l6) == false -# Line crosses through two ring edges -> not coveredby -@test GO.coveredby(r1, l7) == LG.coveredby(r1, l7) == false -# Line crosses through two ring edges and touches third edge -> not coveredby -@test GO.coveredby(r1, l8) == LG.coveredby(r1, l8) == false -# Line is equal to linear ring -> coveredby -@test GO.coveredby(r1, l10) == LG.coveredby(r1, l10) == true -# Line covers linear ring and then has extra segment -> coveredby -@test GO.coveredby(r1, l11) == LG.coveredby(r1, l11) == true - -# Same ring -> coveredby -@test GO.coveredby(r1, r1) == LG.coveredby(r1, r1) == true -# Disjoint ring with one "inside" of hole created => not coveredby -@test GO.coveredby(r2, r1) == LG.coveredby(r2, r1) == false -# Disjoint ring with one "outside" of hole created => not coveredby -@test GO.coveredby(r3, r1) == LG.coveredby(r3, r1) == false -# Rings share two sides and rest of sides don't touch -> not coveredby -@test GO.coveredby(r4, r1) == LG.coveredby(r4, r1) == false -# Ring shares all edges with other ring, plus an extra loop -> coveredby -@test GO.coveredby(r1, r5) == LG.coveredby(r1, r5) == true -# Rings share just one vertex -> not coveredby -@test GO.coveredby(r1, r6) == LG.coveredby(r1, r6) == false -# Rings cross over one another -> not coveredby -@test GO.coveredby(r1, r7) == LG.coveredby(r1, r7) == false - -# Ring on bounday of polygon -> coveredby -@test GO.coveredby(r4, p1) == LG.coveredby(r4, p1) == true -# Ring on boundary and cutting through polygon -> coveredby -@test GO.coveredby(r1, p1) == LG.coveredby(r1, p1) == true -# Ring on hole boundary -> coveredby -@test GO.coveredby(r2, p1) == LG.coveredby(r2, p1) == true -# Ring touches polygon at one vertex -> not coveredby -@test GO.coveredby(r6, p1) == LG.coveredby(r6, p1) == false -# Ring crosses through polygon -> not coveredby -@test GO.coveredby(r7, p1) == LG.coveredby(r7, p1) == false -# Ring inside polygon -> coveredby -@test GO.coveredby(r8, p1) == LG.coveredby(r8, p1) == true -# Ring outside -> not coveredby -@test GO.coveredby(r9, p1) == LG.coveredby(r9, p1) == false -# Ring inside polygon and shares holes edge -> coveredby -@test GO.coveredby(r10, p1) == LG.coveredby(r10, p1) == true -# Ring inside of polygon hole -> not coveredby -@test GO.coveredby(r11, p1) == LG.coveredby(r11, p1) == false - -# # Polygon and Geometry - -# Polygon with holes in polygon without holes -> coveredby -@test GO.coveredby(p1, p2) == LG.coveredby(p1, p2) == true -# Polygon without holes in polygon with holes -> not coveredby -@test GO.coveredby(p2, p1) == LG.coveredby(p2, p1) == false -# Polygon is the same as other poylgon hole -> not coveredby -@test GO.coveredby(p3, p1) == LG.coveredby(p3, p1) == false -# Polygon touches other polygon by vertex -> not coveredby -@test GO.coveredby(p4, p1) == LG.coveredby(p4, p1) == false -# Polygon outside of other polygon -> not coveredby -@test GO.coveredby(p5, p1) == LG.coveredby(p5, p1) == false -# Polygon inside of hole -> not coveredby -@test GO.coveredby(p6, p1) == LG.coveredby(p6, p1) == false -# Polygon overlaps other polygon -> not coveredby -@test GO.coveredby(p7, p1) == LG.coveredby(p7, p1) == false -# Polygon with hole inside polygon with hole (holes nested) -> coveredby -@test GO.coveredby(p10, p1) == LG.coveredby(p10, p1) == true - -# # Multi-geometries and collections -mpt1 = LG.MultiPoint([pt1, pt4, pt5]) -ml1 = LG.MultiLineString([l1, l2, l3]) -c1 = LG.GeometryCollection([l1, pt1, ]) -# Three points all in polygon -@test GO.coveredby(mpt1, p1) == LG.coveredby(mpt1, p1) == true -# Polygon can't be covered by multipoints -@test GO.coveredby(p1, mpt1) == LG.coveredby(p1, mpt1) -# Three lines not all covered by line -@test GO.coveredby(ml1, l1) == LG.coveredby(ml1, l1) == false diff --git a/test/methods/geom_relations/covers.jl b/test/methods/geom_relations/covers.jl deleted file mode 100644 index a31da7265..000000000 --- a/test/methods/geom_relations/covers.jl +++ /dev/null @@ -1,128 +0,0 @@ -# # Point and Geometry - -# Same point -> covers -@test GO.covers(pt1, pt1) == LG.covers(pt1, pt1) == true -# Different point -> doesn't cover -@test GO.covers(pt1, pt2) == LG.covers(pt1, pt2) == false -# Point on line endpoint -> covers -@test GO.covers(l1, pt1) == LG.covers(l1, pt1) == true -# Point outside line -> does not cover -@test GO.covers(l1, pt2) == LG.covers(l1, pt2) == false -# Point on line segment -> covers -@test GO.covers(l1, pt3) == LG.covers(l1, pt3) == true -# Point on line vertex between segments -> cover -@test GO.covers(l1, pt4) == LG.covers(l1, pt4) == true -# Point cannot cover a line -> doesn't cover -@test GO.covers(pt3, l1) == LG.covers(pt3, l1) == false -# Point on ring endpoint -> covers -@test GO.covers(r1, pt1) == LG.covers(r1, pt1) == true -# Point outside ring -> doesn't cover -@test GO.covers(r1, pt2) == LG.covers(r1, pt2) == false -# Point on ring segment -> covers -@test GO.covers(r1, pt3) == LG.covers(r1, pt3) == true -# Point on ring vertex between segments -> covers -@test GO.covers(r1, pt4) == LG.covers(r1, pt4) == true -# Point cannot cover a ring -> doesn't cover -@test GO.covers(pt3, r1) == LG.covers(pt3, r1) == false -# Point on vertex of polygon --> covers -@test GO.covers(p1, pt1) == LG.covers(p1, pt1) == true -# Point outside of polygon's external ring -> not covered -@test GO.covers(p1, pt2) == LG.covers(p1, pt2) == false -# Point on polygon's edge -> covers -@test GO.covers(p1, pt4) == LG.covers(p1, pt4) == true -# Point inside of polygon -> covers -@test GO.covers(p1, pt5) == LG.covers(p1, pt5) == true -# Point on hole edge -> covers -@test GO.covers(p1, pt6) == LG.covers(p1, pt6) == true -# Point inside of polygon hole -> not covered -@test GO.covers(p1, pt7) == LG.covers(p1, pt7) == false -# Point can't cover a polygon -> not covered -@test GO.covers(pt5, p1) == LG.covers(pt5, p1) == false - -# # Line and Geometry - -# Same line -> covers -@test GO.covers(l1, l1) == LG.covers(l1, l1) == true -# Line overlaps line edge and endpoint -> covers -@test GO.covers(l1, l2) == LG.covers(l1, l2) == true -# Line overlaps with one edge and is outside of other edge -> not covered -@test GO.covers(l1, l3) == LG.covers(l1, l3) == false -# Line segments both within other line segments -> covers -@test GO.covers(l1, l4) == LG.covers(l1, l4) == true -# Line segments connect at endpoint -> not covered -@test GO.covers(l1, l5) == LG.covers(l1, l5) == false -# Line segments don't touch -> not covered -@test GO.covers(l1, l6) == LG.covers(l1, l6) == false -# Line segments cross -> not covered -@test GO.covers(l1, l7) == LG.covers(l1, l7) == false -# Line segments cross and go over and out -> not covered -@test GO.covers(l1, l8) == LG.covers(l1, l8) == false -# Line segments cross and overlap on endpoint -> doesn't cover -@test GO.covers(l1, l9) == LG.covers(l1, l9) == false -# Line is within linear ring -> doesn't cover -@test GO.covers(l1, r1) == LG.covers(l1, r1) == false -# Line covers one edge of linera ring and has segment outside -> doesn't cover -@test GO.covers(l3, r1) == LG.covers(l3, r1) == false -# Line and linear ring are only connected at vertex -> doesn't cover -@test GO.covers(l5, r1) == LG.covers(l5, r1) == false -# Line and linear ring are disjoint -> doesn't cover -@test GO.covers(l6, r1) == LG.covers(l6, r1) == false -# Line crosses through two ring edges -> doesn't cover -@test GO.covers(l7, r1) == LG.covers(l7, r1) == false -# Line crosses through two ring edges and touches third edge -> doesn't cover -@test GO.covers(l8, r1) == LG.covers(l8, r1) == false -# Line is equal to linear ring -> covers -@test GO.covers(l10, r1) == LG.covers(l10, r1) == true -# Line covers linear ring and then has extra segment -> covers -@test GO.covers(l11, r1) == LG.covers(l11, r1) == true - -# # Ring and Geometry - -# Line is within linear ring -> covers -@test GO.covers(r1, l1) == LG.covers(r1, l1) == true -# Line covers one edge of linear ring and has segment outside -> doesn't cover -@test GO.covers(r1, l3) == LG.covers(r1, l3) == false -# Line and linear ring are only connected at vertex -> doesn't cover -@test GO.covers(r1, l5) == LG.covers(r1, l5) == false -# Line and linear ring are disjoint -> doesn't cover -@test GO.covers(r1, l6) == LG.covers(r1, l6) == false -# Line crosses through two ring edges -> doesn't cover -@test GO.covers(r1, l7) == LG.covers(r1, l7) == false -# Line crosses through two ring edges and touches third edge -> doesn't cover -@test GO.covers(r1, l8) == LG.covers(r1, l8) == false -# Line is equal to linear ring -> cover -@test GO.covers(r1, l10) == LG.covers(r1, l10) == true -# Line covers linear ring and then has extra segment -> doesn't cover -@test GO.covers(r1, l11) == LG.covers(r1, l11) == false - - - - - - - -# p1 = LG.Point([0.0, 0.0]) -# p2 = LG.Point([0.0, 0.1]) -# p3 = LG.Point([1.0, 0.0]) - -# l1 = LG.LineString([[0.0, 1.0]]) -# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) - -# # Point and point -# @test GO.covers(p1, p1) == LG.covers(p1, p1) -# @test GO.covers(p1, p2) == LG.covers(p1, p2) - -# # Point and line -# @test GO.covers(l1, p1) == LG.covers(l1, p1) -# @test GO.covers(l1, p2) == LG.covers(l1, p2) -# @test GO.covers(l1, p3) == LG.covers(l1, p3) -# @test GO.covers(l2, p1) == LG.covers(l2, p1) -# @test GO.covers(l2, p2) == LG.covers(l2, p2) -# @test GO.covers(l2, p3) == LG.covers(l2, p3) - -# # Line and line -# @test GO.covers(l1, l1) == LG.covers(l1, l1) -# @test GO.covers(l2, l1) == LG.covers(l2, l1) -# @test GO.covers(l3, l1) == LG.covers(l3, l1) -# @test GO.covers(l4, l1) == LG.covers(l4, l1) -# @test GO.covers(l5, l1) == LG.covers(l5, l1) \ No newline at end of file diff --git a/test/methods/geom_relations/crosses.jl b/test/methods/geom_relations/crosses.jl deleted file mode 100644 index c7b167f3e..000000000 --- a/test/methods/geom_relations/crosses.jl +++ /dev/null @@ -1,86 +0,0 @@ -# # Point and Geometry - -# Same point -> doesn't cross -@test GO.crosses(pt1, pt1) == LG.crosses(pt1, pt1) == false -# Different point -> doesn't cross -@test GO.crosses(pt1, pt2) == LG.crosses(pt1, pt2) == false -# Point cannot cross line -> doesn't cross -@test GO.crosses(pt3, l1) == LG.crosses(pt3, l1) == false -# Line cannot cross point -> doesn't cross -@test GO.crosses(l1, pt3) == LG.crosses(l1, pt3) == false -# Point cannot cross ring -> doesn't cross -@test GO.crosses(pt3, r1) == LG.crosses(pt3, r1) == false -# Ring cannot cross point -> doesn't cross -@test GO.crosses(r1, pt3) == LG.crosses(r1, pt3) == false -# Point cannot cross polygon -> doesn't cross -@test GO.crosses(pt3, p1) == LG.crosses(pt3, p1) == false -# Polygon cannot cross point -> doesn't cross -@test GO.crosses(p1, pt3) == LG.crosses(p1, pt3) == false - -# # Line and Geometry - -# Same line -> doesn't cross -@test GO.crosses(l1, l1) == LG.crosses(l1, l1) == false -# Line overlaps line edge and endpoint -> doesn't cross -@test GO.crosses(l1, l2) == LG.crosses(l1, l2) == false -# Line overlaps with one edge and is outside of other edge -> not covered -@test GO.crosses(l1, l3) == LG.crosses(l1, l3) == false -# Line segments both within other line segments -> doesn't cross -@test GO.crosses(l1, l4) == LG.crosses(l1, l4) == false -# Line segments connect at endpoint -> doesn't cross -@test GO.crosses(l1, l5) == LG.crosses(l1, l5) == false -# Line segments don't touch -> doesn't cross -@test GO.crosses(l1, l6) == LG.crosses(l1, l6) == false -# Line segments cross -> crosses -@test GO.crosses(l1, l7) == LG.crosses(l1, l7) == true -# Line segments cross and go over and out -> doesn't cross -@test GO.crosses(l1, l8) == LG.crosses(l1, l8) == false -# Line segments cross and overlap on endpoint -> crosses -@test GO.crosses(l1, l9) == LG.crosses(l1, l9) == true - -#= -Extra tests since this uses different code due to curve-curve crosses definition -requiring meeting in a point and having lines cross one another -=# -h = LG.LineString([[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]) -v1 = LG.LineString([[0.0, 1.0], [0.0, 0.0], [0.0, -1.0]]) -v2 = LG.LineString([[2.0, 1.0], [2.0, 0.0], [2.0, -1.0]]) -v3 = LG.LineString([[1.0, 1.0], [1.0, 0.0], [1.0, -1.0]]) -v4 = LG.LineString([[1.5, 1.0], [1.5, 0.0], [1.5, -1.0]]) -v5 = LG.LineString([[1.5, 1.0], [1.5, -1.0]]) -d1 = LG.LineString([[-1.0, -1.0], [1.0, 1.0]]) -d2 = LG.LineString([[-1.0, 1.0], [0.0, 0.0], [1.0, -1.0]]) -b1 = LG.LineString([[0.0, 1.0], [1.5, 0.0], [0.0, -1.0]]) -b2 = LG.LineString([[1.0, 1.0], [1.5, 0.0], [2.0, 1.0]]) -b3 = LG.LineString([[0.0, 1.0], [1.0, 0.0], [2.0, 1.0]]) -b4 = LG.LineString([[1.0, -0.5], [0.0, 0.0], [1.0, 0.0]]) -b5 = LG.LineString([[-1.0, 0.0], [0.0, 0.0], [1.0, 1.0]]) -b6 = LG.LineString([[-1.0, 0.0], [0.0, 0.0], [1.0, -1.0]]) -b7 = LG.LineString([[1.0, 0.0], [0.0, 0.0], [-1.0, -1.0]]) -# Crosses through line starting endpoint -> doesn't cross -@test GO.crosses(h, v1) == LG.crosses(h, v1) == false -# Crosses through line ending endpoint -> doesn't cross -@test GO.crosses(h, v2) == LG.crosses(h, v2) == false -# Crosses through line middle vertex -> crosses -@test GO.crosses(h, v3) == LG.crosses(h, v3) == true -# Crosses through line edge at vertex -> crosses -@test GO.crosses(h, v4) == LG.crosses(h, v4) == true -# Crosses through line edge -> crosses -@test GO.crosses(h, v5) == LG.crosses(h, v5) == true -# Crosses through line edge -> crosses -@test GO.crosses(v5, h) == LG.crosses(v5, h) == true -# Line bounces off of vertical curve on edge -> doesn't cross -@test GO.crosses(b1, v5) == GO.crosses(v5, b1) == LG.crosses(b1, v5) == true -# Line bounces off of horizontal curve on edge --> doesn't cross -@test GO.crosses(b2, h) == GO.crosses(h, b2) == LG.crosses(b2, h) == true -# Line bounces off of horizontal curve on vertex --> crosses -@test GO.crosses(b3, h) == GO.crosses(h, b3) == LG.crosses(b3, h) == true -# Diagonal lines pass through one another --> crosses -@test GO.crosses(d1, d2) == GO.crosses(d2, d1) == LG.crosses(d1, d2) == true -# Curve bounces off of diagonal line -> crosses -@test GO.crosses(d1, b4) == GO.crosses(b4, d1) == LG.crosses(d1, b4) == true -# Lines with parallel segments cross -> cross -@test GO.crosses(b5, b7) == GO.crosses(b7, b5) == LG.crosses(b7, b5) == true -# Lines with parallel segments bounce -> crosses -@test GO.crosses(b6, b7) == GO.crosses(b7, b6) == LG.crosses(b7, b6) == true - diff --git a/test/methods/geom_relations/disjoint.jl b/test/methods/geom_relations/disjoint.jl deleted file mode 100644 index 386ce09e7..000000000 --- a/test/methods/geom_relations/disjoint.jl +++ /dev/null @@ -1,266 +0,0 @@ -# # Point and Geometry - -# Same point --> not disjoint -@test GO.disjoint(pt1, pt1) == LG.disjoint(pt1, pt1) == false -# Different point --> disjoint -@test GO.disjoint(pt1, pt2) == LG.disjoint(pt1, pt2) == true -# Point on line endpoint -> not disjoint -@test GO.disjoint(pt1, l1) == GO.disjoint(l1, pt1) == LG.disjoint(l1, pt1) == false -# Point outside line -> disjoint -@test GO.disjoint(pt2, l1) == GO.disjoint(l1, pt2) == LG.disjoint(l1, pt2) == true -# Point on line segment -> not disjoint -@test GO.disjoint(pt3, l1) == GO.disjoint(l1, pt3) == LG.disjoint(l1, pt3) == false -# Point on line vertex between segments -> not disjoint -@test GO.disjoint(pt4, l1) == GO.disjoint(l1, pt4) == LG.disjoint(l1, pt4) == false -# Point on ring endpoint -> not disjoint -@test GO.disjoint(pt1, r1) == GO.disjoint(r1, pt1) == LG.disjoint(r1, pt1) == false -# Point outside ring -> disjoint -@test GO.disjoint(pt2, r1) == GO.disjoint(r1, pt2) == LG.disjoint(r1, pt2) == true -# Point on ring segment -> not disjoint -@test GO.disjoint(pt3, r1) == GO.disjoint(r1, pt3) == LG.disjoint(r1, pt3) == false -# Point on ring vertex between segments -> not disjoint -@test GO.disjoint(pt4, r1) == GO.disjoint(r1, pt4) == LG.disjoint(r1, pt4) == false -# Point within hole formed by ring -> disjoint -@test GO.disjoint(pt5, r1) == GO.disjoint(r1, pt5) == LG.disjoint(r1, pt5) == true -# Point on vertex of polygon --> not disjoint -@test GO.disjoint(pt1, p1) == GO.disjoint(p1, pt1) == LG.disjoint(p1, pt1) == false -# Point outside of polygon's external ring -> disjoint -@test GO.disjoint(pt2, p1) == GO.disjoint(p1, pt2) == LG.disjoint(p1, pt2) == true -# Point on polygon's edge -> not disjoint -@test GO.disjoint(pt4, p1) == GO.disjoint(p1, pt4) == LG.disjoint(p1, pt4) == false -# Point inside of polygon -> not disjoint -@test GO.disjoint(pt5, p1) == GO.disjoint(p1, pt5) == LG.disjoint(p1, pt5) == false -# Point on hole edge -> not disjoint -@test GO.disjoint(pt6, p1) == GO.disjoint(p1, pt6) == LG.disjoint(p1, pt6) == false -# Point inside of polygon hole -> disjoint -@test GO.disjoint(pt7, p1) == GO.disjoint(p1, pt7) == LG.disjoint(p1, pt7) == true - -# # Line and Geometry - -# Same line -> not disjoint -@test GO.disjoint(l1, l1) == LG.disjoint(l1, l1) == false -# Line overlaps line edge and endpoint -> not disjoint -@test GO.disjoint(l1, l2) == LG.disjoint(l1, l2) == false -# Line overlaps with one edge and is outside of other edge -> not disjoint -@test GO.disjoint(l1, l3) == LG.disjoint(l1, l3) == false -# Line segments both within other line segments -> not disjoint -@test GO.disjoint(l1, l4) == LG.disjoint(l1, l4) == false -# Line segments connect at endpoint -> not disjoint -@test GO.disjoint(l1, l5) == LG.disjoint(l1, l5) == false -# Line segments don't touch -> disjoint -@test GO.disjoint(l1, l6) == LG.disjoint(l1, l6) == true -# Line segments cross -> not disjoint -@test GO.disjoint(l1, l7) == LG.disjoint(l1, l7) == false -# Line segments cross and go over and out -> not disjoint -@test GO.disjoint(l1, l8) == LG.disjoint(l1, l8) == false -# Line segments cross and overlap on endpoint -> not disjoint -@test GO.disjoint(l1, l9) == LG.disjoint(l1, l9) == false - -# # Test points -# pt1 = LG.Point([0.0, 0.0]) -# pt2 = LG.Point([0.0, 0.1]) -# pt3 = LG.Point([1.0, 0.0]) -# pt4 = LG.Point([0.5, 1.0]) -# pt5 = LG.Point([0.2, 0.5]) -# pt6 = LG.Point([0.3, 0.55]) -# pt7 = LG.Point([0.6, 0.49]) -# pt8 = LG.Point([0.25, 0.75]) -# pt9 = LG.Point([-1.0, 0.0]) -# # Test lines -# l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) -# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) -# l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) -# l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) -# l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) -# l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) -# l7 = LG.LineString([[0.6, 0.6], [0.8, 0.6]]) -# l8 = LG.LineString([[0.3, 0.55], [0.3, 0.65]]) -# l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) -# l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) -# l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) -# l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) -# l13 = LG.LineString([[0.5, 0.01], [0.5, 0.09]]) -# # Test rings -# r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) -# r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) -# r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) -# r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) -# r5 = LG.LinearRing([[5.0, 5.0], [6.0, 6.0], [7.0, 5.0], [5.0, 5.0]]) -# r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) -# # Test polygons -# p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) -# p2 = LG.Polygon([ -# [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], -# [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], -# [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] -# ]) -# p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) -# p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) -# p5 = LG.Polygon([[[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]]) -# p6 = LG.Polygon([[[0.1, 0.4], [0.1, 0.8], [0.3, 0.8], [0.3, 0.4], [0.1, 0.4]]]) -# p7 = LG.Polygon([[[-2.0, 0.0], [-1.0, 0.0], [-1.5, 1.5], [-2.0, 0.0]]]) -# p8 = LG.Polygon([ -# [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] -# ]) -# # Test multipolygons -# m1 = LG.MultiPolygon([p3, p6]) -# m2 = LG.MultiPolygon([p3, p4]) -# m3 = LG.MultiPolygon([p2, p7]) -# m4 = LG.MultiPolygon([p7]) - -# # # Point and point -# # Equal points -> not disjoint -# @test GO.disjoint(pt1, pt1) == LG.disjoint(pt1, pt1) -# # Non-equal points -> disjoint -# @test GO.disjoint(pt1, pt2) == LG.disjoint(pt1, pt2) - -# # # Point and line -# # Line endpoint (1 segment) -> not disjoint -# @test GO.disjoint(pt1, l1) == LG.disjoint(pt1, l1) -# # Middle of line (1 segment) -> not disjoint -# @test GO.disjoint(pt2, l1) == LG.disjoint(pt2, l1) -# # Not on line (1 segment) -> disjoint -# @test GO.disjoint(pt3, l1) == LG.disjoint(pt3, l1) -# # Line endpoint (2 segments) -> not disjoing -# @test GO.disjoint(pt2, l2) == LG.disjoint(pt2, l2) -# # Middle of line on joint (2 segments) -> not disjoint -# @test GO.disjoint(pt3, l2) == LG.disjoint(pt3, l2) -# # Endpoint on closed line -> not disjoint -# @test GO.disjoint(pt1, l6) == LG.disjoint(pt1, l6) - -# # # Point and ring -# # On ring corner -> not disjoint -# @test GO.disjoint(pt1, r1) == LG.disjoint(pt1, r1) -# # Outside of ring -> disjoint -# @test GO.disjoint(pt2, r1) == LG.disjoint(pt2, r1) -# # Inside of ring center (not on line) -> disjoint -# @test GO.disjoint(pt3, r1) == LG.disjoint(pt3, r1) -# # On ring edge -> not disjoint -# @test GO.disjoint(pt8, r1) == LG.disjoint(pt8, r1) - -# # # Point and polygon -# # Point on polygon vertex -> not disjoint -# @test GO.disjoint(pt1, p2) == LG.disjoint(pt1, p2) -# # Point on polygon edge -> not disjoint -# @test GO.disjoint(pt2, p2) == LG.disjoint(pt2, p2) -# # Point on edge of hold --> not disjoint -# @test GO.disjoint(pt5, p2) == LG.disjoint(pt5, p2) -# # Point in hole -> disjoint -# @test GO.disjoint(pt6, p2) == LG.disjoint(pt6, p2) -# # Point inside of polygon -> not disjoint -# @test GO.disjoint(pt7, p2) == LG.disjoint(pt7, p2) -# # Point outside of polygon -> disjoint -# @test GO.disjoint(pt9, p2) == LG.disjoint(pt9, p2) - -# # # Geometry and point (switched direction) -# @test GO.disjoint(pt1, l1) == GO.disjoint(l1, pt1) -# @test GO.disjoint(pt1, r1) == GO.disjoint(r1, pt1) -# @test GO.disjoint(pt1, p2) == GO.disjoint(p2, pt1) - -# # # Line and line -# # Equal lines -> not disjoint -# @test GO.disjoint(l1, l1) == LG.disjoint(l1, l1) -# # Lines share 2 endpoints, but don't overlap -> not disjoint -# @test GO.disjoint(l1, l2) == LG.disjoint(l1, l2) -# # Lines overlap, but neither is within other -> not disjoint -# @test GO.disjoint(l1, l3) == LG.disjoint(l1, l3) -# # Within line (no shared endpoints) -> not disjoint -# @test GO.disjoint(l1, l4) == LG.disjoint(l1, l4) -# # Line shares just 1 endpoint -> not disjoint -# @test GO.disjoint(l1, l5) == LG.disjoint(l1, l5) -# # Lines don't touch at all -> disjoint -# @test GO.disjoint(l7, l1) == LG.disjoint(l7, l1) - -# # # Line and ring -# # Shares all endpoints -> not disjoint -# @test GO.disjoint(l6, r1) == LG.disjoint(l6, r1) -# # Shares only some edges -> not disjoint -# @test GO.disjoint(l2, r3) == LG.disjoint(l2, r3) -# # line inside of ring -> disjoint -# @test GO.disjoint(l7, r1) == LG.disjoint(l7, r1) -# # line outside of ring -> disjoint -# @test GO.disjoint(l7, r2) == LG.disjoint(l7, r2) - -# # # # Line and polygon -# # # Line traces entire outline of polygon edges -> not disjoint -# # @test GO.disjoint(l6, p1) == LG.disjoint(l6, p1) -# # # Line is on edge + inside of polygon -> not disjoint -# # @test GO.disjoint(l2, p2) == LG.disjoint(l2, p2) -# # # Line goes outside of polygon -> not disjoint -# # @test GO.disjoint(l3, p2) == LG.disjoint(l3, p2) -# # # Line is fully within hole -> disjoint -# # @test GO.disjoint(l8, p2) == LG.disjoint(l8, p2) -# # # Line is on polygon edge and then cuts through hole -> not disjoint -# # @test GO.disjoint(l11, p2) == LG.disjoint(l11, p2) - -# # # Geometry and line (switched direction) -# @test GO.disjoint(l7, r1) == GO.disjoint(r1, l7) -# # @test GO.disjoint(l8, p2) == GO.disjoint(p2, l8) - -# # # Ring and line -# # Shares all endpoints -> not disjoint -# @test GO.disjoint(r1, l6) == LG.disjoint(r1, l6) -# # Shares some edges -> not disjoint -# @test GO.disjoint(r3, l2) == LG.disjoint(r3, l2) -# # Doesn't share any edges -> disjoint -# @test GO.disjoint(r4, l2) == LG.disjoint(r4, l2) - -# # # Ring and ring -# # Equal ring -> not disjoint -# @test GO.disjoint(r1, r1) == LG.disjoint(r1, r1) -# # Not equal ring but share a vertex -> not disjoint -# @test GO.disjoint(r1, r2) == LG.disjoint(r1, r2) -# # Rings not touching -> not disjoint -# @test GO.disjoint(r3, r4) == LG.disjoint(r3, r4) -# # Ring inside of ring -> disjoint -# @test GO.disjoint(r4, r2) == LG.disjoint(r4, r2) -# # Ring outside of other ring -> disjoint -# @test GO.disjoint(r2, r4) == LG.disjoint(r2, r4) - -# # Ring and polygon -# # Ring goes outside of polygon's external ring -> not disjoint -# @test GO.within(r1, p2) == LG.within(r1, p1) -# # Ring is one of polygon's holes -> not disjoint -# @test GO.disjoint(r4, p2) == LG.disjoint(r4, p2) -# # Ring is fully within polygon -> not disjoint -# @test GO.disjoint(r5, p2) == LG.disjoint(r5, p2) -# # Ring is fully within polygon's hole -> disjoint -# @test GO.disjoint(r6, p2) == LG.disjoint(r6, p2) -# # Ring is fully outside of the polygon -> disjoint -# @test GO.disjoint(r5, p2) == LG.disjoint(r5, p2) - -# # # Geometry and ring (switched direction) -# @test GO.disjoint(r4, r2) == GO.disjoint(r2, r4) -# @test GO.disjoint(p2, r6) == GO.disjoint(r6, p2) - -# # # Polygon and polygon -# # Overlapping polygons -> not disjoint -# @test GO.disjoint(p1, p2) == LG.disjoint(p1, p2) -# # Polygon is within polygon, but also on edges -> not disjoint -# @test GO.disjoint(p3, p2) == LG.disjoint(p3, p2) -# # Polygon within polygon hole --> disjoint -# @test GO.disjoint(p5, p2) == LG.disjoint(p5, p2) -# # polygon extactly overlaps with other polygon's hole -> not disjoint -# @test GO.disjoint(p8, p7) == LG.disjoint(p8, p7) - -# # # Multipolygon tests -# # Point in multipolygon -> not disjoint -# @test GO.disjoint(pt5, m1) == LG.disjoint(pt5, m1) -# # Point outside of multipolygon -> disjoint -# @test GO.disjoint(pt4, m1) == LG.disjoint(pt4, m1) -# # Line in multipolygon -> not disjoint -# @test GO.disjoint(l13, m1) == LG.disjoint(l13, m1) -# # Line outside of multipolygon -> disjoint -# @test GO.disjoint(l8, m3) == LG.disjoint(l8, m3) -# # Ring in multipolygon -> not disjoint -# @test GO.disjoint(r1, m2) == LG.disjoint(r1, m2) -# # Ring outside of multipolygon -# @test GO.disjoint(r6, m3) == LG.disjoint(r6, m3) -# # Polygon in multipolygon -> not disjoint -# @test GO.disjoint(p3, m1) == LG.disjoint(p3, m1) -# # Polygon outside of multipolygon -> disjoint -# @test GO.disjoint(p5, m3) == LG.disjoint(p5, m3) -# # Multipolygon in multipolygon -> not disjoint -# @test GO.disjoint(m1, m1) == LG.disjoint(m1, m1) -# # Multipolygon outside of multipolygon -> disjoint -# @test GO.disjoint(m1, m4) == LG.disjoint(m1, m4) \ No newline at end of file diff --git a/test/methods/geom_relations/intersects.jl b/test/methods/geom_relations/intersects.jl deleted file mode 100644 index 9747c49ce..000000000 --- a/test/methods/geom_relations/intersects.jl +++ /dev/null @@ -1,201 +0,0 @@ -# Same point --> intersects -@test GO.intersects(pt1, pt1) == LG.intersects(pt1, pt1) == true -# Different point --> don't intersect -@test GO.intersects(pt1, pt2) == LG.intersects(pt1, pt2) == false -# Point on line endpoint -> intersects -@test GO.intersects(pt1, l1) == GO.intersects(l1, pt1) == LG.intersects(l1, pt1) == true -# Point outside line -> don't intersect -@test GO.intersects(pt2, l1) == GO.intersects(l1, pt2) == LG.intersects(l1, pt2) == false -# Point on line segment -> intersects -@test GO.intersects(pt3, l1) == GO.intersects(l1, pt3) == LG.intersects(l1, pt3) == true -# Point on line vertex between segments -> intersects -@test GO.intersects(pt4, l1) == GO.intersects(l1, pt4) == LG.intersects(l1, pt4) == true -# Point on ring endpoint -> intersects -@test GO.intersects(pt1, r1) == GO.intersects(r1, pt1) == LG.intersects(r1, pt1) == true -# Point outside ring -> don't intersect -@test GO.intersects(pt2, r1) == GO.intersects(r1, pt2) == LG.intersects(r1, pt2) == false -# Point on ring segment -> intersects -@test GO.intersects(pt3, r1) == GO.intersects(r1, pt3) == LG.intersects(r1, pt3) == true -# Point on ring vertex between segments -> intersects -@test GO.intersects(pt4, r1) == GO.intersects(r1, pt4) == LG.intersects(r1, pt4) == true -# Point within hole formed by ring -> don't intersect -@test GO.intersects(pt5, r1) == GO.intersects(r1, pt5) == LG.intersects(r1, pt5) == false -# Point on vertex of polygon --> intersects -@test GO.intersects(pt1, p1) == GO.intersects(p1, pt1) == LG.intersects(p1, pt1) == true -# Point outside of polygon's external ring -> doesn't intersect -@test GO.intersects(pt2, p1) == GO.intersects(p1, pt2) == LG.intersects(p1, pt2) == false -# Point on polygon's edge -> intersects -@test GO.intersects(pt4, p1) == GO.intersects(p1, pt4) == LG.intersects(p1, pt4) == true -# Point inside of polygon -> intersects -@test GO.intersects(pt5, p1) == GO.intersects(p1, pt5) == LG.intersects(p1, pt5) == true -# Point on hole edge -> intersects -@test GO.intersects(pt6, p1) == GO.intersects(p1, pt6) == LG.intersects(p1, pt6) == true -# Point inside of polygon hole -> doesn't intersect -@test GO.intersects(pt7, p1) == GO.intersects(p1, pt7) == LG.intersects(p1, pt7) == false - -# # Line and Geometry - -# Same line -> intersects -@test GO.intersects(l1, l1) == LG.intersects(l1, l1) == true -# Line overlaps line edge and endpoint -> intersects -@test GO.intersects(l1, l2) == LG.intersects(l1, l2) == true -# Line overlaps with one edge and is outside of other edge -> intersects -@test GO.intersects(l1, l3) == LG.intersects(l1, l3) == true -# Line segments both within other line segments -> intersects -@test GO.intersects(l1, l4) == LG.intersects(l1, l4) == true -# Line segments connect at endpoint -> intersects -@test GO.intersects(l1, l5) == LG.intersects(l1, l5) == true -# Line segments don't touch -> doesn't intersect -@test GO.intersects(l1, l6) == LG.intersects(l1, l6) == false -# Line segments cross -> intersects -@test GO.intersects(l1, l7) == LG.intersects(l1, l7) == true -# Line segments cross and go over and out -> intersects -@test GO.intersects(l1, l8) == LG.intersects(l1, l8) == true -# Line segments cross and overlap on endpoint -> intersects -@test GO.intersects(l1, l9) == LG.intersects(l1, l9) == true - -# @testset "Lines/Rings" begin -# # Line test intersects ----------------------------------------------------- - -# # Test for parallel lines - # l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) - # l2 = GI.Line([(0.0, 1.0), (2.5, 1.0)]) - # @test !GO.intersects(l1, l2) -# @test isnothing(GO.intersection(l1, l2)) - -# # Test for non-parallel lines that don't intersect -# l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) -# l2 = GI.Line([(2.0, -3.0), (3.0, 0.0)]) -# @test !GO.intersects(l1, l2) -# @test isnothing(GO.intersection(l1, l2)) - -# # Test for lines only touching at endpoint -# l1 = GI.Line([(0.0, 0.0), (2.5, 0.0)]) -# l2 = GI.Line([(2.0, -3.0), (2.5, 0.0)]) -# @test GO.intersects(l1, l2) -# @test all(GO.intersection(l1, l2) .≈ (2.5, 0.0)) - -# # Test for lines that intersect in the middle -# l1 = GI.Line([(0.0, 0.0), (5.0, 5.0)]) -# l2 = GI.Line([(0.0, 5.0), (5.0, 0.0)]) -# @test GO.intersects(l1, l2) -# @test all(GO.intersection(l1, l2) .≈ (2.5, 2.5)) - -# # Line string test intersects ---------------------------------------------- - -# # Single element line strings crossing over each other -# l1 = LG.LineString([[5.5, 7.2], [11.2, 12.7]]) -# l2 = LG.LineString([[4.3, 13.3], [9.6, 8.1]]) -# @test GO.intersects(l1, l2) -# go_inter = GO.intersection(l1, l2) -# lg_inter = LG.intersection(l1, l2) -# @test go_inter[1][1] .≈ GI.x(lg_inter) -# @test go_inter[1][2] .≈ GI.y(lg_inter) - -# # Multi-element line strings crossing over on vertex -# l1 = LG.LineString([[0.0, 0.0], [2.5, 0.0], [5.0, 0.0]]) -# l2 = LG.LineString([[2.0, -3.0], [3.0, 0.0], [4.0, 3.0]]) -# @test GO.intersects(l1, l2) -# go_inter = GO.intersection(l1, l2) -# @test length(go_inter) == 1 -# lg_inter = LG.intersection(l1, l2) -# @test go_inter[1][1] .≈ GI.x(lg_inter) -# @test go_inter[1][2] .≈ GI.y(lg_inter) - -# # Multi-element line strings crossing over with multiple intersections -# l1 = LG.LineString([[0.0, -1.0], [1.0, 1.0], [2.0, -1.0], [3.0, 1.0]]) -# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [3.0, 0.0]]) -# @test GO.intersects(l1, l2) -# go_inter = GO.intersection(l1, l2) -# @test length(go_inter) == 3 -# lg_inter = LG.intersection(l1, l2) -# @test issetequal( -# Set(go_inter), -# Set(GO._tuple_point.(GI.getpoint(lg_inter))) -# ) - -# # Line strings far apart so extents don't overlap -# l1 = LG.LineString([[100.0, 0.0], [101.0, 0.0], [103.0, 0.0]]) -# l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [3.0, 0.0]]) -# @test !GO.intersects(l1, l2) -# @test isnothing(GO.intersection(l1, l2)) - -# # Line strings close together that don't overlap -# l1 = LG.LineString([[3.0, 0.25], [5.0, 0.25], [7.0, 0.25]]) -# l2 = LG.LineString([[0.0, 0.0], [5.0, 10.0], [10.0, 0.0]]) -# @test !GO.intersects(l1, l2) -# @test isempty(GO.intersection(l1, l2)) - -# # Closed linear ring with open line string -# r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) -# l2 = LG.LineString([[0.0, -2.0], [12.0, 10.0],]) -# @test GO.intersects(r1, l2) -# go_inter = GO.intersection(r1, l2) -# @test length(go_inter) == 2 -# lg_inter = LG.intersection(r1, l2) -# @test issetequal( -# Set(go_inter), -# Set(GO._tuple_point.(GI.getpoint(lg_inter))) -# ) - -# # Closed linear ring with closed linear ring -# r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) -# r2 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) -# @test GO.intersects(r1, r2) -# go_inter = GO.intersection(r1, r2) -# @test length(go_inter) == 2 -# lg_inter = LG.intersection(r1, r2) -# @test issetequal( -# Set(go_inter), -# Set(GO._tuple_point.(GI.getpoint(lg_inter))) -# ) -# end - -# @testset "Polygons" begin -# # Two polygons that intersect -# p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) -# p2 = LG.Polygon([[[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]]) -# @test GO.intersects(p1, p2) -# @test all(GO.intersection_points(p1, p2) .== [(6.5, 3.5), (6.5, -3.5)]) - -# # Two polygons that don't intersect -# p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) -# p2 = LG.Polygon([[[13.0, 0.0], [18.0, 5.0], [23.0, 0.0], [18.0, -5.0], [13.0, 0.0]]]) -# @test !GO.intersects(p1, p2) -# @test isnothing(GO.intersection_points(p1, p2)) - -# # Polygon that intersects with linestring -# p1 = LG.Polygon([[[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]]) -# l2 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) -# @test GO.intersects(p1, l2) -# GO.intersection_points(p1, l2) -# @test all(GO.intersection_points(p1, l2) .== [(0.0, 0.0), (10.0, 0.0)]) - -# # Polygon with a hole, line through polygon and hole -# p1 = LG.Polygon([ -# [[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]], -# [[2.0, -1.0], [2.0, 1.0], [3.0, 1.0], [3.0, -1.0], [2.0, -1.0]] -# ]) -# l2 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) -# @test GO.intersects(p1, l2) -# @test all(GO.intersection_points(p1, l2) .== [(0.0, 0.0), (2.0, 0.0), (3.0, 0.0), (10.0, 0.0)]) - -# # Polygon with a hole, line only within the hole -# p1 = LG.Polygon([ -# [[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]], -# [[2.0, -1.0], [2.0, 1.0], [3.0, 1.0], [3.0, -1.0], [2.0, -1.0]] -# ]) -# l2 = LG.LineString([[2.25, 0.0], [2.75, 0.0]]) -# @test !GO.intersects(p1, l2) -# @test isempty(GO.intersection_points(p1, l2)) -# end - -# @testset "MultiPolygons" begin -# # TODO: Add these tests -# # Multi-polygon and polygon that intersect - -# # Multi-polygon and polygon that don't intersect - -# # Multi-polygon that intersects with linestring - -# end \ No newline at end of file diff --git a/test/methods/geom_relations/overlaps.jl b/test/methods/geom_relations/overlaps.jl deleted file mode 100644 index 0079be0cd..000000000 --- a/test/methods/geom_relations/overlaps.jl +++ /dev/null @@ -1,151 +0,0 @@ -# # Point and Geometry - -# Same point -> doesn't overlap -@test GO.overlaps(pt1, pt1) == LG.overlaps(pt1, pt1) == false -# Different point -> doesn't overlap -@test GO.overlaps(pt1, pt2) == LG.overlaps(pt1, pt2) == false -# Point cannot overlap line -> doesn't overlap -@test GO.overlaps(pt3, l1) == GO.overlaps(l1, pt3) == LG.overlaps(pt3, l1) == false -# Line cannot overlap point -> doesn't overlap -@test GO.overlaps(l1, pt3) == LG.overlaps(l1, pt3) == false -# Point cannot overlap ring -> doesn't overlap -@test GO.overlaps(pt3, r1) == LG.overlaps(pt3, r1) == false -# Ring cannot overlap point -> doesn't overlap -@test GO.overlaps(r1, pt3) == LG.overlaps(r1, pt3) == false -# Point cannot overlap polygon -> doesn't overlap -@test GO.overlaps(pt3, p1) == LG.overlaps(pt3, p1) == false -# Polygon cannot overlap point -> doesn't overlap -@test GO.overlaps(p1, pt3) == LG.overlaps(p1, pt3) == false - -# # Line and Geometry - -# Same line -> doesn't overlap -@test GO.overlaps(l1, l1) == LG.overlaps(l1, l1) == false -# Line overlaps line edge and endpoint (nothing exterior) -> doesn't overlap -@test GO.overlaps(l1, l2) == LG.overlaps(l1, l2) == false -# Line overlaps with one edge and is outside of other edge -> overlaps -@test GO.overlaps(l1, l3) == LG.overlaps(l1, l3) == true -# Line segments both within other line segments -> doesn't overlap -@test GO.overlaps(l1, l4) == LG.overlaps(l1, l4) == false -# Line segments connect at endpoint -> doesn't overlap -@test GO.overlaps(l1, l5) == LG.overlaps(l1, l5) == false -# Line segments don't touch -> doesn't overlap -@test GO.overlaps(l1, l6) == LG.overlaps(l1, l6) == false -# Line segments cross -> doesn't overlaps -@test GO.overlaps(l1, l7) == LG.overlaps(l1, l7) == false -# Line segments cross and go over and out -> overlaps -# @test GO.overlaps(l1, l8) == LG.overlaps(l1, l8) == true -# Line segments cross and overlap on endpoint -> don't overlap -# @test GO.overlaps(l1, l9) == LG.overlaps(l1, l9) == false - - -# @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]]) -# l5 = LG.LineString([[0.0, 0.0], [10.0, 0.0]]) -# l6 = LG.LineString([[0.0, 0.0], [0.0, -10.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) -# # Lines that form a hinge at the origin -# @test GO.overlaps(l1, l5) == LG.overlaps(l1, l5) -# # Lines meet at one point and continue parallel in opposite directions -# @test GO.overlaps(l1, l6) == LG.overlaps(l1, l6) -# # 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 diff --git a/test/methods/geom_relations/touches.jl b/test/methods/geom_relations/touches.jl deleted file mode 100644 index 2178646c6..000000000 --- a/test/methods/geom_relations/touches.jl +++ /dev/null @@ -1,61 +0,0 @@ -# # Point and Geometry - -# Same point -> doesn't touch -@test GO.touches(pt1, pt1) == LG.touches(pt1, pt1) == false -# Different point -> doesn't touch -@test GO.touches(pt1, pt2) == LG.touches(pt1, pt2) == false -# Point on line endpoint -> touches -@test GO.touches(pt1, l1) == GO.touches(l1, pt1) == LG.touches(l1, pt1) == true -# Point outside line -> doesn't touch -@test GO.touches(pt2, l1) == GO.touches(l1, pt2) == LG.touches(l1, pt2) == false -# Point on line segment -> doesn't touch -@test GO.touches(pt3, l1) == GO.touches(l1, pt3) == LG.touches(l1, pt3) == false -# Point on line vertex between segments -> doesn't touch -@test GO.touches(pt4, l1) == GO.touches(l1, pt4) == LG.touches(l1, pt4) == false -# Point on ring endpoint -> doesn't touch -@test GO.touches(pt1, r1) == GO.touches(r1, pt1) == LG.touches(r1, pt1) == false -# Point outside ring -> doesn't touch -@test GO.touches(pt2, r1) == GO.touches(r1, pt2) == LG.touches(r1, pt2) == false -# Point on ring segment -> doesn't touch -@test GO.touches(pt3, r1) == GO.touches(r1, pt3) == LG.touches(r1, pt3) == false -# Point on ring vertex between segments -> doesn't touch -@test GO.touches(pt4, r1) == GO.touches(r1, pt4) == LG.touches(r1, pt4) == false -# Point within hole formed by ring -> doesn't touch -@test GO.touches(pt5, r1) == GO.touches(r1, pt5) == LG.touches(r1, pt5) == false -# Point on vertex of polygon --> touches -@test GO.touches(pt1, p1) == GO.touches(p1, pt1) == LG.touches(p1, pt1) == true -# Point outside of polygon's external ring -> doesn't touch -@test GO.touches(pt2, p1) == GO.touches(p1, pt2) == LG.touches(p1, pt2) == false -# Point on polygon's edge -> touches -@test GO.touches(pt4, p1) == GO.touches(p1, pt4) == LG.touches(p1, pt4) == true -# Point inside of polygon -> doesn't touch -@test GO.touches(pt5, p1) == GO.touches(p1, pt5) == LG.touches(p1, pt5) == false -# Point on hole edge -> touches -@test GO.touches(pt6, p1) == GO.touches(p1, pt6) == LG.touches(p1, pt6) == true -# Point inside of polygon hole -> doesn't touch -@test GO.touches(pt7, p1) == GO.touches(p1, pt7) == LG.touches(p1, pt7) == false - -# # Line and Geometry - -# Same line -> doesn't touch -@test GO.touches(l1, l1) == LG.touches(l1, l1) == false -# Line overlaps line edge and endpoint (nothing exterior) -> doesn't touch -@test GO.touches(l1, l2) == LG.touches(l1, l2) == false -# Line overlaps with one edge and is outside of other edge -> doesn't touch -@test GO.touches(l1, l3) == LG.touches(l1, l3) == false -# Line segments both within other line segments -> doesn't touch -@test GO.touches(l1, l4) == LG.touches(l1, l4) == false -# Line segments connect at endpoint -> touches -@test GO.touches(l1, l5) == LG.touches(l1, l5) == true -# Line segments don't touch -> doesn't touch -@test GO.touches(l1, l6) == LG.touches(l1, l6) == false -# Line segments cross -> doesn't touch -@test GO.touches(l1, l7) == LG.touches(l1, l7) == false -# Line segments cross and go over and out -> doesn't touch -@test GO.touches(l1, l8) == LG.touches(l1, l8) == false -# Line segments cross and overlap on endpoint -> doesn't touch -@test GO.touches(l1, l9) == LG.touches(l1, l9) == false - - - - diff --git a/test/methods/geom_relations/within.jl b/test/methods/geom_relations/within.jl deleted file mode 100644 index 2deb1ed3c..000000000 --- a/test/methods/geom_relations/within.jl +++ /dev/null @@ -1,306 +0,0 @@ -# # Point and Geometry -# Same point --> within -@test GO.within(pt1, pt1) == LG.within(pt1, pt1) == true -# Different point --> not within -@test GO.within(pt1, pt2) == LG.within(pt1, pt2) == false -# Point on line endpoint -> not within -@test GO.within(pt1, l1) == LG.within(pt1, l1) == false -# Point outside line -> not within -@test GO.within(pt2, l1) == LG.within(pt2, l1) == false -# Point on line segment -> within -@test GO.within(pt3, l1) == LG.within(pt3, l1) == true -# Point on line vertex between segments -> within -@test GO.within(pt4, l1) == LG.within(pt4, l1) == true -# line cannot be within a point -> not within -@test GO.within(l1, pt3) == LG.within(l1, pt3) == false -# Point on ring endpoint -> within -@test GO.within(pt1, r1) == LG.within(pt1, r1) == true -# Point outside ring -> not within -@test GO.within(pt2, r1) == LG.within(pt2, r1) == false -# Point on ring segment -> within -@test GO.within(pt3, r1) == LG.within(pt3, r1) == true -# Point on ring vertex between segments -> within -@test GO.within(pt4, r1) == LG.within(pt4, r1) == true -# Ring cannot be covered by a point -> not within -@test GO.within(r1, pt3) == LG.within(r1, pt3) == false -# Point on vertex of polygon --> not within -@test GO.within(pt1, p1) == LG.within(pt1, p1) == false -# Point outside of polygon's external ring -> not within -@test GO.within(pt2, p1) == LG.within(pt2, p1) == false -# Point on polygon's edge -> not within -@test GO.within(pt4, p1) == LG.within(pt4, p1) == false -# Point inside of polygon -> within -@test GO.within(pt5, p1) == LG.within(pt5, p1) == true -# Point on hole edge -> not within -@test GO.within(pt6, p1) == LG.within(pt6, p1) == false -# Point inside of polygon hole -> not within -@test GO.within(pt7, p1) == LG.within(pt7, p1) == false -# Polygon can't be within point -> not within -@test GO.within(p1, pt5) == LG.within(p1, pt5) == false - -# # Line and Geometry - -# Same line -> within -@test GO.within(l1, l1) == LG.within(l1, l1) == true -# Line overlaps line edge and endpoint (nothing exterior) -> within -@test GO.within(l2, l1) == LG.within(l2, l1) == true -# Line overlaps with one edge and is outside of other edge -> not within -@test GO.within(l3, l1) == LG.within(l3, l1) == false -# Line segments both within other line segments -> within -@test GO.within(l4, l1) == LG.within(l4, l1) == true -# Line segments connect at endpoint -> not within -@test GO.within(l5, l1) == LG.within(l5, l1) == false -# Line segments don't touch -> not within -@test GO.within(l6, l1) == LG.within(l6, l1) == false -# Line segments cross -> not within -@test GO.within(l7, l1) == LG.within(l7, l1) == false -# Line segments cross and go over and out -> not within -@test GO.within(l8, l1) == LG.within(l8, l1) == false -# Line segments cross and overlap on endpoint -> not within -@test GO.within(l9, l1) == LG.within(l9, l1) == false - -# # Test points -pt1 = LG.Point([0.0, 0.0]) -pt2 = LG.Point([0.0, 0.1]) -pt3 = LG.Point([1.0, 0.0]) -pt4 = LG.Point([0.5, 1.0]) -pt5 = LG.Point([0.2, 0.5]) -pt6 = LG.Point([0.3, 0.55]) -pt7 = LG.Point([0.6, 0.49]) -pt8 = LG.Point([0.25, 0.75]) -pt9 = LG.Point([0.5, 0.1]) -# Test lines -l1 = LG.LineString([[0.0, 0.0], [0.0, 1.0]]) -l2 = LG.LineString([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]]) -l3 = LG.LineString([[0.0, -1.0], [0.0, 0.5]]) -l4 = LG.LineString([[0.0, -1.0], [0.0, 1.5]]) -l5 = LG.LineString([[0.0, -1.0], [0.0, 0.0]]) -l6 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) -l7 = LG.LineString([[0.6, 0.6], [0.8, 0.6]]) -l8 = LG.LineString([[0.3, 0.55], [0.3, 0.65]]) -l9 = LG.LineString([[0.2, 0.5], [0.3, 0.7]]) -l10 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.8, 0.4]]) -l11 = LG.LineString([[1.0, 0.0], [1.0, 1.0], [0.7, 0.21]]) -l12 = LG.LineString([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0], [-1.0, 0.0]]) -l13 = LG.LineString([[0.5, 0.01], [0.5, 0.09]]) -# Test rings -r1 = LG.LinearRing([[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]) -r2 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.0, 0.0]]) -r3 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]) -r4 = LG.LinearRing([[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]]) -r5 = LG.LinearRing([[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]) -r6 = LG.LinearRing([[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]) -r7 = LG.LinearRing([[0.0, 0.0], [1.0, 0.0], [0.0, 0.1], [0.5, 0.3], [0.0, 0.3],[0.0, 0.0]]) -# Test polygons -p1 = LG.Polygon([[[0.0, 0.0], [0.5, 1.5], [2.5, -0.5], [0.0, 0.0]]]) -p2 = LG.Polygon([ - [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], - [[0.2, 0.5], [0.3, 0.7], [0.4, 0.5], [0.2, 0.5]], - [[0.5, 0.5], [0.8, 0.4], [0.7, 0.2], [0.5, 0.5]] -]) -p3 = LG.Polygon([[[0.0, 0.0], [1.0, 0.0], [0.0, 0.2], [0.0, 0.0]]]) -p4 = LG.Polygon([[[0.6, 0.9], [0.7, 0.8], [0.6, 0.8], [0.6, 0.9]]]) -p5 = LG.Polygon([[[0.25, 0.55], [0.3, 0.65], [0.35, 0.55], [0.25, 0.55]]]) -p6 = LG.Polygon([[[0.1, 0.4], [0.1, 0.8], [0.3, 0.8], [0.3, 0.4], [0.1, 0.4]]]) -p7 = LG.Polygon([ - [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]], - [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]], -]) -p8 = LG.Polygon([ - [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], - [[0.3, 0.3], [0.3, 0.7], [0.7, 0.7], [0.7, 0.3], [0.3, 0.3]] -]) -p9 = LG.Polygon([ - [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], - [[0.45, 0.45], [0.45, 0.55], [0.55, 0.55], [0.55, 0.45], [0.45, 0.45]] -]) -p10 = LG.Polygon([ - [[0.1, 0.1], [0.1, 0.9], [0.9, 0.9], [0.9, 0.1], [0.1, 0.1]], - [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] -]) -p11 = LG.Polygon([ - [[0.45, 0.5], [0.45, 0.75], [0.55, 0.75], [0.55, 0.5], [0.45, 0.5]] -]) -p12 = LG.Polygon([ - [[0.4, 0.4], [0.4, 0.6], [0.6, 0.6], [0.6, 0.4], [0.4, 0.4]] -]) -# Test multipolygons -m1 = LG.MultiPolygon([p3, p6]) -m2 = LG.MultiPolygon([p3, p4]) - -# # Point and point -# Equal points -> within -@test GO.within(pt1, pt1) == LG.within(pt1, pt1) -# Different points -> not within -@test GO.within(pt1, pt2) == LG.within(pt1, pt2) - -# # Point and line -# Line endpoint (1 segment) -> not within -@test GO.within(pt1, l1) == LG.within(pt1, l1) -# Line endpoint (2 segments) -> not within -@test GO.within(pt2, l2) == LG.within(pt2, l2) -# Middle of line (1 segment) -> within -@test GO.within(pt2, l1) == LG.within(pt2, l1) -# Not on line (1 segment) -> not within -@test GO.within(pt3, l1) == LG.within(pt3, l1) -# Middle of line on joint (2 segments) -> within -@test GO.within(pt3, l2) == LG.within(pt3, l2) -# Endpoint on closed line -> within -@test GO.within(pt1, l6) == LG.within(pt1, l6) - -# # Point and Ring -# On ring corner -> within -@test GO.within(pt1, r1) == LG.within(pt1, r1) -# Outside of ring -> not within -@test GO.within(pt2, r1) == LG.within(pt2, r1) -# Inside of ring center (not on line) -> not within -@test GO.within(pt3, r1) == LG.within(pt3, r1) -# On ring edge -> within -@test GO.within(pt8, r1) == LG.within(pt8, r1) - -# # Point and polygon -# On polygon vertex -> not within -@test GO.within(pt1, p1) == LG.within(pt1, p1) -# Outside of polygon -> not within -@test GO.within(pt2, p1) == LG.within(pt2, p1) -# Inside of polygon -> within -@test GO.within(pt3, p1) == LG.within(pt3, p1) -# On polygon vertex (with holes) -> not within -@test GO.within(pt1, p2) == LG.within(pt1, p2) -# On polygon edge (with holes) -> not within -@test GO.within(pt2, p2) == LG.within(pt2, p2) -# On hole vertex -> not within -@test GO.within(pt5, p2) == LG.within(pt5, p2) -# Within hole -> not within -@test GO.within(pt6, p2) == LG.within(pt6, p2) -# Inside of polygon (with holes) -> within -@test GO.within(pt7, p2) == LG.within(pt7, p2) - -# # Line and line -# Equal lines -> within -@test GO.within(l1, l1) == LG.within(l1, l1) -# Lines share 2 endpoints, but don't overlap -> not within -@test GO.within(l1, l2) == LG.within(l1, l2) -# Lines overlap, but neither is within other -> not within -@test GO.within(l1, l3) == LG.within(l1, l3) -# Within line (no shared endpoints) -> within -@test GO.within(l1, l4) == LG.within(l1, l4) -# Line shares just one endpoint -> not within -@test GO.within(l1, l5) == LG.within(l1, l5) - -# # Line and ring -# Shares all endpoints -> within -@test GO.within(l6, r1) == LG.within(l6, r1) -# Shares all endpoints, but ring has extra edge -> within -@test GO.within(l2, r2) == LG.within(l2, r2) -# Doesn't share all edges -> not within -@test GO.within(l2, r3) == LG.within(l2, r3) -# Shares all endpoints, but adds one extra segment -> not within -@test GO.within(l12, r1) == LG.within(l12, r1) - -# Line and polygon -# Line traces entire outline of polygon edges -> not within -@test GO.within(l6, p1) == LG.within(l6, p1) -# Line is edge of polygon -> not within -@test GO.within(l1, p2) == LG.within(l1, p2) -# Line is on edge + inside of polygon -> within -@test GO.within(l2, p2) == LG.within(l2, p2) -# Line goes outside of polygon -> not within -@test GO.within(l3, p2) == LG.within(l3, p2) -# Line is fully within polygon -> within -@test GO.within(l7, p2) == LG.within(l7, p2) -# Line is fully within hole -> not within -@test GO.within(l8, p2) == LG.within(l8, p2) -# Line is on hole edge -> not within -@test GO.within(l9, p2) == LG.within(l9, p2) -# Line on polygon edge and then enters polygon to end on hole vertex -> within -@test GO.within(l10, p2) == LG.within(l10, p2) -# Line is on polygon edge and then cuts through hole -> not within -@test GO.within(l11, p2) == LG.within(l11, p2) - -# # Ring and line -# Shares all endpoints -> within -@test GO.within(r1, l6) == LG.within(r1, l6) -# Shares all endpoints but ring has closing edge -> not within -@test GO.within(r2, l2) == LG.within(r2, l2) -# Doesn't share all edges -> not within -@test GO.within(r3, l2) == LG.within(r3, l2) -# Shares all endpoints, but line has one extra segment -> within -@test GO.within(r1, l12) == LG.within(r1, l12) - -# # Ring and ring -# Equal ring -> within -@test GO.within(r1, r1) == LG.within(r1, r1) -# Not equal ring -> not within -@test GO.within(r1, r2) == LG.within(r1, r2) -# Not equal ring -> not within -@test GO.within(r1, r3) == LG.within(r1, r3) -# Rings share all edges, but second ring has extra edges -> within -@test GO.within(r2, r7) == LG.within(r2, r7) - -# # Ring and polygon -# Ring is equal to polygon's external ring, no holes -> not within -@test GO.within(r1, p1) == LG.within(r1, p1) -# Ring goes outside of polygon's external ring -> not within -@test GO.within(r1, p2) == LG.within(r1, p1) -# Ring is within polygon, but also on edges -> within -@test GO.within(r2, p2) == LG.within(r2, p2) -# Ring is within polygon, but also on edges -> within -@test GO.within(r3, p2) == LG.within(r3, p2) -# Ring is one of polygon's holes -> not within -@test GO.within(r4, p2) == LG.within(r4, p2) -# Ring is fully within polygon that has holes -> within -@test GO.within(r5, p2) == LG.within(r5, p2) -# Ring is fully within polygon's hole -> not within -@test GO.within(r6, p2) == LG.within(r6, p2) - -# # Polygon in polygon -# Same polygon -> within -@test GO.within(p1, p1) == LG.within(p1, p1) -@test GO.within(p2, p2) == LG.within(p2, p2) -# Polygon not in polygon -> not within -@test GO.within(p1, p2) == LG.within(p1, p2) -@test GO.within(p2, p1) == LG.within(p2, p1) -# Polygon is within polygon, but also on edges -> within -@test GO.within(p3, p2) == LG.within(p3, p2) -# Polygon within polygon with holes -> within -@test GO.within(p4, p2) == LG.within(p4, p2) -# Polygon within polygon hole --> not within -@test GO.within(p5, p2) == LG.within(p5, p2) -# Polygon overlapping with other polygon's hole -> not within -@test GO.within(p6, p2) == LG.within(p6, p2) -# Polygon with hole nested with other polygon's hole --> within -@test GO.within(p8, p7) == LG.within(p8, p7) -# Nested holes but not within -> not within -@test GO.within(p9, p7) == LG.within(p9, p7) -# Nested with same hole -> within -@test GO.within(p10, p7) == LG.within(p10, p7) -# within external ring but intersects with hole -> not within -@test GO.within(p11, p7) == LG.within(p11, p7) -# polygon extactly overlaps with other polygon's hole -> not within -@test GO.within(p12, p7) == LG.within(p12, p7) - -# # Multipolygon tests -# Point in multipolygon -@test GO.within(pt5, m1) == LG.within(pt5, m1) -@test GO.within(pt9, m1) == LG.within(pt9, m1) -# Point outside of multipolygon -@test GO.within(pt4, m1) == LG.within(pt4, m1) -# Line in multipolygon -@test GO.within(l13, m1) == LG.within(l13, m1) -@test GO.within(l9, m1) == LG.within(l9, m1) -# Line outside of multipolygon -@test GO.within(l1, m1) == LG.within(l1, m1) -# Ring in multipolygon -@test GO.within(r1, m2) == LG.within(r1, m2) -# Ring outside of multipolygon -@test GO.within(r1, m1) == LG.within(r1, m1) -# Polygon in multipolygon -@test GO.within(p3, m1) == LG.within(p3, m1) -@test GO.within(p6, m1) == LG.within(p6, m1) -# Polygon outside of multipolygon -@test GO.within(p1, m1) == LG.within(p1, m1) -# Multipolygon in multipolygon -@test GO.within(m1, m1) == LG.within(m1, m1) -# Multipolygon outside of multipolygon -@test GO.within(m2, m1) == LG.within(m2, m1) diff --git a/test/runtests.jl b/test/runtests.jl index 08d6b43c3..ce78237af 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,11 +17,11 @@ const GO = GeometryOps # Methods @testset "Barycentric coordinate operations" begin include("methods/barycentric.jl") end @testset "Centroid" begin include("methods/centroid.jl") end + @testset "Equals" begin include("methods/equals.jl") end @testset "Geometry Relations" begin include("methods/geom_relations.jl") end @testset "Bools" begin include("methods/bools.jl") end @testset "Signed Area" begin include("methods/signed_area.jl") end - # Transformations @testset "Reproject" begin include("transformations/reproject.jl") end @testset "Flip" begin include("transformations/flip.jl") end From d001c5f1513612cfcc2cd709ea56445d82947631 Mon Sep 17 00:00:00 2001 From: Skylar Gering Date: Tue, 2 Jan 2024 01:21:40 -0800 Subject: [PATCH 33/33] Fix bugs --- Project.toml | 2 - src/GeometryOps.jl | 6 +- src/methods/geom_relations/coveredby.jl | 4 +- src/methods/geom_relations/disjoint.jl | 4 +- .../geom_relations/geom_geom_processors.jl | 2 +- src/methods/geom_relations/within.jl | 4 +- test/methods/equals.jl | 277 ++++++++---------- test/methods/geom_relations.jl | 2 + 8 files changed, 137 insertions(+), 164 deletions(-) diff --git a/Project.toml b/Project.toml index 196b887ea..35b23ebe6 100644 --- a/Project.toml +++ b/Project.toml @@ -4,8 +4,6 @@ authors = ["Anshul Singhvi and contributors"] version = "0.0.1-DEV" [deps] -BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -Cthulhu = "f68482b8-f384-11e8-15f7-abe071a5a75f" ExactPredicates = "429591f6-91af-11e9-00e2-59fbe8cec110" GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f" GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" diff --git a/src/GeometryOps.jl b/src/GeometryOps.jl index c5b3a2eb7..95a33ed8e 100644 --- a/src/GeometryOps.jl +++ b/src/GeometryOps.jl @@ -23,18 +23,18 @@ include("methods/bools.jl") include("methods/distance.jl") include("methods/area.jl") include("methods/centroid.jl") -include("methods/geom_relations/intersects.jl") include("methods/geom_relations/contains.jl") -include("methods/geom_relations/covers.jl") include("methods/geom_relations/coveredby.jl") +include("methods/geom_relations/covers.jl") include("methods/geom_relations/crosses.jl") include("methods/geom_relations/disjoint.jl") +include("methods/geom_relations/geom_geom_processors.jl") +include("methods/geom_relations/intersects.jl") include("methods/geom_relations/overlaps.jl") include("methods/geom_relations/within.jl") include("methods/polygonize.jl") include("methods/barycentric.jl") include("methods/equals.jl") -include("methods/geom_relations/geom_geom_processors.jl") include("methods/orientation.jl") include("methods/geom_relations/touches.jl") include("transformations/extent.jl") diff --git a/src/methods/geom_relations/coveredby.jl b/src/methods/geom_relations/coveredby.jl index 7fd48f679..45223f122 100644 --- a/src/methods/geom_relations/coveredby.jl +++ b/src/methods/geom_relations/coveredby.jl @@ -99,7 +99,7 @@ _coveredby( ) = _point_curve_process( g1, g2; COVEREDBY_ALLOWS..., - repeated_last_coord = false, + closed_curve = false, ) # Point is coveredby a linearring if it is on a vertex or an edge of ring @@ -109,7 +109,7 @@ _coveredby( ) = _point_curve_process( g1, g2; COVEREDBY_ALLOWS..., - repeated_last_coord = true, + closed_curve = true, ) # Point is coveredby a polygon if it is inside polygon, including edges/vertices diff --git a/src/methods/geom_relations/disjoint.jl b/src/methods/geom_relations/disjoint.jl index 50cee76a1..f3e051c05 100644 --- a/src/methods/geom_relations/disjoint.jl +++ b/src/methods/geom_relations/disjoint.jl @@ -92,7 +92,7 @@ _disjoint( ) = _point_curve_process( g1, g2; DISJOINT_ALLOWS..., - repeated_last_coord = false, + closed_curve = false, ) # Point is disjoint from a linearring if it is not on the ring's edges/vertices. @@ -102,7 +102,7 @@ _disjoint( ) = _point_curve_process( g1, g2; DISJOINT_ALLOWS..., - repeated_last_coord = true, + closed_curve = true, ) #= Point is disjoint from a polygon if it is not on any edges, vertices, or diff --git a/src/methods/geom_relations/geom_geom_processors.jl b/src/methods/geom_relations/geom_geom_processors.jl index ef3ae70c9..3068f023f 100644 --- a/src/methods/geom_relations/geom_geom_processors.jl +++ b/src/methods/geom_relations/geom_geom_processors.jl @@ -669,7 +669,7 @@ function _line_filled_curve_interactions( curve ) npoints = length(ipoints) # since hinge, at least one - sort!(ipoints, by = p -> euclid_distance(p, l_start)) + sort!(ipoints, by = p -> _euclid_distance(Float64, p, l_start)) p_start = _tuple_point(l_start) for i in 1:(npoints + 1) p_end = i ≤ npoints ? diff --git a/src/methods/geom_relations/within.jl b/src/methods/geom_relations/within.jl index bcccdac34..9b28c0ee7 100644 --- a/src/methods/geom_relations/within.jl +++ b/src/methods/geom_relations/within.jl @@ -101,7 +101,7 @@ _within( ) = _point_curve_process( g1, g2; WITHIN_POINT_ALLOWS..., - repeated_last_coord = false, + closed_curve = false, ) # Point is within a linearring if it is on a vertex or an edge of that ring. @@ -111,7 +111,7 @@ _within( ) = _point_curve_process( g1, g2; WITHIN_POINT_ALLOWS..., - repeated_last_coord = true, + closed_curve = true, ) #= Point is within a polygon if it is inside of that polygon, excluding edges, diff --git a/test/methods/equals.jl b/test/methods/equals.jl index f7fd257d0..f24f54908 100644 --- a/test/methods/equals.jl +++ b/test/methods/equals.jl @@ -1,160 +1,133 @@ # # Point and Geometry -# Same point --> equal -@test GO.equals(pt1, pt1) == LG.equals(pt1, pt1) == true -# Different point --> not equal -@test GO.equals(pt1, pt2) == LG.equals(pt1, pt2) == false -# Point cannot equal line -> not equal -@test GO.equals(pt3, l1) == LG.equals(pt3, l1) == false -# Line cannot equal point -> not equal -@test GO.equals(l1, pt3) == LG.equals(l1, pt3) == false -# Point cannot equal ring -> not equal -@test GO.equals(pt3, r1) == LG.equals(pt3, r1) == false -# Ring cannot equal point -> not equal -@test GO.equals(r1, pt3) == LG.equals(r1, pt3) == false -# Point cannot equal polygon -> not equal -@test GO.equals(p1, pt3) == LG.equals(p1, pt3) == false -# Polygon cannot equal point -> not equal -@test GO.equals(pt3, p1) == LG.equals(pt3, p1) == false +@testset "Points/MultiPoints" begin + p1 = LG.Point([0.0, 0.0]) + p2 = LG.Point([0.0, 1.0]) + # Same points + @test GO.equals(p1, p1) == LG.equals(p1, p1) + @test GO.equals(p2, p2) == LG.equals(p2, p2) + # Different points + @test GO.equals(p1, p2) == LG.equals(p1, p2) -# # Line and Geometry -# Same line -> equals -@test GO.equals(l1, l1) == LG.equals(l1, l1) == true -# Line overlaps line edge and endpoint (nothing exterior) -> not equal -@test GO.equals(l1, l2) == LG.equals(l1, l2) == false -# Line segments both within other line segments -> doesn't touch -@test GO.equals(l1, l4) == LG.equals(l1, l4) == false + mp1 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0]]) + mp2 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0], [3.0, 3.0]]) + mp3 = LG.MultiPoint([p2]) + # Same points + @test GO.equals(mp1, mp1) == LG.equals(mp1, mp1) + @test GO.equals(mp2, mp2) == LG.equals(mp2, mp2) + # Different points + @test GO.equals(mp1, mp2) == LG.equals(mp1, mp2) + @test GO.equals(mp1, p1) == LG.equals(mp1, p1) + # Point and multipoint + @test GO.equals(p2, mp3) == LG.equals(p2, mp3) +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]]) + # Equal lines + @test GO.equals(l1, l1) == LG.equals(l1, l1) + @test GO.equals(l2, l2) == LG.equals(l2, l2) + # Different lines + @test GO.equals(l1, l2) == GO.equals(l2, l1) == LG.equals(l1, l2) + r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) + r2 = LG.LinearRing([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) + r3 = GI.LinearRing([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0]]) + l3 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) + # Equal rings + @test GO.equals(r1, r1) == LG.equals(r1, r1) + @test GO.equals(r2, r2) == LG.equals(r2, r2) + # Test equal rings without closing point + @test GO.equals(r2, r3) + @test GO.equals(r3, l3) + # Different rings + @test GO.equals(r1, r2) == GO.equals(r2, r1) == LG.equals(r1, r2) + # Equal linear ring and line string + @test GO.equals(r2, l3) == LG.equals(r2, l3) + # Equal line string and line + @test GO.equals(l1, GI.Line([(0.0, 0.0), (0.0, 10.0)])) +end -# @testset "Points/MultiPoints" begin -# p1 = LG.Point([0.0, 0.0]) -# p2 = LG.Point([0.0, 1.0]) -# # Same points -# @test GO.equals(p1, p1) == LG.equals(p1, p1) -# @test GO.equals(p2, p2) == LG.equals(p2, p2) -# # Different points -# @test GO.equals(p1, p2) == LG.equals(p1, p2) +@testset "Polygons/MultiPolygons" begin + pt1 = LG.Point([0.0, 0.0]) + r1 = GI.LinearRing([(0, 0), (0, 5), (5, 5), (5, 0), (0, 0)]) + p1 = GI.Polygon([[(0, 0), (0, 5), (5, 5), (5, 0), (0, 0)]]) + p2 = GI.Polygon([[(1, 1), (1, 6), (6, 6), (6, 1), (1, 1)]]) + p3 = 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]] + ] + ) + p4 = LG.Polygon( + [ + [[10.0, 0.0], [10.0, 20.0], [30.0, 20.0], [30.0, 0.0], [10.0, 0.0]], + [[16.0, 1.0], [16.0, 11.0], [25.0, 11.0], [25.0, 1.0], [16.0, 1.0]] + ] + ) + p5 = 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]], + [[11.0, 1.0], [11.0, 2.0], [12.0, 2.0], [12.0, 1.0], [11.0, 1.0]] + ] + ) + p6 = GI.Polygon([[(6, 6), (6, 1), (1, 1), (1, 6), (6, 6)]]) + p7 = GI.Polygon([[(6, 6), (1, 6), (1, 1), (6, 1), (6, 6)]]) + p8 = GI.Polygon([[(6, 6), (1, 6), (1, 1), (6, 1)]]) + # Point and polygon aren't equal + GO.equals(pt1, p1) == LG.equals(pt1, p1) + # Linear ring and polygon aren't equal + @test GO.equals(r1, p1) == LG.equals(r1, p1) + # Equal polygon + @test GO.equals(p1, p1) == LG.equals(p1, p1) + @test GO.equals(p2, p2) == LG.equals(p2, p2) + # Equal but offset polygons + @test GO.equals(p2, p6) == LG.equals(p2, p6) + # Equal but opposite winding orders + @test GO.equals(p2, p7) == LG.equals(p2, p7) + # Equal but without closing point (implied) + @test GO.equals(p7, p8) + # Different polygons + @test GO.equals(p1, p2) == LG.equals(p1, p2) + # Equal polygons with holes + @test GO.equals(p3, p3) == LG.equals(p3, p3) + # Same exterior, different hole + @test GO.equals(p3, p4) == LG.equals(p3, p4) + # Same exterior and first hole, has an extra hole + @test GO.equals(p3, p5) == LG.equals(p3, p5) -# mp1 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0]]) -# mp2 = LG.MultiPoint([[0.0, 1.0], [2.0, 2.0], [3.0, 3.0]]) -# mp3 = LG.MultiPoint([p2]) -# # Same points -# @test GO.equals(mp1, mp1) == LG.equals(mp1, mp1) -# @test GO.equals(mp2, mp2) == LG.equals(mp2, mp2) -# # Different points -# @test GO.equals(mp1, mp2) == LG.equals(mp1, mp2) -# @test GO.equals(mp1, p1) == LG.equals(mp1, p1) -# # Point and multipoint -# @test GO.equals(p2, mp3) == LG.equals(p2, mp3) -# end + p9 = LG.Polygon( + [[ + [-53.57208251953125, 28.287451910503744], + [-53.33038330078125, 28.29228897739706], + [-53.34136962890625, 28.430052892335723], + [-53.57208251953125, 28.287451910503744], + ]] + ) + # Complex polygon + @test GO.equals(p9, p9) == LG.equals(p9, p9) -# @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]]) -# # Equal lines -# @test GO.equals(l1, l1) == LG.equals(l1, l1) -# @test GO.equals(l2, l2) == LG.equals(l2, l2) -# # Different lines -# @test GO.equals(l1, l2) == GO.equals(l2, l1) == LG.equals(l1, l2) - -# r1 = LG.LinearRing([[0.0, 0.0], [5.0, 5.0], [10.0, 0.0], [5.0, -5.0], [0.0, 0.0]]) -# r2 = LG.LinearRing([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) -# r3 = GI.LinearRing([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0]]) -# l3 = LG.LineString([[3.0, 0.0], [8.0, 5.0], [13.0, 0.0], [8.0, -5.0], [3.0, 0.0]]) -# # Equal rings -# @test GO.equals(r1, r1) == LG.equals(r1, r1) -# @test GO.equals(r2, r2) == LG.equals(r2, r2) -# # Test equal rings without closing point -# @test GO.equals(r2, r3) -# @test GO.equals(r3, l3) -# # Different rings -# @test GO.equals(r1, r2) == GO.equals(r2, r1) == LG.equals(r1, r2) -# # Equal linear ring and line string -# @test GO.equals(r2, l3) == LG.equals(r2, l3) -# # Equal line string and line -# @test GO.equals(l1, GI.Line([(0.0, 0.0), (0.0, 10.0)])) -# end - -# @testset "Polygons/MultiPolygons" begin -# pt1 = LG.Point([0.0, 0.0]) -# r1 = GI.LinearRing([(0, 0), (0, 5), (5, 5), (5, 0), (0, 0)]) -# p1 = GI.Polygon([[(0, 0), (0, 5), (5, 5), (5, 0), (0, 0)]]) -# p2 = GI.Polygon([[(1, 1), (1, 6), (6, 6), (6, 1), (1, 1)]]) -# p3 = 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]] -# ] -# ) -# p4 = LG.Polygon( -# [ -# [[10.0, 0.0], [10.0, 20.0], [30.0, 20.0], [30.0, 0.0], [10.0, 0.0]], -# [[16.0, 1.0], [16.0, 11.0], [25.0, 11.0], [25.0, 1.0], [16.0, 1.0]] -# ] -# ) -# p5 = 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]], -# [[11.0, 1.0], [11.0, 2.0], [12.0, 2.0], [12.0, 1.0], [11.0, 1.0]] -# ] -# ) -# p6 = GI.Polygon([[(6, 6), (6, 1), (1, 1), (1, 6), (6, 6)]]) -# p7 = GI.Polygon([[(6, 6), (1, 6), (1, 1), (6, 1), (6, 6)]]) -# p8 = GI.Polygon([[(6, 6), (1, 6), (1, 1), (6, 1)]]) -# # Point and polygon aren't equal -# GO.equals(pt1, p1) == LG.equals(pt1, p1) -# # Linear ring and polygon aren't equal -# @test GO.equals(r1, p1) == LG.equals(r1, p1) -# # Equal polygon -# @test GO.equals(p1, p1) == LG.equals(p1, p1) -# @test GO.equals(p2, p2) == LG.equals(p2, p2) -# # Equal but offset polygons -# @test GO.equals(p2, p6) == LG.equals(p2, p6) -# # Equal but opposite winding orders -# @test GO.equals(p2, p7) == LG.equals(p2, p7) -# # Equal but without closing point (implied) -# @test GO.equals(p7, p8) -# # Different polygons -# @test GO.equals(p1, p2) == LG.equals(p1, p2) -# # Equal polygons with holes -# @test GO.equals(p3, p3) == LG.equals(p3, p3) -# # Same exterior, different hole -# @test GO.equals(p3, p4) == LG.equals(p3, p4) -# # Same exterior and first hole, has an extra hole -# @test GO.equals(p3, p5) == LG.equals(p3, p5) - -# p9 = LG.Polygon( -# [[ -# [-53.57208251953125, 28.287451910503744], -# [-53.33038330078125, 28.29228897739706], -# [-53.34136962890625, 28.430052892335723], -# [-53.57208251953125, 28.287451910503744], -# ]] -# ) -# # Complex polygon -# @test GO.equals(p9, p9) == LG.equals(p9, p9) - -# 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]] -# ] -# ]) -# m2 = LG.MultiPolygon([ -# [ -# [[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]] -# ], -# [[[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0], [0.0, 0.0]]] -# ]) -# # Equal multipolygon -# @test GO.equals(m1, m1) == LG.equals(m1, m1) -# # Equal multipolygon with different order -# @test GO.equals(m2, m2) == LG.equals(m2, m2) -# # Equal polygon to multipolygon -# m3 = LG.MultiPolygon([p3]) -# @test GO.equals(p1, m3) == LG.equals(p1, m3) -# end \ No newline at end of file + 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]] + ] + ]) + m2 = LG.MultiPolygon([ + [ + [[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]] + ], + [[[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0], [0.0, 0.0]]] + ]) + # Equal multipolygon + @test GO.equals(m1, m1) == LG.equals(m1, m1) + # Equal multipolygon with different order + @test GO.equals(m2, m2) == LG.equals(m2, m2) + # Equal polygon to multipolygon + m3 = LG.MultiPolygon([p3]) + @test GO.equals(p1, m3) == LG.equals(p1, m3) +end \ No newline at end of file diff --git a/test/methods/geom_relations.jl b/test/methods/geom_relations.jl index 9ff1f003f..3fa793913 100644 --- a/test/methods/geom_relations.jl +++ b/test/methods/geom_relations.jl @@ -1,3 +1,5 @@ +# Tests of DE-9IM Methods + pt1 = LG.Point([0.0, 0.0]) pt2 = LG.Point([5.0, 5.0]) pt3 = LG.Point([1.0, 0.0])