Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add package extension on RecipesBase #143

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ jobs:
matrix:
version:
- "1"
- "1.6"
- "nightly"
os:
- ubuntu-latest
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/CIGeoInterfaceMakie.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ jobs:
fail-fast: false
matrix:
version:
- '1.6'
- '1' # automatically expands to the latest stable 1.x release of Julia
os:
- ubuntu-latest
Expand Down
6 changes: 3 additions & 3 deletions GeoInterfaceRecipes/Project.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name = "GeoInterfaceRecipes"
uuid = "0329782f-3d07-4b52-b9f6-d3137cf03c7a"
authors = ["JuliaGeo and contributors"]
version = "1.0.2"
version = "1.0.3"

[deps]
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"

[compat]
GeoInterface = "1"
GeoInterface = "1.3.7"
RecipesBase = "1"
julia = "1"
julia = "1.9"

[extras]
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Expand Down
183 changes: 4 additions & 179 deletions GeoInterfaceRecipes/src/GeoInterfaceRecipes.jl
Original file line number Diff line number Diff line change
@@ -1,186 +1,11 @@
module GeoInterfaceRecipes

using GeoInterface, RecipesBase
import GeoInterface: @enable, @enable_geo_plots

const GI = GeoInterface

# This is now an empty package, but loading both GeoInterface and RecipesBase
# will trigger the extension which loads the recipes
export @enable
export @enable_geo_plots

RecipesBase.@recipe function f(t::Union{GI.PointTrait,GI.MultiPointTrait}, geom)
seriestype --> :scatter
_coordvecs(t, geom)
end

RecipesBase.@recipe function f(t::Union{GI.AbstractLineStringTrait,GI.MultiLineStringTrait}, geom)
seriestype --> :path
_coordvecs(t, geom)
end

RecipesBase.@recipe function f(t::Union{GI.PolygonTrait,GI.MultiPolygonTrait,GI.LinearRingTrait}, geom)
seriestype --> :shape
_coordvecs(t, geom)
end

RecipesBase.@recipe f(::GI.GeometryCollectionTrait, collection) = collect(getgeom(collection))

# Features
RecipesBase.@recipe f(t::GI.FeatureTrait, feature) = GI.geometry(feature)

RecipesBase.@recipe f(t::GI.FeatureCollectionTrait, fc) = collect(GI.getfeature(fc))

# Convert coordinates to the form used by Plots.jl
_coordvecs(::GI.PointTrait, geom) = [tuple(GI.coordinates(geom)...)]
function _coordvecs(::GI.MultiPointTrait, geom)
n = GI.npoint(geom)
# We use a fixed conditional instead of dispatch,
# as `is3d` may not be known at compile-time
if GI.is3d(geom)
_geom2coordvecs!(ntuple(_ -> Array{Float64}(undef, n), 3)..., geom)
else
_geom2coordvecs!(ntuple(_ -> Array{Float64}(undef, n), 2)..., geom)
end
end
function _coordvecs(::GI.AbstractLineStringTrait, geom)
n = GI.npoint(geom)
if GI.is3d(geom)
vecs = ntuple(_ -> Array{Float64}(undef, n), 3)
return _geom2coordvecs!(vecs..., geom)
else
vecs = ntuple(_ -> Array{Float64}(undef, n), 2)
return _geom2coordvecs!(vecs..., geom)
end
end
function _coordvecs(::GI.MultiLineStringTrait, geom)
function loop!(vecs, geom)
i1 = 1
for line in GI.getgeom(geom)
i2 = i1 + GI.npoint(line) - 1
vvecs = map(v -> view(v, i1:i2), vecs)
_geom2coordvecs!(vvecs..., line)
map(v -> v[i2 + 1] = NaN, vecs)
i1 = i2 + 2
end
return vecs
end
n = GI.npoint(geom) + GI.ngeom(geom)
if GI.is3d(geom)
vecs = ntuple(_ -> Array{Float64}(undef, n), 3)
return loop!(vecs, geom)
else
vecs = ntuple(_ -> Array{Float64}(undef, n), 2)
return loop!(vecs, geom)
end
end
function _coordvecs(::GI.LinearRingTrait, geom)
points = GI.getpoint(geom)
if GI.is3d(geom)
return getcoord.(points, 1), getcoord.(points, 2), getcoord.(points, 3)
else
return getcoord.(points, 1), getcoord.(points, 2)
end
end
function _coordvecs(::GI.PolygonTrait, geom)
ring = first(GI.getgeom(geom)) # currently doesn't plot holes
points = GI.getpoint(ring)
if GI.is3d(geom)
return getcoord.(points, 1), getcoord.(points, 2), getcoord.(points, 3)
else
return getcoord.(points, 1), getcoord.(points, 2)
end
end
function _coordvecs(::GI.MultiPolygonTrait, geom)
function loop!(vecs, geom)
i1 = 1
for ring in GI.getring(geom)
i2 = i1 + GI.npoint(ring) - 1
range = i1:i2
vvecs = map(v -> view(v, range), vecs)
_geom2coordvecs!(vvecs..., ring)
map(v -> v[i2 + 1] = NaN, vecs)
i1 = i2 + 2
end
return vecs
end
n = GI.npoint(geom) + GI.nring(geom)
if GI.is3d(geom)
vecs = ntuple(_ -> Array{Float64}(undef, n), 3)
return loop!(vecs, geom)
else
vecs = ntuple(_ -> Array{Float64}(undef, n), 2)
return loop!(vecs, geom)
end
end


_coordvec(n) = Array{Float64}(undef, n)

function _geom2coordvecs!(xs, ys, geom)
for (i, p) in enumerate(GI.getpoint(geom))
xs[i] = GI.x(p)
ys[i] = GI.y(p)
end
return xs, ys
end
function _geom2coordvecs!(xs, ys, zs, geom)
for (i, p) in enumerate(GI.getpoint(geom))
xs[i] = GI.x(p)
ys[i] = GI.y(p)
zs[i] = GI.z(p)
end
return xs, ys, zs
end

function expr_enable(typ)
quote
# We recreate the apply_recipe functions manually here
# as nesting the @recipe macro doesn't work.
function RecipesBase.apply_recipe(plotattributes::Base.AbstractDict{Base.Symbol, Base.Any}, geom::$(esc(typ)))
@nospecialize
series_list = RecipesBase.RecipeData[]
RecipesBase.is_explicit(plotattributes, :label) || (plotattributes[:label] = :none)
Base.push!(series_list, RecipesBase.RecipeData(plotattributes, (GeoInterface.trait(geom), geom)))
return series_list
end
function RecipesBase.apply_recipe(plotattributes::Base.AbstractDict{Base.Symbol, Base.Any}, geom::Base.AbstractVector{<:Base.Union{Base.Missing,<:($(esc(typ)))}})
@nospecialize
series_list = RecipesBase.RecipeData[]
RecipesBase.is_explicit(plotattributes, :label) || (plotattributes[:label] = :none)
for g in Base.skipmissing(geom)
Base.push!(series_list, RecipesBase.RecipeData(plotattributes, (GeoInterface.trait(g), g)))
end
return series_list
end
end
end

"""
GeoInterfaceRecipes.@enable(GeometryType)

Macro to add plot recipes to a geometry type.

# Usage

```julia
struct MyGeometry
...
end
# overload GeoInterface for MyGeometry
...

# Enable Plots.jl plotting
GeoInterfaceRecipes.@enable_geo_plots MyGeometry
```
"""
macro enable(typ)
expr_enable(typ)
end

# Compat
macro enable_geo_plots(typ)
expr_enable(typ)
end

# Enable Plots.jl for GeoInterface wrappers
@enable GeoInterface.Wrappers.WrapperGeometry

end
7 changes: 6 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ version = "1.3.7"
[deps]
Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
GeoFormatTypes = "68eda718-8dee-11e9-39e7-89f7f65f511f"
[weakdeps]
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"

[extensions]
RecipesBaseExt = "RecipesBase"

[compat]
Extents = "0.1.1"
GeoFormatTypes = "0.4"
julia = "1"
julia = "1.9"

[extras]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Expand Down
Loading
Loading