Skip to content

Commit 19abe4b

Browse files
Fix Makie recipe and enable Observable(Pyramid) (#78)
* Fix Makie recipe * Restrict Makie to v0.24 * fix infinite bbox * Fix data_limits * Make plotting work for Observable(Pyramid) * Fix compats * Add test for plot! --------- Co-authored-by: Felix Cremer <[email protected]>
1 parent 6e07cf0 commit 19abe4b

File tree

4 files changed

+145
-53
lines changed

4 files changed

+145
-53
lines changed

Project.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ PyramidSchemeMakieExt = "Makie"
3232
[compat]
3333
Aqua = "0.8"
3434
ArchGDAL = "0.10.4"
35-
CairoMakie = "0.12"
35+
CairoMakie = "0.12,0.13,0.14,0.15"
3636
Colors = "0.12"
3737
DimensionalData = "0.28, 0.29"
3838
DiskArrayEngine = "0.1.2, 0.2"
@@ -42,13 +42,13 @@ Extents = "0.1.3"
4242
FillArrays = "1.11"
4343
Flatten = "0.4.3"
4444
GeoInterface = "1.3.4"
45-
Makie = "0.21.2, 0.22, 0.23, 0.24"
45+
Makie = "0.24"
4646
OffsetArrays = "1.14"
4747
Proj = "1.7.1"
48-
Rasters = "0.11.8, 0.12, 0.13"
48+
Rasters = "0.11.8, 0.12, 0.13, 0.14"
4949
Statistics = "1.10"
5050
Test = "1.10"
51-
TestItemRunner = "0.2.3"
51+
TestItemRunner = "1"
5252
YAXArrayBase = "0.6.1, 0.7"
5353
YAXArrays = "0.5.6, 0.6"
5454
Zarr = "0.9.3"

ext/PyramidSchemeMakieExt.jl

Lines changed: 136 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,150 @@
11
module PyramidSchemeMakieExt
22
using Makie: Axis, Colorbar, DataAspect, Figure, FigureAxisPlot, Observable, Relative
3-
using Makie: on, heatmap!, image!
4-
import Makie: plot, plot!
3+
using Makie: on, heatmap!, map!
4+
using Makie.ComputePipeline: add_input!
5+
using Makie
6+
57
using PyramidScheme: Pyramid, switchkeys, levels, selectlevel, xkey, ykey
68
using DimensionalData: DimensionalData as DD
79
using DimensionalData.Dimensions: XDim, YDim
810
using Extents: Extent, extent, intersects
911
miss2nan(x) = ismissing(x) ? NaN : x
10-
"""
11-
plot(pyramids)
12-
Plot a Pyramid.
13-
This will plot the coarsest resolution level at the beginning and will plot higher resolutions after zooming into the plot.
14-
This is expected to be used with interactive Makie backends.
15-
"""
16-
function plot(pyramid::Pyramid;colorbar=true, size=(1155, 1043), kwargs...)
17-
#This should be converted into a proper recipe for Makie but this would depend on a pyramid type.
18-
fig = Figure(;size)
19-
lon, lat = DD.dims(DD.parent(pyramid))
20-
ax = Axis(fig[1,1], limits=(extrema(lon), extrema(lat)), aspect=DataAspect())
21-
hmap = plot!(ax, pyramid;kwargs...)
22-
if colorbar
23-
Colorbar(fig[1,2], hmap, height = Relative(3.5 / 4))
12+
13+
# hacks to get around DD hacks that get around Makie issues
14+
for p in (Heatmap, Image, Contour, Contourf, Contour3d, Spy, Surface)
15+
f = Makie.plotkey(p)
16+
@eval begin
17+
function Makie.$f(A::Pyramid; kwargs...)
18+
invoke(Makie.$f, Tuple{AbstractMatrix{<: Any}}, A; kwargs...)
19+
end
20+
function Makie.$f(A::Observable{<: Pyramid}; kwargs...)
21+
invoke(Makie.$f, Tuple{<:Union{<:AbstractMatrix{<: Any}, <:Observable{<: AbstractMatrix{<:Any}}}}, A; kwargs...)
22+
end
23+
Makie.expand_dimensions(::Type{$p}, p::Pyramid) = (p,)
24+
Makie.convert_arguments(::Type{$p}, p::Pyramid) = (p,)
2425
end
25-
ax.autolimitaspect = 1
26-
FigureAxisPlot(fig, ax, hmap)
2726
end
2827

29-
function plot!(ax, pyramid::Pyramid;interp=false, kwargs...)#; rastercrs=crs(parent(pyramid)),plotcrs=EPSG(3857), kwargs...)
30-
tip = levels(pyramid)[end-2][:,:]
31-
#@show typeof(tip)
32-
subtypes = Union{typeof.(pyramid.levels)..., typeof(parent(pyramid))}
33-
data = Observable{DD.AbstractDimMatrix}(tip)
34-
xval = only(values(extent(pyramid, XDim)))
35-
yval = only(values(extent(pyramid, YDim)))
36-
rasdataext = Extent(X=xval, Y=yval)
37-
rasext = extent(pyramid)
38-
on(ax.scene.viewport) do viewport
39-
limext = extent(ax.finallimits[])
40-
41-
datalimit = switchkeys(limext, rasext)
42-
data.val = miss2nan.(selectlevel(pyramid, datalimit, target_imsize=viewport.widths))
43-
notify(data)
44-
end
45-
on(ax.finallimits) do limits
46-
limext = extent(limits)
47-
# Compute limit in raster projection
48-
#trans = Proj.Transformation(plotcrs, rastercrs, always_xy=true)
49-
#datalimit = trans_bounds(trans, limext)
50-
datalimit = switchkeys(limext, rasext)
51-
if intersects(rasdataext, limext)
52-
rasdata = selectlevel(pyramid, datalimit, target_imsize=ax.scene.viewport[].widths)
53-
# Project selected data to plotcrs
54-
#data.val = Rasters.resample(rasdata, crs=plotcrs, method=:bilinear )
55-
data.val = miss2nan.(rasdata)
28+
struct PyramidConversion <: Makie.ConversionTrait end
29+
30+
function Makie.conversion_trait(::Type{<:Heatmap}, ::Pyramid)
31+
return PyramidConversion()
32+
end
33+
34+
function Makie.types_for_plot_arguments(::Type{<:Heatmap}, ::PyramidConversion)
35+
return Tuple{<:Pyramid}
36+
end
37+
38+
Makie.expand_dimensions(::PyramidConversion, p::Pyramid) = (p,)
39+
Makie.convert_arguments(::PyramidConversion, p::Pyramid) = (p,)
40+
41+
42+
43+
Makie.plottype(::Pyramid) = Makie.Heatmap
44+
45+
function Makie.plot!(plot::Heatmap{<: Tuple{<: Pyramid}})
46+
#=
47+
Go from a relative space rectangle
48+
to the rectangle in data space and pixel space,
49+
thus getting `ax.finallimits` and `ax.viewport`
50+
respectively.
51+
52+
This is a bit arcane but probably the simplest thing in the long run.
53+
=#
54+
inputpositions = [Point2f(0, 0), Point2f(1, 1)]
55+
add_input!(plot.attributes, :__pyramid_input_positions, inputpositions)
56+
Makie.register_positions_projected!(
57+
plot; input_space = :relative, output_space = :space,
58+
input_name = :__pyramid_input_positions,
59+
output_name = :__pyramid_dataspace_positions,
60+
)
61+
Makie.register_positions_projected!(
62+
plot; input_space = :relative, output_space = :pixel,
63+
input_name = :__pyramid_input_positions,
64+
output_name = :__pyramid_pixelspace_positions,
65+
)
66+
67+
data = Observable{DD.AbstractDimMatrix}(levels(plot.arg1[])[end-2][:, :])
68+
onany(plot, plot.arg1, plot.__pyramid_dataspace_positions, plot.__pyramid_pixelspace_positions) do pyramid, datapos, pixelpos
69+
xyext = values.(extent(pyramid, (XDim, YDim)))
70+
xval, yval = first(xyext), last(xyext)
71+
pyramid_data_ext = Extent(X=xval, Y=yval)
72+
pyramid_ext = extent(pyramid)
73+
74+
data_limits_ext = Extent(X = extrema(first, datapos), Y = extrema(x -> x[2], datapos))
75+
pixel_widths = Point2f(abs.(pixelpos[2] .- pixelpos[1]))
76+
77+
datalimit = switchkeys(data_limits_ext, pyramid_ext)
78+
79+
if intersects(pyramid_data_ext, data_limits_ext)
80+
data[] = miss2nan.(
81+
selectlevel(pyramid, datalimit, target_imsize = pixel_widths)
82+
)
5683
end
57-
notify(data)
84+
nothing
5885
end
59-
#@show typeof(data)
60-
hmap = heatmap!(ax, data; interpolate=interp, kwargs...)
86+
87+
heatmap!(plot, plot.attributes, data)
6188
end
89+
90+
function Makie.data_limits(p::Heatmap{<: Tuple{<: Pyramid}})
91+
extX, extY = extent(p.arg1[], (XDim, YDim))
92+
rect = Rect3f((extX[1], extY[1],0), (extX[2] - extX[1], extY[2] - extY[1], 0))
93+
return rect
94+
end
95+
Makie.boundingbox(p::Heatmap{<: Tuple{<: Pyramid}}, space::Symbol = :data) = Makie.apply_transform_and_model(p, Makie.data_limits(p))
96+
97+
98+
# """
99+
# plot(pyramids)
100+
# Plot a Pyramid.
101+
# This will plot the coarsest resolution level at the beginning and will plot higher resolutions after zooming into the plot.
102+
# This is expected to be used with interactive Makie backends.
103+
# """
104+
# function plot(pyramid::Pyramid;colorbar=true, size=(1155, 1043), kwargs...)
105+
# #This should be converted into a proper recipe for Makie but this would depend on a pyramid type.
106+
# fig = Figure(;size)
107+
# lon, lat = DD.dims(DD.parent(pyramid))
108+
# ax = Axis(fig[1,1], limits=(extrema(lon), extrema(lat)), aspect=DataAspect())
109+
# hmap = plot!(ax, pyramid;kwargs...)
110+
# if colorbar
111+
# Colorbar(fig[1,2], hmap, height = Relative(3.5 / 4))
112+
# end
113+
# ax.autolimitaspect = 1
114+
# FigureAxisPlot(fig, ax, hmap)
115+
# end
116+
117+
# function plot!(ax, pyramid::Pyramid;interp=false, kwargs...)#; rastercrs=crs(parent(pyramid)),plotcrs=EPSG(3857), kwargs...)
118+
# tip = levels(pyramid)[end-2][:,:]
119+
# #@show typeof(tip)
120+
# subtypes = Union{typeof.(pyramid.levels)..., typeof(parent(pyramid))}
121+
# data = Observable{DD.AbstractDimMatrix}(tip)
122+
# xyext = values.(extent(pyramid, (XDim, YDim)))
123+
# xval = first(xyext)
124+
# yval = last(xyext)
125+
# rasdataext = Extent(X=xval, Y=yval)
126+
# rasext = extent(pyramid)
127+
# on(ax.scene.viewport) do viewport
128+
# limext = extent(ax.finallimits[])
129+
130+
# datalimit = switchkeys(limext, rasext)
131+
# data[] = miss2nan.(selectlevel(pyramid, datalimit, target_imsize=viewport.widths))
132+
# end
133+
# on(ax.finallimits) do limits
134+
# limext = extent(limits)
135+
# # Compute limit in raster projection
136+
# #trans = Proj.Transformation(plotcrs, rastercrs, always_xy=true)
137+
# #datalimit = trans_bounds(trans, limext)
138+
# datalimit = switchkeys(limext, rasext)
139+
# if intersects(rasdataext, limext)
140+
# rasdata = selectlevel(pyramid, datalimit, target_imsize=ax.scene.viewport[].widths)
141+
# # Project selected data to plotcrs
142+
# #data.val = Rasters.resample(rasdata, crs=plotcrs, method=:bilinear )
143+
# data.val = miss2nan.(rasdata)
144+
# end
145+
# notify(data)
146+
# end
147+
# #@show typeof(data)
148+
# hmap = heatmap!(ax, data; interpolate=interp, kwargs...)
149+
# end
62150
end

src/PyramidScheme.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,6 @@ function agg_axis(d,n)
275275
# TODO this is only correct for points not intervals
276276
npoints = cld(length(d), n)
277277
half_stepsize = step(d) * (n-1) / 2
278-
@show half_stepsize
279278
sgn = DD.isreverse(d) ? -1 : 1
280279
DD.set(d, LinRange(first(d) + sgn * half_stepsize, last(d) - sgn * half_stepsize, npoints))
281280
end

test/runtests.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ end
4242
@test size(subpyramid) == (10,10)
4343
@test parent(subpyramid) == data[1:10,1:10]
4444
fig, axis, h = plot(pyramid)
45+
@test length(axis.scene.plots) == 1
46+
plot!(axis, pyramid)
47+
@test length(axis.scene.plots) == 2
4548
end
4649

4750
@testitem "Pyramid building RGB eltype" begin
@@ -137,6 +140,8 @@ end
137140
end
138141
end
139142

143+
144+
140145
#=
141146
@testitem "Comparing zarr pyramid with tif pyramid" begin
142147
using PyramidScheme: PyramidScheme as PS

0 commit comments

Comments
 (0)