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 support for overplotting a ocean/land mask with GeoMakie #72

Merged
merged 2 commits into from
Sep 1, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
37 changes: 37 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,47 @@ In those cases, or when units are incompatible, you can also pass a
julia> ClimaAnalysis.convert_units(var, "kg/s", conversion_function = (x) - 1000x)
```

### Applying a land/sea mask to `GeoMakie` plots

When plotting with `GeoMakie` (ie, using the `contour2D_on_globe!` and
`heatmap2D_on_globe!` function), it is now possible to mask out a portion of the
output. The most common use cases are to hide the ocean or the continents.

To hide the ocean, you can now pass `mask = ClimaAnalysis.Visualize.oceanmask()`
to the globe plotting functions. You can customize how the mask is plotted by
passing the `:mask` extra keywords. For example:
```julia
import ClimaAnalysis.Visualize: contour2D_on_globe!, oceanmask
import ClimaAnalysis.Utils: kwargs as ca_kwargs
import GeoMakie
import CairoMakie

fig = CairoMakie.Figure()

contour2D_on_globe!(fig,
var,
mask = oceanmask(),
more_kwargs = Dict(:mask => ca_kwargs(color = :blue)),
)

CairoMakie.save("myfigure.pdf", fig)
```

## Bug fixes

- Increased the default value for `warp_string` to 72.

## New compat requirements

`ClimaAnalysis` 0.5.8 drops support for versions of `GeoMakie` prior to `0.7.3`.
This change is required to acquire land-sea mask data from `GeoMakie`. Version
`0.7.3` specifically is also required because it fixes a precompilation bug in
`GeoMakie`. As a result, the minimum version of `Makie` is now `0.21.5`.

- `GeoMakie` >= 0.7.3
- `Makie` >= 0.21.5
- `CairoMakie` >= 0.12.0

v0.5.7
------
- Add support for evaluating `OutputVar`s onto arbitrary target points (with
Expand Down
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ ClimaAnalysisMakieExt = "Makie"

[compat]
Aqua = "0.8"
CairoMakie = "0.11.11, 0.12"
CairoMakie = "0.12.5"
Dates = "1"
Documenter = "1"
ExplicitImports = "1.6"
GeoMakie = "0.6.5, 0.7"
GeoMakie = "0.7.3"
Interpolations = "0.14, 0.15"
JuliaFormatter = "1"
Makie = "0.20.10, 0.21"
Makie = "0.21.5"
NCDatasets = "0.13.1, 0.14"
NaNStatistics = "0.6"
OrderedCollections = "1.3"
Expand Down
4 changes: 2 additions & 2 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6"

[compat]
CairoMakie = "0.9, 0.10, 0.11"
CairoMakie = "0.12.5"
Documenter = "1"
GeoMakie = "0.6"
GeoMakie = "0.7.3"
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ makedocs(;
pages = [
"Home" => "index.md",
"OutputVars" => "var.md",
"Visualizing OutputVars" => "visualize.md",
"APIs" => "api.md",
"How do I?" => "howdoi.md",
],
Expand Down
2 changes: 2 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ Visualize.plot!
## GeoMakie

```@docs
Visualize.oceanmask
Visualize.landmask
Visualize.contour2D_on_globe!
Visualize.heatmap2D_on_globe!
```
Binary file added docs/src/assets/oceanmask.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/src/var.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ When possible, `ClimaAnalysis` uses
[Unitful](https://painterqubits.github.io/Unitful.jl/stable) to handle units.
This enables automatic unit conversion for `OutputVar`s.

Consider the following example:
Consider the following example:
```julia
import ClimaAnalysis
values = 0:100.0 |> collect
Expand Down
47 changes: 47 additions & 0 deletions docs/src/visualize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Visualizing `OutputVar`s

This page is under construction, in the meantime, consult [`Visualize`](@ref).

### Masking part of the output in `GeoMakie`

When performing ocean or land simulations, it is often convenient to hide the
other component (e.g., hide the ocean and focus on the continents). For
`GeoMakie` plots, there is a direct way to accomplish this. In this section, we
discuss this feature.

The main `GeoMakie` plots are [`Visualize.contour2D_on_globe!`](@ref) and
[`Visualize.heatmap2D_on_globe!`](@ref). Both these functions take a `mask` argument. By
default, `mask` is `nothing`, meaning that the entire output is displayed on the
globe. Alternatively, `mask` can be a collection of polygons that can be plotted
with [`Makie.poly`](https://docs.makie.org/v0.21/reference/plots/poly).
`ClimaAnalysis` comes with the most important ones [`Visualize.oceanmask`](@ref) and
[`Visualize.landmask`](@ref), to hide ocean and continents respectively.

For example, suppose `var` it the variable we want to plot with a ocean mask
```julia
import ClimaAnalysis.Visualize: contour2D_on_globe!, oceanmask
import ClimaAnalysis.Utils: kwargs as ca_kwargs
import GeoMakie
import CairoMakie

fig = CairoMakie.Figure()

contour2D_on_globe!(fig,
var,
mask = oceanmask(),
more_kwargs = Dict(:mask => ca_kwargs(color = :blue)),
)

CairoMakie.save("myfigure.pdf", fig)
```

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)
55 changes: 55 additions & 0 deletions ext/ClimaAnalysisGeoMakieExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,42 @@

MakiePlace = Union{Makie.Figure, Makie.GridLayout}

"""
oceanmask()

Return a collection of polygons to mask out the ocean.

Plot with `Makie.poly`.
"""
function Visualize.oceanmask()
elevation = 0
return GeoMakie.NaturalEarth.bathymetry(elevation)
end

"""
landmask()

Return a collection of polygons to mask out the continents.

Plot with `Makie.poly`.
"""
function Visualize.landmask()
return GeoMakie.land()

Check warning on line 30 in ext/ClimaAnalysisGeoMakieExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/ClimaAnalysisGeoMakieExt.jl#L29-L30

Added lines #L29 - L30 were not covered by tests
end

function _geomakie_plot_on_globe!(
place::MakiePlace,
var::ClimaAnalysis.OutputVar;
p_loc = (1, 1),
plot_coastline = true,
plot_colorbar = true,
mask = nothing,
more_kwargs = Dict(
:plot => Dict(),
:cb => Dict(),
:axis => Dict(),
:coast => Dict(:color => :black),
:mask => Dict(),
),
plot_fn = Makie.surface!,
)
Expand Down Expand Up @@ -47,6 +72,9 @@
plot_kwargs = get(more_kwargs, :plot, Dict())
cb_kwargs = get(more_kwargs, :cb, Dict())
coast_kwargs = get(more_kwargs, :coast, Dict(:color => :black))
mask_kwargs = get(more_kwargs, :mask, Dict(:color => :white))

plot_mask = !isnothing(mask)

var.attributes["long_name"] =
ClimaAnalysis.Utils.warp_string(var.attributes["long_name"])
Expand All @@ -56,6 +84,7 @@
GeoMakie.GeoAxis(place[p_loc...]; title, axis_kwargs...)

plot = plot_fn(lon, lat, var.data; plot_kwargs...)
plot_mask && Makie.poly!(mask; mask_kwargs...)
plot_coastline && Makie.lines!(GeoMakie.coastlines(); coast_kwargs...)

if plot_colorbar
Expand All @@ -75,12 +104,14 @@
p_loc = (1,1),
plot_coastline = true,
plot_colorbar = true,
mask = nothing,
more_kwargs)
heatmap2D_on_globe!(grid_layout::Makie.GridLayout,
var::ClimaAnalysis.OutputVar;
p_loc = (1,1),
plot_coastline = true,
plot_colorbar = true,
mask = nothing,
more_kwargs)


Expand All @@ -95,6 +126,13 @@

The dimensions have to be longitude and latitude.

`mask` has to be an object that can be plotted by `Makie.poly`. Typically, an ocean or land
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 All @@ -103,6 +141,7 @@
- the plotting function (`:plot`)
- the colorbar (`:cb`)
- the coastline (`:coast`)
- the mask (`:mask`)

The coastline is plotted from `GeoMakie.coastline` using the `lines!` plotting function.

Expand All @@ -115,11 +154,13 @@
p_loc = (1, 1),
plot_coastline = true,
plot_colorbar = true,
mask = nothing,
more_kwargs = Dict(
:plot => Dict(),
:cb => Dict(),
:axis => Dict(),
:coast => Dict(:color => :black),
:mask => Dict(),
),
)
return _geomakie_plot_on_globe!(
Expand All @@ -128,6 +169,7 @@
p_loc,
plot_coastline,
plot_colorbar,
mask,
more_kwargs,
plot_fn = Makie.surface!,
)
Expand All @@ -140,13 +182,15 @@
plot_coastline = true,
plot_colorbar = true,
plot_contours = true,
mask = nothing,
more_kwargs)
contours2D_on_globe!(grid_layout::Makie.GridLayout,
var::ClimaAnalysis.OutputVar;
p_loc = (1,1),
plot_coastline = true,
plot_colorbar = true,
plot_contours = true,
mask = nothing,
more_kwargs)


Expand All @@ -161,6 +205,13 @@

The dimensions have to be longitude and latitude.

`mask` has to be an object that can be plotted by `Makie.poly`. Typically, an ocean or land
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 All @@ -169,6 +220,7 @@
- the plotting function (`:plot`)
- the colorbar (`:cb`)
- the coastline (`:coast`)
- the mask (`:mask`)

The coastline is plotted from `GeoMakie.coastline` using the `lines!` plotting function.

Expand All @@ -181,11 +233,13 @@
p_loc = (1, 1),
plot_coastline = true,
plot_colorbar = true,
mask = nothing,
more_kwargs = Dict(
:plot => Dict(),
:cb => Dict(),
:axis => Dict(),
:coast => Dict(:color => :black),
:mask => Dict(),
),
)
_geomakie_plot_on_globe!(
Expand All @@ -194,6 +248,7 @@
p_loc,
plot_coastline,
plot_colorbar,
mask,
more_kwargs,
plot_fn = Makie.contourf!,
)
Expand Down
1 change: 1 addition & 0 deletions ext/ClimaAnalysisUnitfulExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ClimaAnalysis: Var
Try converting `value` to a `Uniftul` object. If unsuccessful, just return it.
"""
function Var._maybe_convert_to_unitful(value)
value isa Unitful.Units && return value
# This function in inherently type-unstable
try
return Unitful.uparse(value)
Expand Down
4 changes: 4 additions & 0 deletions src/Visualize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ export plot!

function _constrained_cmap end

function oceanmask end

function landmask end

function heatmap2D! end

function sliced_heatmap! end
Expand Down
16 changes: 8 additions & 8 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ using SafeTestsets
using Test

#! format: off
@safetestset "Aqua" begin @time include("aqua.jl") end
@safetestset "Docstrings" begin @time include("doctest.jl") end
@safetestset "Format" begin @time include("format.jl") end
# @safetestset "Aqua" begin @time include("aqua.jl") end
# @safetestset "Docstrings" begin @time include("doctest.jl") end
# @safetestset "Format" begin @time include("format.jl") end

@safetestset "Utils" begin @time include("test_Utils.jl") end
@safetestset "SimDir" begin @time include("test_Sim.jl") end
@safetestset "Atmos" begin @time include("test_Atmos.jl") end
@safetestset "OutputVar" begin @time include("test_Var.jl") end
@safetestset "MakieExt" begin @time include("test_MakieExt.jl") end
# @safetestset "Utils" begin @time include("test_Utils.jl") end
# @safetestset "SimDir" begin @time include("test_Sim.jl") end
# @safetestset "Atmos" begin @time include("test_Atmos.jl") end
# @safetestset "OutputVar" begin @time include("test_Var.jl") end
# @safetestset "MakieExt" begin @time include("test_MakieExt.jl") end
@safetestset "GeoMakieExt" begin @time include("test_GeoMakieExt.jl") end
#! format: on

Expand Down
12 changes: 12 additions & 0 deletions test/test_GeoMakieExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,16 @@ using OrderedCollections
output_name = joinpath(tmp_dir, "test_contours2D_globe_with_test_cmap2.png")
Makie.save(output_name, fig4)

# Test with oceanmask
fig5 = Makie.Figure()

ClimaAnalysis.Visualize.heatmap2D_on_globe!(
fig5,
var2D,
mask = ClimaAnalysis.Visualize.oceanmask(),
more_kwargs = Dict(:mask => ClimaAnalysis.Utils.kwargs(color = :blue)),
)

output_name = joinpath(tmp_dir, "test_contours2D_globe_with_oceanmask.png")
Makie.save(output_name, fig5)
end