From 78ee2f62bbdb4d5a6cf69559059116c238eb13f1 Mon Sep 17 00:00:00 2001 From: EFSTRATIOS NIKIDIS <100240665+enikidis@users.noreply.github.com> Date: Fri, 12 Jun 2026 23:08:14 +0200 Subject: [PATCH 1/3] Refactor Cartopy tutorial CLI --- .gitignore | 1 + Cartopy/README.md | 17 +- Cartopy/cera_cartopy.py | 464 +++++++++++++++++++++++++++++----------- 3 files changed, 352 insertions(+), 130 deletions(-) diff --git a/.gitignore b/.gitignore index 96f02a4..f623005 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Map_Contouring_Matplotlib/Contouring_Tutorial/maxele_contouring.63.nc.2 /NetCDF_quality check_png/*.nc /Analyzing_NetCDF/NetCDF_Tutorial/data/*.nc /Map_Contouring_Matplotlib/Contouring_Tutorial/*.nc +/Cartopy/water_level_stations.csv diff --git a/Cartopy/README.md b/Cartopy/README.md index d049feb..578812f 100644 --- a/Cartopy/README.md +++ b/Cartopy/README.md @@ -29,18 +29,17 @@ Given the relative path to the repository and the name of the Python script, you ```bash cd Cartopy -python3 cera_cartopy.py +wget -O water_level_stations.csv https://cloud.cera.lsu.edu/s/6qamYSWn2FarbLP/download/water_level_stations.csv +python3 cera_cartopy.py water_level_stations.csv +python3 cera_cartopy.py water_level_stations.csv --output station_map.png +python3 cera_cartopy.py water_level_stations.csv --no-show +python3 cera_cartopy.py water_level_stations.csv --title "Water level stations" ``` -If your script requires a CSV file as an argument, you can provide it like this: - -```bash -python3 cera_cartopy.py data.csv -``` +The input CSV file must contain these columns: -Replace `data.csv` with the name of your CSV file or for the case of the example 'water_level_stations.csv'. -```bash -python3 cera_cartopy.py water_level_stations.csv +```text +station_id, lat, lon ``` --- diff --git a/Cartopy/cera_cartopy.py b/Cartopy/cera_cartopy.py index dea49cb..100930d 100644 --- a/Cartopy/cera_cartopy.py +++ b/Cartopy/cera_cartopy.py @@ -2,126 +2,348 @@ # CERA Group, Louisiana State University # Website: https://cera.coastalrisk.live # Github: https://github.com/CERA-GROUP -############################################################################################################ -# -# Import libraries -import sys -import cartopy.feature as cfeature -import cartopy.crs as ccrs -import matplotlib.pyplot as plt -import matplotlib.image as image -from matplotlib.offsetbox import (AnnotationBbox, OffsetImage, TextArea) -import numpy as np -import pandas as pd + +# This script is the command-line companion to the Cartopy notebook. It keeps +# the tutorial flow visible while separating input checks, data loading, and +# plotting into small functions that are easier to test and reuse. + +import argparse +from pathlib import Path import urllib.request -from PIL import Image import warnings -import os -# Checking if CSV file is provided -if len(sys.argv) < 2: - warnings.warn("No CSV file provided. Please provide a CSV file as an argument.") - sys.exit() - -# Checking if the provided CSV file exists and is readable -csv_file = sys.argv[1] -if not os.path.isfile(csv_file) or not os.access(csv_file, os.R_OK): - sys.exit("\033[91mThe provided CSV file does not exist or is not readable. Please provide a valid CSV file.\033[0m") - -# Part 2: Map Visualization -# 2.1 - Understanding the Matplotlib Plot Structure -# Ignore warnings -warnings.filterwarnings('ignore') - -# 2.2 - Creating a basic map with Cartopy -print("\033[92mCreating a basic map with Cartopy - Plate Carrée projection\033[0m") -# Creating a map with Plate Carrée projection and add coastline -ax = plt.axes(projection=ccrs.PlateCarree()) -ax.add_feature(cfeature.COASTLINE) -plt.show() - -print("\033[92mCreating a basic map with Cartopy - Mollweide projection\033[0m") -# Creating a map with Mollweide projection and add coastline -ax = plt.axes(projection=ccrs.Mollweide(central_longitude=-90)) -ax.add_feature(cfeature.COASTLINE) -plt.show() - -# 2.3 - Adding a background image and customize the coastline feature -print("\033[92mCreating a map with Plate Carrée projection, adding customized coastline and a background image\033[0m") -# Creating a map with Plate Carrée projection, add customized coastline and a background image -ax = plt.axes(projection=ccrs.PlateCarree()) -ax.add_feature(cfeature.COASTLINE, linestyle='dotted', linewidth=1, color='red') -ax.stock_img() -plt.show() - -# Part 3: Overlaying Coordinate Points on Maps -# 3.1 - Importing the CSV file -csv_file = sys.argv[1] -df_stations = pd.read_csv(csv_file) - -# 3.2 - Exploring the Pandas data frame -print("\033[92mPrinting the Pandas data frame\033[0m") -df_stations.info() - -# 3.3 - Mapping the point data -lon = df_stations['lon'][:] -lat = df_stations['lat'][:] - -plt.plot(lon, lat, marker='o', linewidth=0) -plt.show() - -# 3.4 - Adding the points to the background map -print("\033[92mAdding the points to the background map\033[0m") -plt.figure(figsize=(12,6)) -ax = plt.axes(projection=ccrs.PlateCarree()) -ax.set_extent([-45, -120, 5, 50]) - -ax.add_feature(cfeature.COASTLINE.with_scale('50m'), linewidth=.6) -ax.add_feature(cfeature.OCEAN.with_scale('50m'), color='#EDFBFF') -ax.add_feature(cfeature.LAND.with_scale('50m'), color='#FBF5EA') -ax.add_feature(cfeature.LAKES.with_scale('50m'), color='#EDFBFF') -ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=.5) - -gls = ax.gridlines(draw_labels=True, linestyle='dotted', color='black') -gls.top_labels=False -gls.right_labels=False - -ax.set_title('Background map with water level stations') - -plt.plot(lon, lat, color='r', marker='o', markersize=10, linewidth=0) -plt.show() - -# 3.5 - Limiting the map extent to the area of interest and add point labels -print("\033[92mLimiting the map extent to the area of interest and adding point labels\033[0m") -plt.figure(figsize=(12,6)) -ax = plt.axes(projection=ccrs.PlateCarree()) - -ax.add_feature(cfeature.COASTLINE.with_scale('10m'), linewidth=.6) -ax.add_feature(cfeature.OCEAN.with_scale('10m'), color='#EDFBFF') -ax.add_feature(cfeature.LAND.with_scale('10m'), color='#FBF5EA') -ax.add_feature(cfeature.LAKES.with_scale('10m'), color='#EDFBFF') -ax.add_feature(cfeature.STATES.with_scale('10m'), linewidth=.5) - -gls = ax.gridlines(draw_labels=True, linestyle='dotted', color='black') -gls.top_labels=False -gls.right_labels=False - -ax.set_title('Background map with water level stations') - -plt.plot(lon, lat, color='r', marker='o', markersize=10, linewidth=0) - -station_id = df_stations['station_id'][:] -for i, txt in enumerate(station_id): - ax.annotate(txt, (lon[i]+0.2, lat[i])) - -plt.xlim((lon.min()-1, lon.max()+1)) -plt.ylim((lat.min()-1, lat.max()+1)) - -with urllib.request.urlopen('https://coastalrisk.live/wp-content/uploads/2018/05/cera_50x50.png') as url: - logo = np.array(Image.open(url)) -imagebox = OffsetImage(logo, zoom = 0.5) -ab_img = AnnotationBbox(imagebox, (lon.min()-0.6,lat.max()+0.6), bboxprops =dict(edgecolor='None'), frameon=False) -ab_text = AnnotationBbox(TextArea("cera.coastalrisk.live"), (lon.min()+0.75,lat.max()+0.6)) -ax.add_artist(ab_img) -ax.add_artist(ab_text) - -plt.show() \ No newline at end of file + + +# Shared tutorial settings are constants so map bounds, titles, and required +# CSV columns are defined once and stay consistent across the script. +ATLANTIC_GULF_EXTENT = [-120, -45, 5, 50] +DEFAULT_TITLE = "Background map with water level stations" +LOGO_URL = "https://coastalrisk.live/wp-content/uploads/2018/05/cera_50x50.png" +REQUIRED_COLUMNS = ("station_id", "lat", "lon") + + +def parse_args(): + """Parse command-line options for the Cartopy tutorial script.""" + # argparse gives the script standard help text and clear errors for missing + # or misspelled command-line options. + parser = argparse.ArgumentParser( + description="Create Cartopy maps from a CSV file of station coordinates." + ) + parser.add_argument( + "csv_file", + type=Path, + help="CSV file containing station_id, lat, and lon columns.", + ) + parser.add_argument( + "-o", + "--output", + type=Path, + help="Save the final station map to this image file.", + ) + parser.add_argument( + "--no-show", + action="store_true", + help="Do not open interactive plot windows.", + ) + parser.add_argument( + "--title", + default=DEFAULT_TITLE, + help="Title for the final station map.", + ) + return parser.parse_args() + + +def validate_csv(path): + """Check that the CSV path exists and can be read.""" + # File checks happen before loading data or plotting libraries, so common + # user mistakes fail with a direct message. + if not path.exists(): + raise ValueError(f"CSV file not found: {path}") + if not path.is_file(): + raise ValueError(f"CSV path is not a file: {path}") + + try: + with path.open("r", encoding="utf-8"): + pass + except OSError as exc: + raise ValueError(f"CSV file is not readable: {path}") from exc + + return path + + +def validate_output_path(path): + """Check that the output directory exists before plotting.""" + if path is None: + return None + + # Matplotlib can create the image file, but it will not create missing + # parent folders. Catch that early and report the exact directory problem. + output_dir = path.parent + if output_dir and not output_dir.exists(): + raise ValueError(f"Output directory does not exist: {output_dir}") + + return path + + +def load_station_data(path): + """Load and validate station coordinate data.""" + # Import pandas here so path validation can still run in minimal + # environments and report missing files without needing plotting packages. + import pandas as pd + + try: + # Station IDs may contain leading zeros, so keep them as text for labels. + stations = pd.read_csv(path, dtype={"station_id": str}) + except Exception as exc: + raise ValueError(f"Could not read CSV file: {path}") from exc + + # The plotting functions rely on these three columns. Failing here avoids + # harder-to-debug errors later in Cartopy or Matplotlib. + missing_columns = [ + column for column in REQUIRED_COLUMNS if column not in stations.columns + ] + if missing_columns: + raise ValueError( + "CSV file is missing required column(s): " + + ", ".join(missing_columns) + ) + + if stations.empty: + raise ValueError("CSV file does not contain any station rows.") + + for coordinate in ("lat", "lon"): + # Convert coordinates once after loading, then keep the DataFrame clean + # for all downstream plotting functions. + numeric_values = pd.to_numeric(stations[coordinate], errors="coerce") + invalid_rows = numeric_values.isna() + if invalid_rows.any(): + # Add 2 because pandas indices are zero-based and CSV row 1 is the + # header line. + row_numbers = stations.index[invalid_rows][:5] + 2 + rows_text = ", ".join(str(row) for row in row_numbers) + raise ValueError( + f"Column '{coordinate}' must contain numeric values. " + f"Invalid data found on CSV row(s): {rows_text}" + ) + stations[coordinate] = numeric_values + + return stations + + +def create_overview_maps(show=True): + """Create the simple overview maps used in the tutorial.""" + # These maps mirror the early notebook examples and introduce projections + # before station data is added. + import cartopy.crs as ccrs + import cartopy.feature as cfeature + import matplotlib.pyplot as plt + + print("Creating a basic map with Cartopy - Plate Carree projection") + # Use a separate figure for each example so closing or showing one plot does + # not affect the next tutorial step. + fig = plt.figure(figsize=(8, 4)) + ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree()) + ax.add_feature(cfeature.COASTLINE) + _show_or_close(fig, show) + + print("Creating a basic map with Cartopy - Mollweide projection") + fig = plt.figure(figsize=(8, 4)) + ax = fig.add_subplot( + 1, + 1, + 1, + projection=ccrs.Mollweide(central_longitude=-90), + ) + ax.add_feature(cfeature.COASTLINE) + _show_or_close(fig, show) + + print("Creating a map with customized coastline and a background image") + fig = plt.figure(figsize=(8, 4)) + ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree()) + ax.add_feature(cfeature.COASTLINE, linestyle="dotted", linewidth=1, color="red") + ax.stock_img() + _show_or_close(fig, show) + + +def create_regional_station_map(stations, show=True): + """Create a broad Atlantic/Gulf map with station points.""" + import cartopy.crs as ccrs + import matplotlib.pyplot as plt + + print("Adding station points to the regional background map") + fig = plt.figure(figsize=(12, 6)) + ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree()) + # Cartopy expects extent as [west, east, south, north]. This broad view + # provides geographic context before zooming to the station bounding box. + ax.set_extent(ATLANTIC_GULF_EXTENT, crs=ccrs.PlateCarree()) + + _add_map_features(ax, scale="50m") + _add_gridlines(ax) + + ax.set_title(DEFAULT_TITLE) + ax.scatter( + stations["lon"], + stations["lat"], + color="red", + marker="o", + s=100, + # The input CSV stores station coordinates as longitude/latitude. + transform=ccrs.PlateCarree(), + ) + _show_or_close(fig, show) + + +def create_station_map(stations, title, output=None, show=True): + """Create the final labeled station map and optionally save it.""" + import cartopy.crs as ccrs + import matplotlib.pyplot as plt + + print("Creating the final labeled station map") + fig = plt.figure(figsize=(12, 6)) + ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree()) + + # The final map focuses on the provided data rather than a fixed region, so + # its extent is derived from station coordinates. + ax.set_extent(_station_extent(stations), crs=ccrs.PlateCarree()) + _add_map_features(ax, scale="10m") + _add_gridlines(ax) + + ax.set_title(title) + ax.scatter( + stations["lon"], + stations["lat"], + color="red", + marker="o", + s=100, + # Tell Cartopy that station values are already lon/lat coordinates. + transform=ccrs.PlateCarree(), + ) + + # Labels are offset slightly so they do not sit directly on top of markers. + for station in stations.itertuples(index=False): + ax.text( + station.lon + 0.2, + station.lat, + str(station.station_id), + transform=ccrs.PlateCarree(), + ) + + _add_cera_logo(ax, stations["lon"].min(), stations["lat"].max()) + + if output is not None: + # Only the final station map is saved; the overview maps remain tutorial + # demonstrations. + fig.savefig(output, dpi=150, bbox_inches="tight") + print(f"Saved final station map to {output}") + + _show_or_close(fig, show) + + +def _add_map_features(ax, scale): + """Add common Cartopy background features.""" + import cartopy.feature as cfeature + + # Keeping repeated Natural Earth features in one helper makes the regional + # and final maps visually consistent. + ax.add_feature(cfeature.COASTLINE.with_scale(scale), linewidth=0.6) + ax.add_feature(cfeature.OCEAN.with_scale(scale), color="#EDFBFF") + ax.add_feature(cfeature.LAND.with_scale(scale), color="#FBF5EA") + ax.add_feature(cfeature.LAKES.with_scale(scale), color="#EDFBFF") + ax.add_feature(cfeature.STATES.with_scale(scale), linewidth=0.5) + + +def _add_gridlines(ax): + """Add labeled gridlines to a Cartopy axis.""" + gridlines = ax.gridlines(draw_labels=True, linestyle="dotted", color="black") + # Top and right labels duplicate the bottom and left labels on a simple map. + gridlines.top_labels = False + gridlines.right_labels = False + + +def _station_extent(stations, buffer_degrees=1.0): + """Return a [west, east, south, north] extent around the stations.""" + # A small buffer keeps markers and labels from touching the map edges. + west = stations["lon"].min() - buffer_degrees + east = stations["lon"].max() + buffer_degrees + south = stations["lat"].min() - buffer_degrees + north = stations["lat"].max() + buffer_degrees + return [west, east, south, north] + + +def _add_cera_logo(ax, lon_min, lat_max): + """Add the CERA logo when it is reachable.""" + import numpy as np + from PIL import Image + from matplotlib.offsetbox import AnnotationBbox, OffsetImage, TextArea + + try: + # The logo is decorative context. If the network is unavailable, the map + # should still be produced and the user should see a warning. + with urllib.request.urlopen(LOGO_URL) as url: + logo = np.array(Image.open(url)) + except Exception as exc: + warnings.warn(f"Could not load CERA logo: {exc}") + return + + imagebox = OffsetImage(logo, zoom=0.5) + logo_box = AnnotationBbox( + imagebox, + (lon_min - 0.6, lat_max + 0.6), + bboxprops={"edgecolor": "None"}, + frameon=False, + ) + label_box = AnnotationBbox( + TextArea("cera.coastalrisk.live"), + (lon_min + 0.75, lat_max + 0.6), + ) + ax.add_artist(logo_box) + ax.add_artist(label_box) + + +def _show_or_close(fig, show): + """Show a figure interactively or close it for batch runs.""" + import matplotlib.pyplot as plt + + if show: + plt.show() + else: + # Closing figures in --no-show mode prevents memory growth and avoids + # opening windows during automated runs. + plt.close(fig) + + +def main(): + args = parse_args() + show_plots = not args.no_show + + try: + # Validate all user-controlled inputs before spending time on plotting. + csv_path = validate_csv(args.csv_file) + output_path = validate_output_path(args.output) + stations = load_station_data(csv_path) + except ValueError as exc: + raise SystemExit(f"Error: {exc}") from exc + + if not show_plots: + import matplotlib + + # The Agg backend renders images without a GUI, which is useful for + # servers, scripts, and reproducible command-line runs. + matplotlib.use("Agg") + + print("Printing the station data frame") + stations.info() + + # Keep the notebook's learning sequence: simple projection examples first, + # then a regional station view, and finally the labeled output map. + create_overview_maps(show=show_plots) + create_regional_station_map(stations, show=show_plots) + create_station_map( + stations, + title=args.title, + output=output_path, + show=show_plots, + ) + + +if __name__ == "__main__": + main() From 285e2ec040a043c039b054a1079cddfbe6370a74 Mon Sep 17 00:00:00 2001 From: EFSTRATIOS NIKIDIS <100240665+enikidis@users.noreply.github.com> Date: Fri, 12 Jun 2026 23:49:25 +0200 Subject: [PATCH 2/3] docs: add shared tutorial environment and reproducible notebook setup Add shared environment files and update the tutorial documentation so users can clone the repository, create one environment, and run both the NetCDF and Cartopy tutorials without ad hoc package installation. Add repository-level dependency files: - Add environment.yml for the recommended Conda/Mamba workflow. - Use the cera-tutorials environment name. - Use conda-forge for geospatial/scientific dependencies. - Include Python 3.12, NumPy, pandas, Matplotlib, netCDF4, Cartopy, Pillow, ipywidgets, JupyterLab, notebook, and ipykernel. - Add requirements.txt as a pip fallback with the equivalent Python packages. Update documentation: - Add root README setup instructions for Conda/Mamba and pip fallback. - Add root README run commands for the NetCDF and Cartopy tutorials. - Update Cartopy README to point users at the shared root environment. - Update Analyzing_NetCDF README with local run instructions. - Fix stale NetCDF tutorial links to point at files in this repository. - Fix the "Jupiter Notebook" typo to "Jupyter Notebook". Clean up notebook environment handling: - Replace executable pip install cells in both notebooks with non-mutating setup guidance. - Document the shared cera-tutorials kernel inside the notebooks. - Keep pip fallback commands as commented examples only. - Standardize both notebooks on the Python (cera-tutorials) kernel. - Avoid automatic package installation when users run all notebook cells. Improve tutorial data handling: - Keep README download commands simple for copy/paste terminal usage. - Add notebook-only checks before downloading tutorial data files. - Skip re-downloading water_level_stations.csv in the Cartopy notebook when it already exists. - Skip re-downloading maxele.63.nc in the NetCDF notebook when it already exists. Update ignore rules: - Ignore Python cache files and notebook checkpoints. - Ignore local virtual environments and .env files. - Ignore Cartopy generated map images. - Ignore NetCDF tutorial output text files. - Ignore downloaded NetCDF sample files, including repeated wget suffixes. Validation: - Verified both notebooks are valid JSON with jq. - Verified no active notebook pip install commands remain. - Ran git diff --check successfully. - Ran py_compile successfully for the Cartopy and NetCDF Python scripts. --- .gitignore | 18 ++++++++ .../NetCDF_Tutorial/netCDF4.ipynb | 39 ++++++++++++++--- Analyzing_NetCDF/README.md | 24 +++++++++-- Cartopy/Cartopy.ipynb | 32 +++++++++----- Cartopy/README.md | 7 +++ README.md | 43 +++++++++++++++++-- environment.yml | 15 +++++++ requirements.txt | 10 +++++ 8 files changed, 165 insertions(+), 23 deletions(-) create mode 100644 environment.yml create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index f623005..2f34128 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,23 @@ Map_Contouring_Matplotlib/Contouring_Tutorial/maxele_contouring.63.nc.1 Map_Contouring_Matplotlib/Contouring_Tutorial/maxele_contouring.63.nc.2 /NetCDF_quality check_png/*.nc /Analyzing_NetCDF/NetCDF_Tutorial/data/*.nc +/Analyzing_NetCDF/NetCDF_Tutorial/maxele.63.nc +/Analyzing_NetCDF/NetCDF_Tutorial/maxele.63.nc.* /Map_Contouring_Matplotlib/Contouring_Tutorial/*.nc /Cartopy/water_level_stations.csv + +# Python generated files +__pycache__/ +*.py[cod] +.ipynb_checkpoints/ + +# Local environments +.venv/ +venv/ +.env + +# Tutorial generated files +.cartopy_test/ +Cartopy/station_map.png +Cartopy/*_map.png +Analyzing_NetCDF/NetCDF_Tutorial/output_*.txt diff --git a/Analyzing_NetCDF/NetCDF_Tutorial/netCDF4.ipynb b/Analyzing_NetCDF/NetCDF_Tutorial/netCDF4.ipynb index e178ef6..0432e41 100644 --- a/Analyzing_NetCDF/NetCDF_Tutorial/netCDF4.ipynb +++ b/Analyzing_NetCDF/NetCDF_Tutorial/netCDF4.ipynb @@ -142,8 +142,14 @@ "metadata": {}, "outputs": [], "source": [ - "pip install numpy\n", - "pip install netCDF4" + "# Recommended local setup: create the shared repository environment from the repository root.\n", + "# conda env create -f environment.yml\n", + "# conda activate cera-tutorials\n", + "# python -m ipykernel install --user --name cera-tutorials --display-name \"Python (cera-tutorials)\"\n", + "#\n", + "# Then open this notebook with the Python (cera-tutorials) kernel.\n", + "# Pip fallback only if you are not using the Conda environment:\n", + "# %pip install -r ../../requirements.txt" ] }, { @@ -180,11 +186,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "maxele.63.nc already exists; skipping download.\n" + ] + } + ], "source": [ - "!wget https://cloud.cera.lsu.edu/s/7PfqfzWDj285Afw/download/maxele.63.nc" + "from pathlib import Path\n", + "from urllib.request import urlretrieve\n", + "\n", + "netcdf_file = Path(\"maxele.63.nc\")\n", + "if netcdf_file.exists():\n", + " print(f\"{netcdf_file} already exists; skipping download.\")\n", + "else:\n", + " urlretrieve(\n", + " \"https://cloud.cera.lsu.edu/s/7PfqfzWDj285Afw/download/maxele.63.nc\",\n", + " netcdf_file,\n", + " )\n", + " print(f\"Downloaded {netcdf_file}.\")" ] }, { @@ -974,7 +999,7 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "cera-tutorials", "language": "python", "name": "python3" }, @@ -988,7 +1013,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.12.13" }, "orig_nbformat": 4 }, diff --git a/Analyzing_NetCDF/README.md b/Analyzing_NetCDF/README.md index 52213fd..5620c1e 100644 --- a/Analyzing_NetCDF/README.md +++ b/Analyzing_NetCDF/README.md @@ -15,18 +15,36 @@ Scientific geospatial datasets like climatological or oceanographic model result ## Getting Started -### Jupiter Notebook +### Jupyter Notebook [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/CERA-GROUP/Tutorials/blob/main/Analyzing_NetCDF/NetCDF_Tutorial/netCDF4.ipynb) ### Python standalone -* Direct python script [file](https://github.com/CERA-GROUP/CERA_Tutorials/blob/main/Notebook/net_CDF4.py). +* Direct python script [file](NetCDF_Tutorial/net_CDF4.py). -* Check the tutorial [instructions](https://github.com/CERA-GROUP/CERA_Tutorials/blob/main/Notebook/netCDF4.ipynb). +* Check the tutorial [instructions](NetCDF_Tutorial/netCDF4.ipynb). * Example ADCIRC NetCDF file [input file](https://cloud.cera.lsu.edu/s/7PfqfzWDj285Afw/download/maxele.63.nc). +## Running the tutorial + +From the repository root: + +```bash +conda env create -f environment.yml +conda activate cera-tutorials +``` + +Then download the example data and run the notebook or standalone script: + +```bash +cd Analyzing_NetCDF/NetCDF_Tutorial +wget -O maxele.63.nc https://cloud.cera.lsu.edu/s/7PfqfzWDj285Afw/download/maxele.63.nc +jupyter lab netCDF4.ipynb +python3 net_CDF4.py +``` + --- ### Contact Us If you have any questions, suggestions, or would like to get in touch with our team, please feel free to reach out to us. Together, we can make a significant impact in enhancing our preparedness and resilience to storm-related challenges. diff --git a/Cartopy/Cartopy.ipynb b/Cartopy/Cartopy.ipynb index 18fc1da..9ed421f 100644 --- a/Cartopy/Cartopy.ipynb +++ b/Cartopy/Cartopy.ipynb @@ -142,13 +142,14 @@ "metadata": {}, "outputs": [], "source": [ - "#First, we need to install the Python libraries\n", - "!pip install cartopy\n", - "!pip install matplotlib\n", - "!pip install numpy\n", - "!pip install pandas\n", - "#optional only for the last part of the notebook\n", - "!pip install ipywidgets" + "# Recommended local setup: create the shared repository environment from the repository root.\n", + "# conda env create -f environment.yml\n", + "# conda activate cera-tutorials\n", + "# python -m ipykernel install --user --name cera-tutorials --display-name \"Python (cera-tutorials)\"\n", + "#\n", + "# Then open this notebook with the Python (cera-tutorials) kernel.\n", + "# Pip fallback only if you are not using the Conda environment:\n", + "# %pip install -r ../requirements.txt" ] }, { @@ -166,7 +167,18 @@ "metadata": {}, "outputs": [], "source": [ - "!wget https://cloud.cera.lsu.edu/s/6qamYSWn2FarbLP/download/water_level_stations.csv" + "from pathlib import Path\n", + "from urllib.request import urlretrieve\n", + "\n", + "csv_file = Path(\"water_level_stations.csv\")\n", + "if csv_file.exists():\n", + " print(f\"{csv_file} already exists; skipping download.\")\n", + "else:\n", + " urlretrieve(\n", + " \"https://cloud.cera.lsu.edu/s/6qamYSWn2FarbLP/download/water_level_stations.csv\",\n", + " csv_file,\n", + " )\n", + " print(f\"Downloaded {csv_file}.\")" ] }, { @@ -844,9 +856,9 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "Python (cera-tutorials)", "language": "python", - "name": "python3" + "name": "cera-tutorials" }, "language_info": { "codemirror_mode": { diff --git a/Cartopy/README.md b/Cartopy/README.md index 578812f..8bb6f2b 100644 --- a/Cartopy/README.md +++ b/Cartopy/README.md @@ -14,6 +14,13 @@ This tutorial is an integral part of the CERA Storm Analysis Tutorials Repositor ## Getting Started +Create and activate the shared environment from the repository root before running the notebook or standalone script: + +```bash +conda env create -f environment.yml +conda activate cera-tutorials +``` + ### Jupyter Notebook [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/CERA-GROUP/Tutorials/blob/main/Cartopy/Cartopy.ipynb) diff --git a/README.md b/README.md index 49820e1..8d81cef 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,54 @@ Our objective is to simulate and visualize storm surge using advanced computer m --- -## Tutorials - Unveiling the Storm -### [A Beginner's Guide to Analyzing ADCIRC NetCDF Data with Python](https://github.com/CERA-GROUP/Tutorials/tree/main/Analyzing_NetCDF) +## Environment setup +The tutorials use Python, Jupyter, NumPy, pandas, Matplotlib, netCDF4, Cartopy, Pillow, and ipywidgets. + +The recommended setup uses Conda/Mamba with packages from conda-forge: + +```bash +conda env create -f environment.yml +conda activate cera-tutorials +python -m ipykernel install --user --name cera-tutorials --display-name "Python (cera-tutorials)" +``` + +Alternatively, with pip: + +```bash +python3 -m venv .venv +source .venv/bin/activate +python3 -m pip install -r requirements.txt +``` + +Cartopy and netCDF4 depend on compiled libraries, so Conda/Mamba is recommended if pip installation fails. + +--- + +## Tutorials +### [A Beginner's Guide to Analyzing ADCIRC NetCDF Data with Python](Analyzing_NetCDF) The NetCDF format stores large datasets in a well-organized manner that allows a successful data analysis in a user-friendly way. This tutorial explains the structure of a NetCDF file using the Python library NetCDF4. -### [Matplotlib Contouring for ADCIRC NetCDF Data](https://github.com/CERA-GROUP/Tutorials/tree/main/Map_Contouring_Matplotlib) +```bash +cd Analyzing_NetCDF/NetCDF_Tutorial +wget -O maxele.63.nc https://cloud.cera.lsu.edu/s/7PfqfzWDj285Afw/download/maxele.63.nc +jupyter lab netCDF4.ipynb +python3 net_CDF4.py +``` + +### [Matplotlib Contouring for ADCIRC NetCDF Data](Map_Contouring_Matplotlib) The Coastal Emergency Risks Assessment (CERA) tutorial leverages Matplotlib to visualize NetCDF data, offering insights into coastal phenomena for the Northern Gulf and the Atlantic Coast regions. ### [Geospatial Data Visualization: Introduction to Cartopy](Cartopy) The CERA Storm Analysis Tutorials, maintained by the LSU CERA-Group, provide guides for simulating and visualizing storm surge using advanced computer models and technologies, with tutorials on analyzing ADCIRC NetCDF data with Python, Matplotlib contouring, and geospatial data visualization using Cartopy. +```bash +cd Cartopy +wget -O water_level_stations.csv https://cloud.cera.lsu.edu/s/6qamYSWn2FarbLP/download/water_level_stations.csv +jupyter lab Cartopy.ipynb +python3 cera_cartopy.py water_level_stations.csv --output station_map.png --no-show +``` + --- #### Contact Us If you have any questions, suggestions, or would like to get in touch with our team, please feel free to reach out to us. Together, we can make a significant impact in enhancing our preparedness and resilience to storm-related challenges. diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..a379a18 --- /dev/null +++ b/environment.yml @@ -0,0 +1,15 @@ +name: cera-tutorials +channels: + - conda-forge +dependencies: + - python=3.12 + - numpy + - pandas + - matplotlib + - netcdf4 + - cartopy + - pillow + - ipywidgets + - jupyterlab + - notebook + - ipykernel diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3023608 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +cartopy +ipykernel +ipywidgets +jupyterlab +matplotlib +netCDF4 +notebook +numpy +pandas +Pillow From 6ee031d32f9b9f843b1a98d9accaddb7b2cd7ada Mon Sep 17 00:00:00 2001 From: EFSTRATIOS NIKIDIS <100240665+enikidis@users.noreply.github.com> Date: Sat, 13 Jun 2026 00:08:57 +0200 Subject: [PATCH 3/3] fix: update command syntax in README for Cartopy tutorial --- Cartopy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartopy/README.md b/Cartopy/README.md index 8bb6f2b..6444d18 100644 --- a/Cartopy/README.md +++ b/Cartopy/README.md @@ -39,7 +39,7 @@ cd Cartopy wget -O water_level_stations.csv https://cloud.cera.lsu.edu/s/6qamYSWn2FarbLP/download/water_level_stations.csv python3 cera_cartopy.py water_level_stations.csv python3 cera_cartopy.py water_level_stations.csv --output station_map.png -python3 cera_cartopy.py water_level_stations.csv --no-show +python3 cera_cartopy.py water_level_stations.csv --output station_map.png --no-show python3 cera_cartopy.py water_level_stations.csv --title "Water level stations" ```