Skip to content

Commit

Permalink
Update plotting functions to use masks
Browse files Browse the repository at this point in the history
This commit also fixes the issue of masking not affecting the colorbar.
This commit also changes `Visualize.oceanmask` to return the geometry
instead of a FeatureCollection.
  • Loading branch information
ph-kev committed Oct 11, 2024
1 parent 7115e78 commit 6c98e99
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 17 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ var_no_land = ClimaAnalysis.apply_landmask(var)
var_no_ocean = ClimaAnalysis.apply_oceanmask(var)
```

## Bug fixes
- Masking now affects the colorbar.

v0.5.10
-------

Expand Down
Binary file added docs/src/assets/bias_plot_oceanmask.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 26 additions & 4 deletions docs/src/visualize.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ In this example, we plotted `var` on the globe and overplotted a blue ocean.
`ca_kwargs` (`Utils.kwargs`) is a convenience function to pass keyword arguments
more easily.

!!! note Masking does not affect the colorbar. If you have values defined
beneath the map, they can still affect the colorbar.

The output might look something like:

![oceanmask](./assets/oceanmask.png)
Expand Down Expand Up @@ -70,4 +67,29 @@ CairoMakie.save("myfigure.pdf", fig)

The output produces something like:

![biasplot](./assets/bias_plot.png)
![biasplot](./assets/bias_plot.png)

We can also plot the bias using an ocean mask. This also means we compute the bias only
over land.

```julia
import ClimaAnalysis
import ClimaAnalysis.Visualize: plot_bias_on_globe!, oceanmask
import GeoMakie
import CairoMakie

obs_var = ClimaAnalysis.OutputVar("ta_1d_average.nc")
sim_var = ClimaAnalysis.get(ClimaAnalysis.simdir("simulation_output"), "ta")

fig = CairoMakie.Figure()
plot_bias_on_globe!(fig,
var,
mask = oceanmask(),
more_kwargs = Dict(:mask => ca_kwargs(color = :blue)),
)
CairoMakie.save("myfigure.pdf", fig)
```

The output produces something like:

![biasplot_oceanmask](./assets/bias_plot_oceanmask.png)
47 changes: 34 additions & 13 deletions ext/ClimaAnalysisGeoMakieExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Plot with `Makie.poly`.
"""
function Visualize.oceanmask()
elevation = 0
return GeoMakie.NaturalEarth.bathymetry(elevation)
return GeoMakie.NaturalEarth.bathymetry(elevation).geometry
end

"""
Expand Down Expand Up @@ -48,6 +48,9 @@ function _geomakie_plot_on_globe!(
)
length(var.dims) == 2 || error("Can only plot 2D variables")

apply_mask = _find_mask_to_apply(mask)
!isnothing(apply_mask) && (var = apply_mask(var))

lon_name = ""
lat_name = ""

Expand Down Expand Up @@ -130,9 +133,6 @@ The dimensions have to be longitude and latitude.
mask. `ClimaAnalysis` comes with predefined masks, check out [`Visualize.oceanmask`](@ref) and
[`Visualize.landmask`](@ref).
!!! note Masking does not affect the colorbar. If you have values defined beneath the map,
they can still affect the colorbar.
Additional arguments to the plotting and axis functions
=======================================================
Expand Down Expand Up @@ -209,9 +209,6 @@ The dimensions have to be longitude and latitude.
mask. `ClimaAnalysis` comes with predefined masks, check out [`Visualize.oceanmask`](@ref) and
[`Visualize.landmask`](@ref).
!!! note Masking does not affect the colorbar. If you have values defined beneath the map,
they can still affect the colorbar.
Additional arguments to the plotting and axis functions
=======================================================
Expand Down Expand Up @@ -287,10 +284,9 @@ The dimensions have to be longitude and latitude.
`mask` has to be an object that can be plotted by `Makie.poly`. `ClimaAnalysis` comes with
predefined masks, check out [`Visualize.oceanmask`](@ref) and [`Visualize.landmask`](@ref).
!!! note
Masking does not affect the colorbar. If you have values defined beneath the map, they
can still affect the colorbar.
Also, the corresponding mask is applied to the `OutputVar`s. For instance, using
`Visualize.landmask` means `ClimaAnalysis.apply_landmask` is applied to the `OutputVar`s
when computing the bias.
Additional arguments to the plotting and axis functions
=======================================================
Expand Down Expand Up @@ -324,9 +320,14 @@ function Visualize.plot_bias_on_globe!(
:mask => Dict(),
),
)
bias_var = ClimaAnalysis.bias(sim, obs)
apply_mask = _find_mask_to_apply(mask)

bias_var = ClimaAnalysis.bias(sim, obs, mask = apply_mask)
global_bias = round(bias_var.attributes["global_bias"], sigdigits = 3)
rmse = round(ClimaAnalysis.global_rmse(sim, obs), sigdigits = 3)
rmse = round(
ClimaAnalysis.global_rmse(sim, obs, mask = apply_mask),
sigdigits = 3,
)
units = ClimaAnalysis.units(bias_var)

bias_var.attributes["long_name"] *= " (RMSE: $rmse $units, Global bias: $global_bias $units)"
Expand Down Expand Up @@ -375,4 +376,24 @@ function Visualize.plot_bias_on_globe!(
)
end

"""
Return the appropriate mask to apply to a `OutputVar` from the plotting `mask`.
If `mask` is `Visualize.landmask()`, return `ClimaAnalysis.apply_landmask`. If `mask` is
`Visualize.oceanmask()`, return `ClimaAnalysis.apply_oceanmask`. In all other cases, return
nothing.
"""
function _find_mask_to_apply(mask)
if isnothing(mask)
return nothing
elseif mask == Visualize.landmask()
return ClimaAnalysis.apply_landmask
elseif mask == Visualize.oceanmask()
return ClimaAnalysis.apply_oceanmask
else
@warn "Mask not recognized, overplotting it. The colorbar will not be correct"
return nothing
end
end

end
50 changes: 50 additions & 0 deletions test/test_GeoMakieExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,54 @@ using OrderedCollections
ClimaAnalysis.Visualize.heatmap2D_on_globe!(fig9, ocean_var)
output_name = joinpath(tmp_dir, "plot_apply_ocean_mask.png")
Makie.save(output_name, fig9)

# Test bias plots with apply_landmask and apply_oceanmask
fig10 = Makie.Figure()

lon = collect(range(-179.5, 179.5, 360))
lat = collect(range(-89.5, 89.5, 180))
data = collect(reshape(-32400:32399, (360, 180))) ./ (32399.0 / 5.0)
dims = OrderedDict(["lon" => lon, "lat" => lat])
attribs = Dict("long_name" => "idk", "short_name" => "ta", "units" => "K")
dim_attribs = OrderedDict([
"lon" => Dict("units" => "deg"),
"lat" => Dict("units" => "deg"),
])
var = ClimaAnalysis.OutputVar(attribs, dims, dim_attribs, data)

data_zero = zeros(length(lon), length(lat))
var_zero = ClimaAnalysis.OutputVar(attribs, dims, dim_attribs, data_zero)
ClimaAnalysis.Visualize.plot_bias_on_globe!(
fig10,
var,
var_zero;
mask = ClimaAnalysis.Visualize.landmask(),
more_kwargs = Dict(:mask => ClimaAnalysis.Utils.kwargs(color = :red)),
)
output_name = joinpath(tmp_dir, "plot_bias_landmask.png")
Makie.save(output_name, fig10)

fig11 = Makie.Figure()
ClimaAnalysis.Visualize.plot_bias_on_globe!(
fig11,
var,
var_zero;
mask = ClimaAnalysis.Visualize.oceanmask(),
more_kwargs = Dict(:mask => ClimaAnalysis.Utils.kwargs(color = :blue)),
)
output_name = joinpath(tmp_dir, "plot_bias_oceanmask.png")
Makie.save(output_name, fig11)

# Test bias plots with a mask that is not landmask() or oceanmask()
fig12 = Makie.Figure()
land_mask_modified = first(ClimaAnalysis.Visualize.landmask(), 50)

ClimaAnalysis.Visualize.heatmap2D_on_globe!(
fig12,
var2D,
mask = land_mask_modified,
more_kwargs = Dict(:mask => ClimaAnalysis.Utils.kwargs(color = :red)),
)
output_name = joinpath(tmp_dir, "plot_modified_mask.png")
Makie.save(output_name, fig12)
end

0 comments on commit 6c98e99

Please sign in to comment.