diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml new file mode 100644 index 0000000..c388afd --- /dev/null +++ b/.github/workflows/Documentation.yml @@ -0,0 +1,80 @@ +name: Documentation + +on: + push: + branches: + - main + tags: '*' + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-docs: + name: Build + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + + - uses: julia-actions/setup-julia@v2 + with: + version: '1' + + - uses: julia-actions/cache@v2 + + - name: Install dependencies + run: | + julia --project=docs -e ' + using Pkg + Pkg.develop(PackageSpec(path=pwd())) + Pkg.instantiate()' + + - name: Build documentation + run: julia --project=docs docs/make.jl + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: documentation-build + path: docs/build/ + retention-days: 7 + + deploy-docs: + name: Deploy + needs: build-docs + runs-on: ubuntu-latest + # Deploy on pushes to main or tags, and PRs from the same repo (not forks) + if: github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork) + permissions: + contents: write + pull-requests: read + statuses: write + concurrency: + group: docs-deploy + cancel-in-progress: false + steps: + - uses: actions/checkout@v4 + + - uses: julia-actions/setup-julia@v2 + with: + version: '1' + + - uses: actions/download-artifact@v4 + with: + name: documentation-build + path: docs/build/ + + - name: Install Documenter + run: julia -e 'using Pkg; Pkg.add("Documenter")' + + - name: Deploy documentation + run: julia docs/deploy.jl + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.gitignore b/.gitignore index c178bd0..09f0117 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,11 @@ Manifest.toml *.png *.mp4 +*.js +*.html +*.css + +docs/build/ +docs/literated/ + + diff --git a/README.md b/README.md index 7291ece..95b4701 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,100 @@ -# CopernicusClimateDataStore.jl + +

+ CopernicusClimateDataStore.jl +

-Julia wrapper around the [Copernicus Climate Data Store (CDS)](https://cds.climate.copernicus.eu/) -for downloading ERA5 reanalysis data. + +

+ 🌍 Julia interface to the Copernicus Climate Data Store for downloading ERA5 reanalysis data +

-## Installation +

+ + Documentation + +

+ +CopernicusClimateDataStore.jl wraps the [`era5cli`](https://era5cli.readthedocs.io/) command-line tool, +providing a convenient Julia interface for downloading ERA5 hourly and monthly data to NetCDF or GRIB. + +### Installation ```julia using Pkg -Pkg.add(url="https://github.com/YOUR_USERNAME/CopernicusClimateDataStore.jl") +Pkg.add(url="https://github.com/NumericalEarth/CopernicusClimateDataStore.jl") ``` -## CDS Account Setup +### Before you start -Before downloading data, you must: +You need a Copernicus Climate Data Store account: -1. **Create a CDS account** at https://cds.climate.copernicus.eu/ -2. **Accept the Terms of Use** for the ERA5 dataset at - https://cds.climate.copernicus.eu/datasets/reanalysis-era5-single-levels?tab=download#manage-licences -3. **Configure your API key** by running: +1. **Create an account** at https://cds.climate.copernicus.eu/ +2. **Accept the ERA5 Terms of Use** at https://cds.climate.copernicus.eu/datasets/reanalysis-era5-single-levels +3. **Configure your API key**: ```bash era5cli config --key YOUR_PERSONAL_ACCESS_TOKEN ``` -Your personal access token can be found on your CDS profile page after logging in. +Your personal access token is on your [CDS profile page](https://cds.climate.copernicus.eu/). -## Quick Start +### Quick start + +Download and visualize 2-metre temperature over Europe: ```julia using CopernicusClimateDataStore using NCDatasets using CairoMakie -# Download 2m temperature for Europe, Jan 1, 2020 at 12:00 UTC -files = hourly( - variables = "2m_temperature", - startyear = 2020, - months = 1, - days = 1, - hours = 12, - area = (lat = (35, 60), lon = (-10, 25)), - format = "netcdf", - outputprefix = "europe" -) - -# Load the downloaded file -filename = first(files) -ds = NCDataset(filename) - -lon = ds["longitude"][:] -lat = ds["latitude"][:] -temp_C = ds["t2m"][:, :, 1] .- 273.15 +files = hourly(variables = "2m_temperature", + startyear = 2020, + months = 6, + days = 21, + hours = 12, + area = (lat = (35, 70), lon = (-15, 40)), + outputprefix = "europe") + +# Load the data +ds = NCDataset(first(files)) +λ = ds["longitude"][:] # degrees East +φ = ds["latitude"][:] # degrees North +T = ds["t2m"][:, :, 1] .- 273.15 # K → °C close(ds) # Plot -fig = Figure(size = (800, 600)) -ax = Axis(fig[1, 1], xlabel = "Longitude", ylabel = "Latitude") -hm = heatmap!(ax, lon, lat, temp_C', colormap = :thermal) -Colorbar(fig[1, 2], hm, label = "Temperature (°C)") +fig, ax, hm = heatmap(λ, φ, T'; colormap = :thermal) +Colorbar(fig[1, 2], hm; label = "Temperature (°C)") +ax.xlabel = "λ (°E)" +ax.ylabel = "φ (°N)" save("temperature.png", fig) ``` -## API - -### `hourly(; variables, startyear, ...)` +![](temperature.png) -Download ERA5 hourly data. +### Key arguments -**Key arguments:** -- `variables`: Variable name(s), e.g. `"2m_temperature"` -- `startyear`, `endyear`: Year range -- `months`, `days`, `hours`: Time filters -- `area`: Bounding box `(lat = (south, north), lon = (west, east))` -- `format`: `"netcdf"` (default) or `"grib"` -- `outputprefix`: Prefix for output filename -- `dryrun`: If `true`, print command without downloading -- `splitmonths`: Split output by month (default: `true`) -- `merge`: Merge all output into a single file (default: `false`) +| Argument | Description | +|:---------|:------------| +| `variables` | Variable name, e.g. `"2m_temperature"`, `"total_precipitation"` | +| `startyear` | Year to download (required) | +| `months` | Month or months (1–12) | +| `days` | Day or days (1–31) | +| `hours` | Hour or hours (0–23) | +| `area` | Bounding box: `(lat = (south, north), lon = (west, east))` | +| `format` | `"netcdf"` (default) or `"grib"` | +| `outputprefix` | Prefix for output filename | +| `merge` | Merge all data into a single file (default: `false`) | -**Note:** By default, one file is created per month. Use `merge=true` for a single file. +By default, one file is created per month. Use `merge = true` for a single file. ### Common variables | Request name | NetCDF name | Description | -|-------------|-------------|-------------| -| `2m_temperature` | `t2m` | 2 metre temperature (K) | -| `10m_u_component_of_wind` | `u10` | 10 metre U wind (m/s) | -| `10m_v_component_of_wind` | `v10` | 10 metre V wind (m/s) | +|:-------------|:------------|:------------| +| `2m_temperature` | `t2m` | 2-metre temperature (K) | +| `10m_u_component_of_wind` | `u10` | 10-metre zonal wind (m/s) | +| `10m_v_component_of_wind` | `v10` | 10-metre meridional wind (m/s) | | `total_precipitation` | `tp` | Total precipitation (m) | +| `mean_sea_level_pressure` | `msl` | Mean sea level pressure (Pa) | -## Examples - -See the `examples/` directory for Literate-style examples including an animation -of European temperature evolution. - -## License - -Apache License 2.0 +See the [CDS ERA5 documentation](https://cds.climate.copernicus.eu/datasets/reanalysis-era5-single-levels) for a complete list. diff --git a/docs/Project.toml b/docs/Project.toml index c4a0f05..df691b8 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,6 +5,9 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" NCDatasets = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" +[sources] +CopernicusClimateDataStore = {path = ".."} + [compat] CairoMakie = "0.12, 0.13" Documenter = "1" diff --git a/docs/deploy.jl b/docs/deploy.jl new file mode 100644 index 0000000..e7f2b12 --- /dev/null +++ b/docs/deploy.jl @@ -0,0 +1,20 @@ +using Documenter + +@info "Cleaning up temporary .jld2 and .nc output created by doctests or literated examples..." + +for (root, _, filenames) in walkdir(@__DIR__) + for file in filenames + if any(occursin(file), (r"\.jld2$", r"\.nc$", r"\.grib$")) + rm(joinpath(root, file); force=true) + end + end +end + +deploydocs(; + repo = "github.com/NumericalEarth/CopernicusClimateDataStore.jl", + devbranch = "main", + # Only push previews if all the relevant environment variables are non-empty. This is an + # attempt to work around https://github.com/JuliaDocs/Documenter.jl/issues/2048. + push_preview = all(!isempty, (get(ENV, "GITHUB_TOKEN", ""), get(ENV, "DOCUMENTER_KEY", ""))), + forcepush = true, +) diff --git a/docs/make.jl b/docs/make.jl index 882c3a8..d696e7c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -13,6 +13,7 @@ examples = [ ("European temperature evolution", "european_temperature_evolution"), ] +#= for (title, basename) in examples script_path = joinpath(examples_src_dir, basename * ".jl") @info "Building example: $title" @@ -20,6 +21,7 @@ for (title, basename) in examples end example_pages = [title => joinpath("literated", basename * ".md") for (title, basename) in examples] +=# makedocs( sitename = "CopernicusClimateDataStore.jl", @@ -30,7 +32,9 @@ makedocs( ), pages = [ "Home" => "index.md", - "Examples" => example_pages, + # "Examples" => example_pages, + "API Reference" => "api.md", ], ) + diff --git a/docs/src/api.md b/docs/src/api.md new file mode 100644 index 0000000..30acbfa --- /dev/null +++ b/docs/src/api.md @@ -0,0 +1,24 @@ +# API Reference + +## Main Functions + +```@docs +hourly +info +``` + +## Installation and CLI + +```@docs +install_era5cli +era5cli_cmd +``` + +## Internal Functions + +```@docs +CopernicusClimateDataStore.build_hourly_cmd +CopernicusClimateDataStore.format_area +CopernicusClimateDataStore.monthly +``` + diff --git a/docs/src/index.md b/docs/src/index.md index aa1cd4c..968a048 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -10,7 +10,7 @@ providing a convenient Julia interface for downloading ERA5 and ERA5-Land data. ```julia using Pkg -Pkg.add(url="https://github.com/YOUR_USERNAME/CopernicusClimateDataStore.jl") +Pkg.add(url="https://github.com/NumericalEarth/CopernicusClimateDataStore.jl") ``` ## CDS Account Setup diff --git a/examples/Project.toml b/examples/Project.toml index 7d6a47c..9537122 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -3,6 +3,9 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" CopernicusClimateDataStore = "bce3f73f-acea-4481-bd86-df89ecc2cb46" NCDatasets = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" +[sources] +CopernicusClimateDataStore = {path = ".."} + [compat] CairoMakie = "0.12, 0.13" NCDatasets = "0.14, 0.15"