diff --git a/Project.toml b/Project.toml index ad40d51..8f9009e 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ version = "0.1.1" [deps] CommonDataModel = "1fbeeb36-5f17-413c-809b-666fb144f157" +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" [compat] diff --git a/docs/make.jl b/docs/make.jl index 1eced3b..5da45f5 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -11,11 +11,22 @@ makedocs(; prettyurls=get(ENV, "CI", "false") == "true", canonical="https://Hirlam.github.io/NCPlots.jl", edit_link="main", + collapselevel = 1, assets=String[], ), pages=[ "Home" => "index.md", - "Examples" => "example.md", + "Examples" => [ + "Basic" => "examples/era5_pv.md", + "Light" => "examples/light.md", + "Animation" => "examples/animations.md", + "Multiple plot in axis" => "examples/single_axis.md", + "Geopotential height" => "examples/geopotential.md", + "Limited Area plots" => "examples/lam.md", + "Metcoop Increments" => "examples/metcoop.md" + "Sliders and Menus" => "examples/sliders_menus.md", + + ], "Getting data" => "cds.md", "Keyboard control" => "keyboard.md", "API reference" => "references.md" diff --git a/docs/src/animations.md b/docs/src/animations.md deleted file mode 100644 index bbddf8f..0000000 --- a/docs/src/animations.md +++ /dev/null @@ -1,22 +0,0 @@ -# Animations - -Pass in an Observable - -```julia -t = Observables(1) -field = @lift(view(ds(time=$t)["pv"]) -fig,ax,plt = plot(field) -``` - - -Then animate like - -```julia -for i in 1:100 - t[]=i - sleep(0.01) -end - -``` - -## Recording animations diff --git a/docs/src/assets/metcoop_menu.png b/docs/src/assets/metcoop_menu.png new file mode 100644 index 0000000..d56b369 Binary files /dev/null and b/docs/src/assets/metcoop_menu.png differ diff --git a/docs/src/example.md b/docs/src/example.md deleted file mode 100644 index 3be9bf8..0000000 --- a/docs/src/example.md +++ /dev/null @@ -1,94 +0,0 @@ -# Examples - - -These examples use NetCDF files from the `docs/src/assets` directory. For the animations download larger datasets as exlained in the Getting data section. - - -## ERA5 potential vorticity - -```julia -using NCPlots, GLMakie, NCDatasets -ds = Dataset("assets/era5_pv_z_500hPa.nc") -pv = view(ds, time=1)["pv"] -fig, ax, plt = plot(pv, colormap=:RdBu, colorrange=(-3e-6,3e-6)) -save("assets/era5_pv_docs.png", fig) # hide -``` - -![](assets/era5_pv_docs.png) - - -## Control light - -Access properties `specular` `diffuse` etc. in the returned `plt`. - -![](assets/marble2.png) - -Explain how to update camera, change lightposition - -## Mutliple plots in single axis - -See `docs/plotlogo.jl` for an example - -![](assets/logo.png) - -## Plot on geopotential height surface - -Multiply `x`, `y`, `z` by geopotential height - -![](assets/logo2.png) - - -## Plot on orography - -See plot geopotential height. Color show temperature increments from data assimilation - -![](assets/envar_2019081803.png) - - - -## CARRA - - -![](assets/east_domain.png) -![](assets/east_west_domain.png) - -## Metcoop - -```julia -archive="/lustre/storeB/immutable/archive/projects/metproduction/MEPS/" -ds = Dataset("$archive/2023/10/01/meps_det_2_5km_20231001T00Z.nc") -field = view(ds,hybrid=65,time=1)["air_temperature_ml"] -plot(field) -``` - - - -![](assets/metcoop.png) - - - -## Animated plots - -To make these animations use Observables and `@lift` to lift the dataset view e.g. - -```julia -ds = Dataset(...) -t = Observable(1) -pv = @lift(view(ds,time=$t)["pv"]) -plot(pv) -``` - -Updating `t` wil update the plot e.g. - -```julia -for i=1:100 - t[] = i - sleep(0.01) -end -``` - -![](assets/pv_era5_2.gif) - -You can access eyeposition in `ax.scene` and update in the for loop - -![](assets/test.mp4) diff --git a/docs/src/examples/animations.md b/docs/src/examples/animations.md new file mode 100644 index 0000000..c8efaca --- /dev/null +++ b/docs/src/examples/animations.md @@ -0,0 +1,31 @@ +# Animations + + + +## Animated plots + +To make these animations use Observables and `@lift` to lift the dataset view e.g. + +```julia +ds = Dataset(...) +t = Observable(1) +pv = @lift(view(ds,time=$t)["pv"]) +plot(pv) +``` + +Updating `t` wil update the plot e.g. + +```julia +for i=1:100 + t[] = i + sleep(0.01) +end +``` + +![](../assets/pv_era5_2.gif) + +You can access eyeposition in `ax.scene` and update in the for loop + +![](../assets/test.mp4) + +## Recording animations diff --git a/docs/src/examples/era5_pv.md b/docs/src/examples/era5_pv.md new file mode 100644 index 0000000..3e7e686 --- /dev/null +++ b/docs/src/examples/era5_pv.md @@ -0,0 +1,19 @@ + +# [Example](@id example) + + + +These examples use NetCDF files from the `docs/src/assets` directory. For the animations download larger datasets as exlained in the Getting data section. + + +## ERA5 potential vorticity + +```julia +using NCPlots, GLMakie, NCDatasets +ds = Dataset("assets/era5_pv_z_500hPa.nc") +pv = view(ds, time=1)["pv"] +fig, ax, plt = plot(pv, colormap=:RdBu, colorrange=(-3e-6,3e-6)) +save("assets/era5_pv_docs.png", fig) # hide +``` + +![](../assets/era5_pv_docs.png) diff --git a/docs/src/examples/geopotential.md b/docs/src/examples/geopotential.md new file mode 100644 index 0000000..c1946b2 --- /dev/null +++ b/docs/src/examples/geopotential.md @@ -0,0 +1,18 @@ + +# Plot on geopotential height surface + +Multiply `x`, `y`, `z` by geopotential height + +![](../assets/logo2.png) + + +## Plot on orography + +See plot geopotential height. Color show temperature increments from data assimilation + +![](../assets/envar_2019081803.png) + + + + + diff --git a/docs/src/examples/lam.md b/docs/src/examples/lam.md new file mode 100644 index 0000000..4732c58 --- /dev/null +++ b/docs/src/examples/lam.md @@ -0,0 +1,21 @@ +# Limited area plots + +## CARRA + + +![](../assets/east_domain.png) +![](../assets/east_west_domain.png) + +## Metcoop + +```julia +archive="/lustre/storeB/immutable/archive/projects/metproduction/MEPS/" +ds = Dataset("$archive/2023/10/01/meps_det_2_5km_20231001T00Z.nc") +field = view(ds,hybrid=65,time=1)["air_temperature_ml"] +plot(field) +``` + + + +![](../assets/metcoop.png) + diff --git a/docs/src/examples/light.md b/docs/src/examples/light.md new file mode 100644 index 0000000..b2cbea1 --- /dev/null +++ b/docs/src/examples/light.md @@ -0,0 +1,7 @@ +# Control light + +Access properties `specular` `diffuse` etc. in the returned `plt`. + +![](../assets/marble2.png) + +Explain how to update camera, change lightposition diff --git a/docs/src/examples/metcoop.md b/docs/src/examples/metcoop.md new file mode 100644 index 0000000..a67c162 --- /dev/null +++ b/docs/src/examples/metcoop.md @@ -0,0 +1,42 @@ +# Metcoop + +Example reading from MEPS archive at MET Norway + +```julia +using Glob, NCDatasets, NCPlots, GLMakie + +archive="/lustre/storeB/immutable/archive/projects/metproduction/MEPS/2023/01/30" + +files = glob("meps_det_2_5km*.nc", archive) # use e.g. "....*00.nc" to only match files with 00:00 UTC + +ds = NCDatasets.Dataset(files, aggdim="cycle", isnewdim=true, constvars=["x","y","hybrid","time"],deferopen=false) + +fig = Figure() +fcint = 3; llmax=66; nlev=65 + + +tsl, lsl,csl = SliderGrid(fig[2,1], + (label="LL",range=1:(llmax-fcint),startvalue=1), + (label="Level",range=nlev:-1:1,startvalue=nlev), + (label="Cycle",range=2:length(files),startvalue=2), + tellheight=false, + width=350 +) +ax= Axis(fig[1,2]) + +varname = "air_temperature_ml" +var = ds[varname] + +dsv = @lift(var[:,:,$(lsl.value),$(tsl.value),$(csl.value)] - + var[:,:,$(lsl.value),$(tsl.value)+3,$(csl.value)-1] + ) + +title = @lift(string(Analysis increment, " " ,varname, basename(files[$(cycle_sl.value)])) +subtitle = @lift(string(" Level ", $(lsl.value), "forecast", $(tsl.value)-1, " Hour") ) + +heatmap!(ax,dsv,colormap=:RdBu,colorrange=(-2,2)) +Label(fig[0,:],label,tellwidth=false) +Label(fig[1,:],label,tellwidth=false) +``` + +![](../assets/metcoop_menu.png) \ No newline at end of file diff --git a/docs/src/examples/single_axis.md b/docs/src/examples/single_axis.md new file mode 100644 index 0000000..40149e1 --- /dev/null +++ b/docs/src/examples/single_axis.md @@ -0,0 +1,6 @@ + +# Mutliple plots in single axis + +See `docs/plotlogo.jl` for an example + +![](../assets/logo.png) diff --git a/docs/src/examples/sliders_menus.md b/docs/src/examples/sliders_menus.md new file mode 100644 index 0000000..5dc53c1 --- /dev/null +++ b/docs/src/examples/sliders_menus.md @@ -0,0 +1,33 @@ +# Sliders Menus + + + +```julia + +crangedict = Dict( + "pv" => (-2e-6,2e-6), + "z" => (20000.0,50000.0) +) + +fig = Figure() + +menu = Menu(fig[1,1],options=setdiff(keys(ds),dimnames(ds))) +menu2 = Menu(fig[1,2], options=colorschemes) +sl = SliderGrid(fig[2,1], + (label="time", range=1:length(ds["time"]),startvalue=1,format = x-> Dates.format(ds["time"][x],"yyyymmdd-HH")) + ) +ax = LScene(fig[3,1], show_axis=false) +colorrange=@lift(crangedict($(menu.selection))) +colormap=@lift(colorschem) + +ds = Dataset("...") +dsv = @lift(view(ds,time=$(sl.sliders[1].value))[$(menu.selection)]) +plotvar!(ax,dsv,colorrange=colorrange,colormap=:RdBu) + +``` + + +## Level slider + + + diff --git a/docs/src/index.md b/docs/src/index.md index 5dcc257..5a92659 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -3,10 +3,9 @@ NCPlots is a Julia package for plotting (meteorological) data on the sphere. Datasets should conform to the CF convention and implement the [CommonDataModel.jl](https://github.com/JuliaGeo/CommonDataModel.jl) interface like [NCDatasets](https://github.com/Alexander-Barth/NCDatasets.jl) and [GRIBDatasets](https://github.com/JuliaGeo/GRIBDatasets.jl) -See [here](@ref Examples) for some examples -See [Getting data](@ref getting_data) how to obtain ERA5 and CARRA data. +See [here](@ref example) for a basic example and [Getting data](@ref getting_data) how to obtain ERA5 and CARRA data. -## Installation +### Installation NCPlots is in the Harmonie registry. Add the Harmonie registry (hit `]` in the Julia REPL to enter package mode). @@ -22,7 +21,7 @@ Then install NCPlots with pkg> add NCPlots ``` -## Data requirements +### Data requirements Variables should only have dimensions `longitude` and `latitude`. If there are additional dimensions create a view e.g. for a dataset `ds` that contains a variable `pv` where `pv` in addition to `longitude` and `latitude` also has dimension `time` do diff --git a/src/NCPlots.jl b/src/NCPlots.jl index 17355f5..5219375 100644 --- a/src/NCPlots.jl +++ b/src/NCPlots.jl @@ -1,10 +1,12 @@ module NCPlots -using Makie, CommonDataModel +using Makie, CommonDataModel, Dates export plot, plotvar!, addmeridian!, addequator!, lonlat2xyz +export SliderGridElem, Menu +include("slider.jl") """ fig,ax,plt = plot(ds) @@ -12,7 +14,6 @@ export plot, plotvar!, addmeridian!, addequator!, lonlat2xyz Plots dataset `ds` """ - function plot(ds::CommonDataModel.AbstractDataset; kwargs...) vars = setdiff(keys(ds), CommonDataModel.dimnames(ds)) @@ -119,7 +120,20 @@ function lonlat2xyz(lons::AbstractMatrix, lats::AbstractMatrix) z = sind.(lats) return (x, y, z) end + +lonlat2xyz(lon::Number, lat::Number) = [cosd(lat)*cosd(lon),cosd(lat)*sind(lon),sind(lat)] +lonlat2xyz(lonlat::Vector) = lonlat2xyz(lonlat[1],lonlat[2]) # for two element vector +lonlat2xyz(lonlat::Tuple) = lonlat2xyz(lonlat[1],lonlat[2]) # for two element tuple +lonlat2xyz(lonlat::Vector{Tuple}) = lonlat2xyz.(lonlat[1],lonlat[2]) + + +#function lonlat2xyz(lonlat::Vector) +# lon = first.(lonlat) +# lat = last.(lonlat) +# x,y,z = lonlat2xyz.(lon,lat) +# return x,y,z +#end """ isperiodiclon(lons) diff --git a/src/cds.jl b/src/cds.jl new file mode 100644 index 0000000..9c31790 --- /dev/null +++ b/src/cds.jl @@ -0,0 +1,38 @@ + +using OrderedCollections +#CDSdatasets = Dict( +ERA5pl = OrderedDict( + "product_type" => MultiSelectMenu(["ensemble_mean", "ensemble_members", "ensemble_spread", "reanalysis"]), + "variable" => MultiSelectMenu([ + "divergence", "fraction_of_cloud_cover", "geopotential", + "ozone_mass_mixing_ratio", "potential_vorticity", "relative_humidity", + "specific_cloud_ice_water_content", "specific_cloud_liquid_water_content", "specific_humidity", + "specific_rain_water_content", "specific_snow_water_content", "temperature", + "u_component_of_wind", "v_component_of_wind", "vertical_velocity", "vorticity" + ],pagesize=16), + "pressure_level" => MultiSelectMenu([ + "1", "2", "3", "5", "7", "10", "20", "30", "50", "70", "100", "125", "150", "175", "200", + "225", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", + "775", "800", "825", "850", "875", "900", "925", "950", "975", "1000", + ],pagesize=24), + "year" => MultiSelectMenu(string.(1940:2023), pagesize=24), + "month" => MultiSelectMenu(lpad.(1:12, 2, "0"), pagesize=12), + "time" => MultiSelectMenu(lpad.(0:23, 2, "0") .* ":00", pagesize=24), + "format" => RadioMenu(["netcdf", "grib"]) + # "area" => +) + + +# ds = request("Select dataset", RadioMenu(collect(keys(CDSdatasets)))) + +# ds = CDSdatasets["ERA5 hourly data on pressure levels from 1940 to present"] + +ds = ERA5pl + +cdsrequest = Dict() + +for key in keys(ds) + choices = request("Select $key", ds[key]) + cdsrequest[key] = ds[key].options[collect(choices)] +end + diff --git a/src/slider.jl b/src/slider.jl new file mode 100644 index 0000000..60fd8b5 --- /dev/null +++ b/src/slider.jl @@ -0,0 +1,25 @@ + +""" + SliderGridElem(dim) + +Returns elements to be used in `SliderGrid` + +Example usage +``` +slg = SliderGrid(fig[1,1], SliderGridElem(ds["time"]) +dsv = @lift(view(ds,time=\$(slg[1].value))["elem"]) +``` +""" +function SliderGridElem(dim::CommonDataModel.AbstractVariable) + @assert length(CommonDataModel.dimnames(dim)) == 1 + dimname = CommonDataModel.dimnames(dim)[1] + datetimeformat = x-> Dates.format(dim[x],"yyyymmddHH") + format = dimname=="time" ? datetimeformat : x -> "$x" + return (label = dimname, range = 1:length(dim), format=format) + +end + +function Menu(fig, dim::CommonDataModel.AbstractVariable) + @assert length(CommonDataModel.dimnames(dim)) == 1 + Makie.Menu(fig,options=zip(dim[:],1:length(dim[:])),default=1) +end