-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement a
GEOS
algorithm in an extension (#100)
* Move types to a new `types.jl` file so they are more accessible and general * Add GEOSCorrection and LibGEOSExt * Minor fixes * Write out stubs for not-yet-implemented functions * Implement `GEOS` algorithm * Update ext/GeometryOpsLibGEOSExt/segmentize.jl * switch from typed keys to no keys * fix erroneous typing in buffer * add test skeleton + move enforce to GO proper * Switch to SafeTestsets for tests This is meant to assure that conflicts in e.g. polygon definitions are avoided, and that tests are immediately runnable as files. * Test segmentize properly * Apply suggestions from code review * Fix tests * Allow LibGEOS to perform natural conversion on DE-9IM and poly set ops * Describe exactly why the filter statement exists in the extension * Remove GEOS correction (not working yet) * Add TopologyPreserve simplification method * Add a basic comment to not_implemented_yet.jl * Add a "default" implementation for buffer that returns GO geoms * Switch back to explicit conversion to LG in all simple overrides * Fix incorrect references * Add Downloads and NaturalEarth to the test env * Fix primitive tests * Add a hack for LibGEOS geometrycollection conversion * Fix accidental swapping in x and y in test * Add overlaps testing for GEOS * Bump version to v0.1.7 (#150) * Set compat for LibGEOS to be after the GeometryCollection patch * Force `simplify` to always be called with GEOS() for GEOS functions * Go back to regular testsets from SafeTestsets * Revert the geom relations tests for #135 * Get simplify working as well * Activate the LibGEOS listed tests * Fix the segmentize test * Fix buffer
- Loading branch information
1 parent
ec6090e
commit 152943a
Showing
34 changed files
with
469 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
module GeometryOpsLibGEOSExt | ||
|
||
import GeometryOps as GO, LibGEOS as LG | ||
import GeometryOps: GI | ||
|
||
import GeometryOps: GEOS, enforce | ||
|
||
using GeometryOps | ||
# The filter statement is required because in Julia, each module has its own versions of these | ||
# functions, which serve to evaluate or include code inside the scope of the module. | ||
# However, if you import those from another module (which you would with `all=true`), | ||
# that creates an ambiguity which causes a warning during precompile/load time. | ||
# In order to avoid this, we filter out these special functions. | ||
for name in filter(!in((:var"#eval", :eval, :var"#include", :include)), names(GeometryOps; all = true)) | ||
@eval using GeometryOps: $name | ||
end | ||
|
||
include("buffer.jl") | ||
include("segmentize.jl") | ||
include("simplify.jl") | ||
|
||
include("simple_overrides.jl") | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
const _GEOS_CAPSTYLE_LOOKUP = Dict{Symbol, LG.GEOSBufCapStyles}( | ||
:round => LG.GEOSBUF_CAP_ROUND, | ||
:flat => LG.GEOSBUF_CAP_FLAT, | ||
:square => LG.GEOSBUF_CAP_SQUARE, | ||
) | ||
|
||
const _GEOS_JOINSTYLE_LOOKUP = Dict{Symbol, LG.GEOSBufJoinStyles}( | ||
:round => LG.GEOSBUF_JOIN_ROUND, | ||
:mitre => LG.GEOSBUF_JOIN_MITRE, | ||
:bevel => LG.GEOSBUF_JOIN_BEVEL, | ||
) | ||
|
||
to_cap_style(style::Symbol) = _GEOS_CAPSTYLE_LOOKUP[style] | ||
to_cap_style(style::LG.GEOSBufCapStyles) = style | ||
to_cap_style(num::Integer) = num | ||
|
||
to_join_style(style::Symbol) = _GEOS_JOINSTYLE_LOOKUP[style] | ||
to_join_style(style::LG.GEOSBufJoinStyles) = style | ||
to_join_style(num::Integer) = num | ||
|
||
function GO.buffer(alg::GEOS, geometry, distance) | ||
# The reason we use apply here is so that this also works with featurecollections, | ||
# tables, vectors of geometries, etc! | ||
return apply(TraitTarget{GI.AbstractGeometryTrait}(), geometry) do geom | ||
LG.bufferWithStyle( | ||
GI.convert(LG, geom), distance; | ||
quadsegs = get(alg, :quadsegs, 8), | ||
endCapStyle = to_cap_style(get(alg, :endCapStyle, :round)), | ||
joinStyle = to_join_style(get(alg, :joinStyle, :round)), | ||
mitreLimit = get(alg, :mitreLimit, 5.0), | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# # Segmentize | ||
import GeometryOps: segmentize, apply | ||
|
||
#= | ||
This file implements the LibGEOS segmentization method for GeometryOps. | ||
=# | ||
|
||
function _segmentize_geos(geom::LG.AbstractGeometry, max_distance) | ||
context = LG.get_context(geom) | ||
result = LG.GEOSDensify_r(context, geom, max_distance) | ||
if result == C_NULL | ||
error("LibGEOS: Error in GEOSDensify") | ||
end | ||
return LG.geomFromGEOS(result, context) | ||
end | ||
|
||
_segmentize_geos(geom, max_distance) = _segmentize_geos(GI.convert(LG, geom), max_distance) | ||
|
||
# 2 behaviours: | ||
# - enforce: enforce the presence of a kwargs | ||
# - fetch: fetch the value of a kwargs, or return a default value | ||
@inline function GO.segmentize(alg::GEOS, geom; threaded::Union{Bool, GO.BoolsAsTypes} = _False()) | ||
max_distance = enforce(alg, :max_distance, GO.segmentize) | ||
return GO.apply( | ||
Base.Fix2(_segmentize_geos, max_distance), | ||
GO.TraitTarget(GI.GeometryCollectionTrait(), GI.MultiPolygonTrait(), GI.PolygonTrait(), GI.MultiLineStringTrait(), GI.LineStringTrait(), GI.LinearRingTrait(), GI.MultiPointTrait(), GI.PointTrait()), | ||
geom; | ||
threaded | ||
) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
#= | ||
# Simple overrides | ||
This file contains simple overrides for GEOS, essentially only those | ||
functions which have direct counterparts in LG and only | ||
require conversion before calling. | ||
=# | ||
# ## Polygon set operations | ||
# ### Difference | ||
function GO.difference(::GEOS, geom_a, geom_b; target=nothing) | ||
return LG.difference(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Union | ||
function GO.union(::GEOS, geom_a, geom_b; target=nothing) | ||
return LG.union(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Intersection | ||
function GO.intersection(::GEOS, geom_a, geom_b; target=nothing) | ||
return LG.intersection(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Symmetric difference | ||
function GO.symdifference(::GEOS, geom_a, geom_b; target=nothing) | ||
return LG.symmetric_difference(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
|
||
# ## DE-9IM boolean methods | ||
# ### Equals | ||
function GO.equals(::GEOS, geom_a, geom_b) | ||
return LG.equals(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Disjoint | ||
function GO.disjoint(::GEOS, geom_a, geom_b) | ||
return LG.disjoint(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Touches | ||
function GO.touches(::GEOS, geom_a, geom_b) | ||
return LG.touches(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Crosses | ||
function GO.crosses(::GEOS, geom_a, geom_b) | ||
return LG.crosses(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Within | ||
function GO.within(::GEOS, geom_a, geom_b) | ||
return LG.within(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Contains | ||
function GO.contains(::GEOS, geom_a, geom_b) | ||
return LG.contains(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Overlaps | ||
function GO.overlaps(::GEOS, geom_a, geom_b) | ||
return LG.overlaps(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Covers | ||
function GO.covers(::GEOS, geom_a, geom_b) | ||
return LG.covers(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### CoveredBy | ||
function GO.coveredby(::GEOS, geom_a, geom_b) | ||
return LG.coveredby(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
# ### Intersects | ||
function GO.intersects(::GEOS, geom_a, geom_b) | ||
return LG.intersects(GI.convert(LG, geom_a), GI.convert(LG, geom_b)) | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Address potential ambiguities | ||
GO._simplify(::GI.PointTrait, ::GO.GEOS, geom; kw...) = geom | ||
GO._simplify(::GI.MultiPointTrait, ::GO.GEOS, geom; kw...) = geom | ||
|
||
function GO._simplify(::GI.AbstractGeometryTrait, alg::GO.GEOS, geom; kwargs...) | ||
method = get(alg, :method, :TopologyPreserve) | ||
@assert haskey(alg.params, :tol) """ | ||
The `:tol` parameter is required for the GEOS algorithm in `simplify`, | ||
but it was not provided. | ||
Provide it by passing `GEOS(; tol = ...,) as the algorithm. | ||
""" | ||
tol = alg.params.tol | ||
if method == :TopologyPreserve | ||
return LG.topologyPreserveSimplify(GI.convert(LG, geom), tol) | ||
elseif method == :DouglasPeucker | ||
return LG.simplify(GI.convert(LG, geom), tol) | ||
else | ||
error("Invalid method passed to `GO.simplify(GEOS(...), ...)`: $method. Please use :TopologyPreserve or :DouglasPeucker") | ||
end | ||
end | ||
|
||
function GO._simplify(trait::GI.AbstractCurveTrait, alg::GO.GEOS, geom; kw...) | ||
Base.invoke( | ||
GO._simplify, | ||
Tuple{GI.AbstractGeometryTrait, GO.GEOS, typeof(geom)}, | ||
trait, alg, geom; | ||
kw... | ||
) | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#= | ||
# Buffer | ||
Buffering a geometry means computing the region `distance` away from it, and returning | ||
that region as the new geometry. | ||
As of now, we only support `GEOS` as the backend, meaning that LibGEOS must be loaded. | ||
=# | ||
|
||
function buffer(geometry, distance; kwargs...) | ||
buffered = buffer(GEOS(; kwargs...), geometry, distance) | ||
return tuples(buffered) | ||
end | ||
|
||
# Below is an error handler similar to the others we have for e.g. segmentize, | ||
# which checks if there is a method error for the geos backend. | ||
|
||
|
||
# Add an error hint for GeodesicSegments if Proj is not loaded! | ||
function _buffer_error_hinter(io, exc, argtypes, kwargs) | ||
if isnothing(Base.get_extension(GeometryOps, :GeometryOpsLibGEOSExt)) && exc.f == buffer && first(exc.argtypes) == GEOS | ||
print(io, "\n\nThe `buffer` method requires the LibGEOS.jl package to be explicitly loaded.\n") | ||
print(io, "You can do this by simply typing ") | ||
printstyled(io, "using LibGEOS"; color = :cyan, bold = true) | ||
println(io, " in your REPL, \nor otherwise loading LibGEOS.jl via using or import.") | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# # Not implemented yet | ||
# All of the functions in this file are not implemented in Julia yet. | ||
# Some of them may have implementations in LibGEOS which we can use | ||
# via an extension, but there is no native-Julia implementation for them. | ||
|
||
function symdifference end | ||
function buffer end | ||
function convexhull end | ||
function concavehull end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
152943a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JuliaRegistrator register
152943a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Registration pull request created: JuliaRegistries/General/108729
Tip: Release Notes
Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.
To add them here just re-invoke and the PR will be updated.
Tagging
After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.
This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via: