Skip to content

Commit df222e8

Browse files
authored
Convert the orient predicate to AdaptivePredicates + minor predicate changes (#275)
- Add AdaptivePredicates as the exact backend for orient. At some point we should switch to an ecosystem wide (DelaunayTriangulation + GeometryOps + ...) representation for predicate kernels (Exact, Adaptive, Auto (choose adaptive if available and exact if not), and Fast). - Fix touches for multi geometries https://github.com/JuliaGeometry/AdaptivePredicates.jl is a library that implements adaptive predicates - substantially faster, BUT harder to construct, than exact predicates. Adaptive predicates are valid through the range of coordinates that geometries usually have, but we may need exact predicates if they are not. * Convert the orient predicate to AdaptivePredicates https://github.com/JuliaGeometry/AdaptivePredicates.jl is a library that implements adaptive predicates - substantially faster, BUT harder to construct, than exact predicates. Adaptive predicates are valid through the range of coordinates that geometries usually have, but we may need exact predicates if they are not. * Use the orient predicate, not cross, in ggp This should increase speed although we will have to benchmark. Still, at least it's benchmarkable now. * Fix touches for multi geoms Only one of the geoms in g2 MUST touch the geom in g1, all others MUST be disjoint or touching. This could potentially be accelerated by building an STRtree if the query width is long enough, or we may also need some tree interface / preparation here. E.g. Canada has a lot of islands that a tree approach could get rid of.
1 parent 5c26fd0 commit df222e8

File tree

4 files changed

+51
-9
lines changed

4 files changed

+51
-9
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ authors = ["Anshul Singhvi <[email protected]>", "Rafael Schouten <rafaels
44
version = "0.1.17"
55

66
[deps]
7+
AdaptivePredicates = "35492f91-a3bd-45ad-95db-fcad7dcfedb7"
78
CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298"
89
DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
910
DelaunayTriangulation = "927a84f5-c5f4-47a5-9785-b46e178433df"
@@ -31,6 +32,7 @@ GeometryOpsProjExt = "Proj"
3132
GeometryOpsTGGeometryExt = "TGGeometry"
3233

3334
[compat]
35+
AdaptivePredicates = "1.2"
3436
CoordinateTransformations = "0.5, 0.6"
3537
DataAPI = "1"
3638
DelaunayTriangulation = "1.0.4"

src/methods/clipping/predicates.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ module Predicates
44
import ExactPredicates.Codegen: group!, @genpredicate
55
import GeometryOps: False, True, booltype, _tuple_point
66
import GeoInterface as GI
7+
import AdaptivePredicates
78

89
#= Determine the orientation of c with regards to the oriented segment (a, b).
910
Return 1 if c is to the left of (a, b).
1011
Return -1 if c is to the right of (a, b).
1112
Return 0 if c is on (a, b) or if a == b. =#
12-
orient(a, b, c; exact) = _orient(booltype(exact), a, b, c)
13+
orient(a, b, c; exact) = _orient(booltype(exact), _tuple_point(a, Float64), _tuple_point(b, Float64), _tuple_point(c, Float64))
1314

1415
# If `exact` is `true`, use `ExactPredicates` to calculate the orientation.
15-
_orient(::True, a, b, c) = ExactPredicates.orient(_tuple_point(a, Float64), _tuple_point(b, Float64), _tuple_point(c, Float64))
16+
_orient(::True, a, b, c) = AdaptivePredicates.orient2p(_tuple_point(a, Float64), _tuple_point(b, Float64), _tuple_point(c, Float64))
17+
# _orient(::True, a, b, c) = ExactPredicates.orient(_tuple_point(a, Float64), _tuple_point(b, Float64), _tuple_point(c, Float64))
1618
# If `exact` is `false`, calculate the orientation without using `ExactPredicates`.
1719
function _orient(exact::False, a, b, c)
1820
a = a .- c

src/methods/geom_relations/geom_geom_processors.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ function _point_filled_curve_orientation(
505505
v2 = GI.y(p_end) - y
506506
if !((v1 < 0 && v2 < 0) || (v1 > 0 && v2 > 0)) # if not cases 11 or 26
507507
u1, u2 = GI.x(p_start) - x, GI.x(p_end) - x
508-
f = Predicates.cross((u1, u2), (v1, v2); exact)
508+
f = Predicates.orient(p_start, p_end, (x, y); exact)
509509
if v2 > 0 && v1 0 # Case 3, 9, 16, 21, 13, or 24
510510
f == 0 && return on # Case 16 or 21
511511
f > 0 && (k += 1) # Case 3 or 9

src/methods/geom_relations/touches.jl

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,19 +225,49 @@ _touches(
225225

226226
# # Geometries touch multi-geometry/geometry collections
227227

228-
#= Geometry touch a multi-geometry or a collection if the geometry touches at
229-
least one of the elements of the collection. =#
228+
#=
229+
230+
A geometry touches a multi-geometry or a collection if the geometry touches at
231+
least one of the elements of the collection.
232+
233+
This is a bit tricky to implement - we have to actually check every geometry,
234+
and make sure that each geom is either disjoint or touching.
235+
236+
Problem here is that we would end up doing double the work.
237+
238+
Either you check disjointness first, and then check touches - in which case
239+
you have already done the work for the touches check, but can't take advantage of it.
240+
241+
Or you check touches first, and if that is false, you check disjointness. But if touches failed,
242+
and you don't know _why_ it was false (disjoint or contained / intersecting), you have to iterate
243+
over every point twice -- again!
244+
245+
246+
At this point we actually need a fast return function...or some more detail returned from the process functions.
247+
248+
That's a project for later though. Right now we need to get this correct, so I'm going to do the dumb thing.
249+
250+
=#
230251
function _touches(
231252
::Union{GI.PointTrait, GI.AbstractCurveTrait, GI.PolygonTrait}, g1,
232253
::Union{
233254
GI.MultiPointTrait, GI.AbstractMultiCurveTrait,
234255
GI.MultiPolygonTrait, GI.GeometryCollectionTrait,
235256
}, g2,
236257
)
258+
has_touched = false
237259
for sub_g2 in GI.getgeom(g2)
238-
!touches(g1, sub_g2) && return false
260+
if touches(g1, sub_g2)
261+
has_touched = true
262+
else
263+
# if not touching, they are either intersecting or disjoint
264+
# if disjoint, then we can continue
265+
# else, we can short circuit, since the geoms are not touching and not disjoint
266+
# i.e. they are intersecting
267+
disjoint(g1, sub_g2) || return false
268+
end
239269
end
240-
return true
270+
return has_touched
241271
end
242272

243273
# # Multi-geometry/geometry collections cross geometries
@@ -251,8 +281,16 @@ function _touches(
251281
}, g1,
252282
::GI.AbstractGeometryTrait, g2,
253283
)
284+
has_touched = false
254285
for sub_g1 in GI.getgeom(g1)
255-
!touches(sub_g1, g2) && return false
286+
if touches(sub_g1, g2)
287+
has_touched = true
288+
else
289+
# if not touching, they are either intersecting or disjoint
290+
# if disjoint, then we can continue
291+
# else, we can short circuit, since the geoms are not touching and not disjoint
292+
disjoint(sub_g1, g2) || return false
293+
end
256294
end
257-
return true
295+
return has_touched
258296
end

0 commit comments

Comments
 (0)