Skip to content

Commit 2c95b27

Browse files
authored
Use GeometryOpsCore for real (#223)
2 parents 6b50e2a + e6e2791 commit 2c95b27

36 files changed

+298
-122
lines changed

.github/workflows/CI.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
version:
2121
- '1.9'
2222
- '1'
23-
# - 'nightly'
23+
- 'nightly'
2424
os:
2525
- ubuntu-latest
2626
arch:
@@ -33,6 +33,8 @@ jobs:
3333
version: ${{ matrix.version }}
3434
arch: ${{ matrix.arch }}
3535
- uses: julia-actions/cache@v1
36+
- name: Dev GeometryOpsCore`
37+
run: julia --project=. -e 'using Pkg; Pkg.develop(; path = joinpath(".", "GeometryOpsCore"))'
3638
- uses: julia-actions/julia-buildpkg@v1
3739
- uses: julia-actions/julia-runtest@v1
3840
- uses: julia-actions/julia-processcoverage@v1
@@ -58,7 +60,7 @@ jobs:
5860
with:
5961
version: '1'
6062
- name: Build and add versions
61-
run: julia --project=docs -e 'using Pkg; Pkg.add([PackageSpec(path = "."), PackageSpec(name = "GeoMakie", rev = "master")])'
63+
run: julia --project=docs -e 'using Pkg; Pkg.develop([PackageSpec(path = "."), PackageSpec(path = joinpath(".", "GeometryOpsCore"))]); Pkg.add([PackageSpec(name = "GeoMakie", rev = "master"), PackageSpec(name="GeoInterface", rev="bugfix_vars")])'
6264
- uses: julia-actions/julia-docdeploy@v1
6365
with:
6466
install-package: false

GeometryOpsCore/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "GeometryOpsCore"
22
uuid = "05efe853-fabf-41c8-927e-7063c8b9f013"
33
authors = ["Anshul Singhvi <[email protected]>", "Rafael Schouten <[email protected]>", "Skylar Gering <[email protected]>", "and contributors"]
4-
version = "0.1.1"
4+
version = "0.1.2"
55

66
[deps]
77
DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"

GeometryOpsCore/src/apply.jl

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ end
126126
@inline function apply(
127127
f::F, target, geom; calc_extent=false, threaded=false, kw...
128128
) where F
129-
threaded = _booltype(threaded)
130-
calc_extent = _booltype(calc_extent)
129+
threaded = booltype(threaded)
130+
calc_extent = booltype(calc_extent)
131131
_apply(f, TraitTarget(target), geom; threaded, calc_extent, kw...)
132132
end
133133

@@ -139,7 +139,7 @@ end
139139
# For an Array there is nothing else to do but map `_apply` over all values
140140
# _maptasks may run this level threaded if `threaded==true`,
141141
# but deeper `_apply` called in the closure will not be threaded
142-
apply_to_array(i) = _apply(f, target, A[i]; threaded=_False(), kw...)
142+
apply_to_array(i) = _apply(f, target, A[i]; threaded=False(), kw...)
143143
_maptasks(apply_to_array, eachindex(A), threaded)
144144
end
145145
# There is no trait and this is not an AbstractArray.
@@ -150,7 +150,7 @@ end
150150
if Tables.istable(iterable)
151151
_apply_table(f, target, iterable; threaded, kw...)
152152
else # this is probably some form of iterable...
153-
if threaded isa _True
153+
if threaded isa True
154154
# `collect` first so we can use threads
155155
_apply(f, target, collect(iterable); threaded, kw...)
156156
else
@@ -226,14 +226,14 @@ end
226226
# Rewrap all FeatureCollectionTrait feature collections as GI.FeatureCollection
227227
# Maybe use threads to call _apply on component features
228228
@inline function _apply(f::F, target, ::GI.FeatureCollectionTrait, fc;
229-
crs=GI.crs(fc), calc_extent=_False(), threaded
229+
crs=GI.crs(fc), calc_extent=False(), threaded
230230
) where F
231231

232232
# Run _apply on all `features` in the feature collection, possibly threaded
233233
apply_to_feature(i) =
234-
_apply(f, target, GI.getfeature(fc, i); crs, calc_extent, threaded=_False())::GI.Feature
234+
_apply(f, target, GI.getfeature(fc, i); crs, calc_extent, threaded=False())::GI.Feature
235235
features = _maptasks(apply_to_feature, 1:GI.nfeature(fc), threaded)
236-
if calc_extent isa _True
236+
if calc_extent isa True
237237
# Calculate the extent of the features
238238
extent = mapreduce(GI.extent, Extents.union, features)
239239
# Return a FeatureCollection with features, crs and calculated extent
@@ -245,13 +245,13 @@ end
245245
end
246246
# Rewrap all FeatureTrait features as GI.Feature, keeping the properties
247247
@inline function _apply(f::F, target, ::GI.FeatureTrait, feature;
248-
crs=GI.crs(feature), calc_extent=_False(), threaded
248+
crs=GI.crs(feature), calc_extent=False(), threaded
249249
) where F
250250
# Run _apply on the contained geometry
251251
geometry = _apply(f, target, GI.geometry(feature); crs, calc_extent, threaded)
252252
# Get the feature properties
253253
properties = GI.properties(feature)
254-
if calc_extent isa _True
254+
if calc_extent isa True
255255
# Calculate the extent of the geometry
256256
extent = GI.extent(geometry)
257257
# Return a new Feature with the new geometry and calculated extent, but the original properties and crs
@@ -264,36 +264,36 @@ end
264264
# Reconstruct nested geometries,
265265
# maybe using threads to call _apply on component geoms
266266
@inline function _apply(f::F, target, trait, geom;
267-
crs=GI.crs(geom), calc_extent=_False(), threaded
267+
crs=GI.crs(geom), calc_extent=False(), threaded
268268
)::(GI.geointerface_geomtype(trait)) where F
269269
# Map `_apply` over all sub geometries of `geom`
270270
# to create a new vector of geometries
271271
# TODO handle zero length
272-
apply_to_geom(i) = _apply(f, target, GI.getgeom(geom, i); crs, calc_extent, threaded=_False())
272+
apply_to_geom(i) = _apply(f, target, GI.getgeom(geom, i); crs, calc_extent, threaded=False())
273273
geoms = _maptasks(apply_to_geom, 1:GI.ngeom(geom), threaded)
274274
return _apply_inner(geom, geoms, crs, calc_extent)
275275
end
276276
@inline function _apply(f::F, target::TraitTarget{<:PointTrait}, trait::GI.PolygonTrait, geom;
277-
crs=GI.crs(geom), calc_extent=_False(), threaded
277+
crs=GI.crs(geom), calc_extent=False(), threaded
278278
)::(GI.geointerface_geomtype(trait)) where F
279279
# We need to force rebuilding a LinearRing not a LineString
280280
geoms = _maptasks(1:GI.ngeom(geom), threaded) do i
281281
lr = GI.getgeom(geom, i)
282282
points = map(GI.getgeom(lr)) do p
283-
_apply(f, target, p; crs, calc_extent, threaded=_False())
283+
_apply(f, target, p; crs, calc_extent, threaded=False())
284284
end
285285
_linearring(_apply_inner(lr, points, crs, calc_extent))
286286
end
287287
return _apply_inner(geom, geoms, crs, calc_extent)
288288
end
289-
function _apply_inner(geom, geoms, crs, calc_extent::_True)
289+
function _apply_inner(geom, geoms, crs, calc_extent::True)
290290
# Calculate the extent of the sub geometries
291291
extent = mapreduce(GI.extent, Extents.union, geoms)
292292
# Return a new geometry of the same trait as `geom`,
293293
# holding the new `geoms` with `crs` and calculated extent
294294
return rebuild(geom, geoms; crs, extent)
295295
end
296-
function _apply_inner(geom, geoms, crs, calc_extent::_False)
296+
function _apply_inner(geom, geoms, crs, calc_extent::False)
297297
# Return a new geometry of the same trait as `geom`, holding the new `geoms` with `crs`
298298
return rebuild(geom, geoms; crs)
299299
end
@@ -322,7 +322,7 @@ using Base.Threads: nthreads, @threads, @spawn
322322
# Threading utility, modified Mason Protters threading PSA
323323
# run `f` over ntasks, where f receives an AbstractArray/range
324324
# of linear indices
325-
@inline function _maptasks(f::F, taskrange, threaded::_True)::Vector where F
325+
@inline function _maptasks(f::F, taskrange, threaded::True)::Vector where F
326326
ntasks = length(taskrange)
327327
# Customize this as needed.
328328
# More tasks have more overhead, but better load balancing
@@ -343,9 +343,12 @@ using Base.Threads: nthreads, @threads, @spawn
343343
return mapreduce(fetch, vcat, tasks)
344344
end
345345
#=
346-
Here we use the compiler directive `@assume_effects :foldable` to force the compiler
346+
Here we used to use the compiler directive `@assume_effects :foldable` to force the compiler
347347
to lookup through the closure. This alone makes e.g. `flip` 2.5x faster!
348+
349+
But it caused inference to fail, so we've removed it. No effect on runtime so far as we can tell,
350+
at least in Julia 1.11.
348351
=#
349-
Base.@assume_effects :foldable @inline function _maptasks(f::F, taskrange, threaded::_False)::Vector where F
352+
@inline function _maptasks(f::F, taskrange, threaded::False)::Vector where F
350353
map(f, taskrange)
351354
end

GeometryOpsCore/src/applyreduce.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,24 @@ feature collections and nested geometries.
3434
@inline function applyreduce(
3535
f::F, op::O, target, geom; threaded=false, init=nothing
3636
) where {F, O}
37-
threaded = _booltype(threaded)
37+
threaded = booltype(threaded)
3838
_applyreduce(f, op, TraitTarget(target), geom; threaded, init)
3939
end
4040

4141
@inline _applyreduce(f::F, op::O, target, geom; threaded, init) where {F, O} =
4242
_applyreduce(f, op, target, GI.trait(geom), geom; threaded, init)
4343
# Maybe use threads reducing over arrays
4444
@inline function _applyreduce(f::F, op::O, target, ::Nothing, A::AbstractArray; threaded, init) where {F, O}
45-
applyreduce_array(i) = _applyreduce(f, op, target, A[i]; threaded=_False(), init)
45+
applyreduce_array(i) = _applyreduce(f, op, target, A[i]; threaded=False(), init)
4646
_mapreducetasks(applyreduce_array, op, eachindex(A), threaded; init)
4747
end
4848
# Try to applyreduce over iterables
4949
@inline function _applyreduce(f::F, op::O, target, ::Nothing, iterable::IterableType; threaded, init) where {F, O, IterableType}
5050
if Tables.istable(iterable)
5151
_applyreduce_table(f, op, target, iterable; threaded, init)
5252
else
53-
applyreduce_iterable(i) = _applyreduce(f, op, target, i; threaded=_False(), init)
54-
if threaded isa _True # Try to `collect` and reduce over the vector with threads
53+
applyreduce_iterable(i) = _applyreduce(f, op, target, i; threaded=False(), init)
54+
if threaded isa True # Try to `collect` and reduce over the vector with threads
5555
_applyreduce(f, op, target, collect(iterable); threaded, init)
5656
else
5757
# Try to `mapreduce` the iterable as-is
@@ -77,23 +77,23 @@ function _applyreduce_table(f::F, op::O, target::GI.FeatureTrait, iterable::Iter
7777
end
7878
# Maybe use threads reducing over features of feature collections
7979
@inline function _applyreduce(f::F, op::O, target, ::GI.FeatureCollectionTrait, fc; threaded, init) where {F, O}
80-
applyreduce_fc(i) = _applyreduce(f, op, target, GI.getfeature(fc, i); threaded=_False(), init)
80+
applyreduce_fc(i) = _applyreduce(f, op, target, GI.getfeature(fc, i); threaded=False(), init)
8181
_mapreducetasks(applyreduce_fc, op, 1:GI.nfeature(fc), threaded; init)
8282
end
8383
# Features just applyreduce to their geometry
8484
@inline _applyreduce(f::F, op::O, target, ::GI.FeatureTrait, feature; threaded, init) where {F, O} =
8585
_applyreduce(f, op, target, GI.geometry(feature); threaded, init)
8686
# Maybe use threads over components of nested geometries
8787
@inline function _applyreduce(f::F, op::O, target, trait, geom; threaded, init) where {F, O}
88-
applyreduce_geom(i) = _applyreduce(f, op, target, GI.getgeom(geom, i); threaded=_False(), init)
88+
applyreduce_geom(i) = _applyreduce(f, op, target, GI.getgeom(geom, i); threaded=False(), init)
8989
_mapreducetasks(applyreduce_geom, op, 1:GI.ngeom(geom), threaded; init)
9090
end
9191
# Don't thread over points it won't pay off
9292
@inline function _applyreduce(
9393
f::F, op::O, target, trait::Union{GI.LinearRing,GI.LineString,GI.MultiPoint}, geom;
9494
threaded, init
9595
) where {F, O}
96-
_applyreduce(f, op, target, GI.getgeom(geom); threaded=_False(), init)
96+
_applyreduce(f, op, target, GI.getgeom(geom); threaded=False(), init)
9797
end
9898
# Apply f to the target
9999
@inline function _applyreduce(f::F, op::O, ::TraitTarget{Target}, ::Trait, x; kw...) where {F,O,Target,Trait<:Target}
@@ -124,7 +124,7 @@ import Base.Threads: nthreads, @threads, @spawn
124124
#
125125
# If you absolutely need a single chunk, then `threaded = false` will always decompose
126126
# to straight `mapreduce` without grouping.
127-
@inline function _mapreducetasks(f::F, op, taskrange, threaded::_True; init) where F
127+
@inline function _mapreducetasks(f::F, op, taskrange, threaded::True; init) where F
128128
ntasks = length(taskrange)
129129
# Customize this as needed.
130130
# More tasks have more overhead, but better load balancing
@@ -144,6 +144,6 @@ import Base.Threads: nthreads, @threads, @spawn
144144
# Finally we join the results into a new vector
145145
return mapreduce(fetch, op, tasks; init)
146146
end
147-
Base.@assume_effects :foldable function _mapreducetasks(f::F, op, taskrange, threaded::_False; init) where F
147+
Base.@assume_effects :foldable function _mapreducetasks(f::F, op, taskrange, threaded::False; init) where F
148148
mapreduce(f, op, taskrange; init)
149149
end

GeometryOpsCore/src/other_primitives.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,10 @@ on geometries from other packages and specify how to rebuild them.
159159
rebuild(geom, child_geoms; kw...) = rebuild(GI.trait(geom), geom, child_geoms; kw...)
160160
function rebuild(trait::GI.AbstractTrait, geom, child_geoms; crs=GI.crs(geom), extent=nothing)
161161
T = GI.geointerface_geomtype(trait)
162-
if GI.is3d(geom)
163-
# The Boolean type parameters here indicate "3d-ness" and "measure" coordinate, respectively.
164-
return T{true,false}(child_geoms; crs, extent)
165-
else
166-
return T{false,false}(child_geoms; crs, extent)
167-
end
162+
# Check the dimensionality of the first child geometry, since it may have changed
163+
# NOTE that without this, 2D to 3D conversions will fail
164+
hasZ = GI.is3d(first(child_geoms))
165+
hasM = GI.ismeasured(first(child_geoms))
166+
167+
return T{hasZ,hasM}(child_geoms; crs, extent)
168168
end

GeometryOpsCore/src/types.jl

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Currently, those circumstances are `area` and `segmentize`, but this could be ex
2222

2323
export Planar, Spherical, Geodesic
2424
export TraitTarget
25-
export BoolsAsTypes, _True, _False, _booltype
25+
export BoolsAsTypes, True, False, booltype
2626

2727
"""
2828
abstract type Manifold
@@ -51,6 +51,8 @@ end
5151
5252
A spherical manifold means that the geometry is on the 3-sphere (but is represented by 2-D longitude and latitude).
5353
54+
By default, the radius is defined as the mean radius of the Earth, `6371008.8 m`.
55+
5456
## Extended help
5557
5658
!!! note
@@ -74,7 +76,7 @@ and `inv_flattening` (``1/f``).
7476
Usually, this is only relevant for area and segmentization calculations. It becomes more relevant as one grows closer to the poles (or equator).
7577
"""
7678
Base.@kwdef struct Geodesic{T} <: Manifold
77-
semimajor_axis::T = 6378137,0
79+
semimajor_axis::T = 6378137.0
7880
inv_flattening::T = 298.257223563
7981
end
8082

@@ -128,7 +130,7 @@ the compiler to separate threaded and non-threaded code paths.
128130
Note that if we didn't include the parent abstract type, this would have been really
129131
type unstable, since the compiler couldn't tell what would be returned!
130132
131-
We had to add the type annotation on the `_booltype(::Bool)` method for this reason as well.
133+
We had to add the type annotation on the `booltype(::Bool)` method for this reason as well.
132134
133135
TODO: should we switch to `Static.jl`?
134136
=#
@@ -140,25 +142,25 @@ TODO: should we switch to `Static.jl`?
140142
abstract type BoolsAsTypes end
141143

142144
"""
143-
struct _True <: BoolsAsTypes
145+
struct True <: BoolsAsTypes
144146
145147
A struct that means `true`.
146148
"""
147-
struct _True <: BoolsAsTypes end
149+
struct True <: BoolsAsTypes end
148150

149151
"""
150-
struct _False <: BoolsAsTypes
152+
struct False <: BoolsAsTypes
151153
152154
A struct that means `false`.
153155
"""
154-
struct _False <: BoolsAsTypes end
156+
struct False <: BoolsAsTypes end
155157

156158
"""
157-
_booltype(x)
159+
booltype(x)
158160
159161
Returns a [`BoolsAsTypes`](@ref) from `x`, whether it's a boolean or a BoolsAsTypes.
160162
"""
161-
function _booltype end
163+
function booltype end
162164

163-
@inline _booltype(x::Bool)::BoolsAsTypes = x ? _True() : _False()
164-
@inline _booltype(x::BoolsAsTypes)::BoolsAsTypes = x
165+
@inline booltype(x::Bool)::BoolsAsTypes = x ? True() : False()
166+
@inline booltype(x::BoolsAsTypes)::BoolsAsTypes = x

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ DelaunayTriangulation = "927a84f5-c5f4-47a5-9785-b46e178433df"
1010
ExactPredicates = "429591f6-91af-11e9-00e2-59fbe8cec110"
1111
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
1212
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
13+
GeometryOpsCore = "05efe853-fabf-41c8-927e-7063c8b9f013"
1314
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1415
SortTileRecursiveTree = "746ee33f-1797-42c2-866d-db2fce69d14d"
1516
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
@@ -33,6 +34,7 @@ ExactPredicates = "2.2.8"
3334
FlexiJoins = "0.1.30"
3435
GeoInterface = "1.2"
3536
GeometryBasics = "0.4.7, 0.5"
37+
GeometryOpsCore = "=0.1.2"
3638
LibGEOS = "0.9.2"
3739
LinearAlgebra = "1"
3840
Proj = "1"

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ makedocs(;
113113
"Explanations" => [
114114
"Paradigms" => "explanations/paradigms.md",
115115
"Peculiarities" => "explanations/peculiarities.md",
116+
"Manifolds" => "explanations/manifolds.md",
116117
],
117118
"Source code" => literate_pages,
118119
],

0 commit comments

Comments
 (0)