cavapy 0.3.1__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cavapy
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: CAVA Python package. Retrive climate data.
5
5
  License: MIT
6
6
  Author: Riccardo Soldan
@@ -12,29 +12,30 @@ Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
- Requires-Dist: bottleneck (>=1.4.2,<2.0.0)
16
- Requires-Dist: dask (>=2024.11.2,<2025.0.0)
17
- Requires-Dist: geopandas (>=0.14.4,<0.15.0)
18
- Requires-Dist: llvmlite (==0.43.0)
19
- Requires-Dist: matplotlib (>=3.9.2,<4.0.0)
20
- Requires-Dist: netcdf4 (>=1.7.2,<2.0.0)
21
- Requires-Dist: xclim (>=0.53.2,<0.54.0)
15
+ Requires-Dist: bottleneck
16
+ Requires-Dist: cartopy
17
+ Requires-Dist: dask
18
+ Requires-Dist: llvmlite
19
+ Requires-Dist: matplotlib
20
+ Requires-Dist: netcdf4
21
+ Requires-Dist: seaborn
22
+ Requires-Dist: xclim
23
+ Requires-Dist: xsdba
22
24
  Project-URL: Repository, https://github.com/Risk-Team/cavapy
23
25
  Description-Content-Type: text/markdown
24
26
 
25
- <h1 align="center">
26
- <br>
27
- <h2 align="center">cavapy: CORDEX-CORE Climate Data Access Simplified</h2>
28
- <br>
27
+ <h1 align="center">cavapy: CORDEX-CORE Climate Data Access Simplified</h1>
28
+
29
29
  <div align="center">
30
- <img src="https://img.shields.io/pepy/dt/cavapy?style=plastic&label=Total%20Downloads" alt="Total downloads">
31
- <img src="https://img.shields.io/pypi/dm/cavapy?label=Monthly%20Downloads" alt="Monthly downloads">
32
- <img src="https://img.shields.io/pypi/v/cavapy?label=pypi%20package" alt="version" style="display: inline-block;">
33
- <img src="https://shields.io/badge/dependencies-Python_3.11+-blue" alt="version" style="display: inline-block;">
30
+ <img src="https://img.shields.io/pepy/dt/cavapy?style=plastic&label=Total%20Downloads" alt="Total downloads">
31
+ <img src="https://img.shields.io/pypi/v/cavapy?label=PyPI%20package&style=plastic" alt="PyPI version" style="display:inline-block;">
32
+ <a href="https://www.fao.org/contact-us/data-protection-and-privacy/en/" aria-label="FAO Data Protection and Privacy policy">
33
+ <img src="https://img.shields.io/badge/Data%20Protection%20%26%20Privacy-FAO-blue" alt="FAO Data Protection and Privacy" style="display:inline-block;">
34
+ </a>
35
+ <br><br>
36
+ ⭐ If you like this project, please <a href="https://github.com/risk-team/cavapy/stargazers">give it a star on GitHub</a>!
34
37
  </div>
35
- </h1
36
38
 
37
- ---
38
39
 
39
40
  --------------------------------------------------------------------------------------------------
40
41
  **Check GitHub issues for known servers' downtimes**
@@ -100,7 +101,6 @@ The get_climate_data function performs automatically:
100
101
  Depending on the interest, downloading climate data can be done in a few different ways. Note that GCM stands for General Circulation Model while RCM stands for Regional Climate Model. As the climate data comes from the CORDEX-CORE initiative, users can choose between 3 different GCMs downscaled with two RCMs. In total, there are six simulations for any given domain (except for CAS-22 where only three are available).
101
102
  Since bias-correction requires both the historical run of the CORDEX model and the observational dataset (in this case ERA5), even when the historical argument is set to False, the historical run will be used for learning the bias correction factor.
102
103
 
103
- It takes about 10 minutes to run each of the tasks below. For bigger areas/country, the computational time increases.
104
104
 
105
105
  ### Bias-corrected climate projections
106
106
  **By default all available climate variables are used. You can specify a subset with the variable argument**
@@ -131,3 +131,55 @@ import cavapy
131
131
  Togo_climate_data = cavapy.get_climate_data(country="Togo", variables=["tasmax", "pr"], obs=True, years_obs=range(1980,2019))
132
132
  ```
133
133
 
134
+ ## Plotting Functionality
135
+
136
+ `cavapy` now includes built-in plotting functions to easily visualize your climate data as maps and time series. The plotting functions work seamlessly with the data returned by `get_climate_data()`.
137
+
138
+ ### Available Plotting Functions
139
+
140
+ - **`plot_spatial_map()`**: Create spatial maps of climate variables
141
+ - **`plot_time_series()`**: Generate time series plots with trend analysis
142
+
143
+ ### Plotting Examples
144
+
145
+ #### Spatial Maps
146
+ ```python
147
+ import cavapy
148
+
149
+ # Get climate data
150
+ data = cavapy.get_climate_data(country="Togo", obs=True, years_obs=range(1990, 2011))
151
+
152
+ # Plot mean temperature map for a specific period
153
+ fig = cavapy.plot_spatial_map(
154
+ data['tasmax'],
155
+ time_period=(2000, 2010),
156
+ title="Mean Max Temperature 2000-2010",
157
+ cmap="Reds"
158
+ )
159
+ ```
160
+
161
+ <div align="center">
162
+ <img src="figures/spatial_map_temperature.png" alt="Spatial Temperature Map" width="600">
163
+ <br><em>Example spatial map showing mean maximum temperature in Togo (2000-2010)</em>
164
+ </div>
165
+
166
+ #### Time Series Analysis
167
+ ```python
168
+ # Plot precipitation time series with trend analysis
169
+ fig_precip = cavapy.plot_time_series(
170
+ data['pr'],
171
+ title="Precipitation Time Series - Togo (1990-2000)",
172
+ trend_line=True,
173
+ ylabel="Annual Precipitation (mm)",
174
+ aggregation="sum",
175
+ figsize=(12, 6)
176
+ )
177
+ ```
178
+
179
+ <div align="center">
180
+ <img src="figures/time_series_precipitation.png" alt="Precipitation Trends" width="600">
181
+ <br><em>Example time series plot showing precipitation trends in Togo (1990-2011) with trend line</em>
182
+ </div>
183
+
184
+
185
+
@@ -1,16 +1,15 @@
1
- <h1 align="center">
2
- <br>
3
- <h2 align="center">cavapy: CORDEX-CORE Climate Data Access Simplified</h2>
4
- <br>
1
+ <h1 align="center">cavapy: CORDEX-CORE Climate Data Access Simplified</h1>
2
+
5
3
  <div align="center">
6
- <img src="https://img.shields.io/pepy/dt/cavapy?style=plastic&label=Total%20Downloads" alt="Total downloads">
7
- <img src="https://img.shields.io/pypi/dm/cavapy?label=Monthly%20Downloads" alt="Monthly downloads">
8
- <img src="https://img.shields.io/pypi/v/cavapy?label=pypi%20package" alt="version" style="display: inline-block;">
9
- <img src="https://shields.io/badge/dependencies-Python_3.11+-blue" alt="version" style="display: inline-block;">
4
+ <img src="https://img.shields.io/pepy/dt/cavapy?style=plastic&label=Total%20Downloads" alt="Total downloads">
5
+ <img src="https://img.shields.io/pypi/v/cavapy?label=PyPI%20package&style=plastic" alt="PyPI version" style="display:inline-block;">
6
+ <a href="https://www.fao.org/contact-us/data-protection-and-privacy/en/" aria-label="FAO Data Protection and Privacy policy">
7
+ <img src="https://img.shields.io/badge/Data%20Protection%20%26%20Privacy-FAO-blue" alt="FAO Data Protection and Privacy" style="display:inline-block;">
8
+ </a>
9
+ <br><br>
10
+ ⭐ If you like this project, please <a href="https://github.com/risk-team/cavapy/stargazers">give it a star on GitHub</a>!
10
11
  </div>
11
- </h1
12
12
 
13
- ---
14
13
 
15
14
  --------------------------------------------------------------------------------------------------
16
15
  **Check GitHub issues for known servers' downtimes**
@@ -76,7 +75,6 @@ The get_climate_data function performs automatically:
76
75
  Depending on the interest, downloading climate data can be done in a few different ways. Note that GCM stands for General Circulation Model while RCM stands for Regional Climate Model. As the climate data comes from the CORDEX-CORE initiative, users can choose between 3 different GCMs downscaled with two RCMs. In total, there are six simulations for any given domain (except for CAS-22 where only three are available).
77
76
  Since bias-correction requires both the historical run of the CORDEX model and the observational dataset (in this case ERA5), even when the historical argument is set to False, the historical run will be used for learning the bias correction factor.
78
77
 
79
- It takes about 10 minutes to run each of the tasks below. For bigger areas/country, the computational time increases.
80
78
 
81
79
  ### Bias-corrected climate projections
82
80
  **By default all available climate variables are used. You can specify a subset with the variable argument**
@@ -106,3 +104,55 @@ Togo_climate_data = cavapy.get_climate_data(country="Togo", variables=["tasmax",
106
104
  import cavapy
107
105
  Togo_climate_data = cavapy.get_climate_data(country="Togo", variables=["tasmax", "pr"], obs=True, years_obs=range(1980,2019))
108
106
  ```
107
+
108
+ ## Plotting Functionality
109
+
110
+ `cavapy` now includes built-in plotting functions to easily visualize your climate data as maps and time series. The plotting functions work seamlessly with the data returned by `get_climate_data()`.
111
+
112
+ ### Available Plotting Functions
113
+
114
+ - **`plot_spatial_map()`**: Create spatial maps of climate variables
115
+ - **`plot_time_series()`**: Generate time series plots with trend analysis
116
+
117
+ ### Plotting Examples
118
+
119
+ #### Spatial Maps
120
+ ```python
121
+ import cavapy
122
+
123
+ # Get climate data
124
+ data = cavapy.get_climate_data(country="Togo", obs=True, years_obs=range(1990, 2011))
125
+
126
+ # Plot mean temperature map for a specific period
127
+ fig = cavapy.plot_spatial_map(
128
+ data['tasmax'],
129
+ time_period=(2000, 2010),
130
+ title="Mean Max Temperature 2000-2010",
131
+ cmap="Reds"
132
+ )
133
+ ```
134
+
135
+ <div align="center">
136
+ <img src="figures/spatial_map_temperature.png" alt="Spatial Temperature Map" width="600">
137
+ <br><em>Example spatial map showing mean maximum temperature in Togo (2000-2010)</em>
138
+ </div>
139
+
140
+ #### Time Series Analysis
141
+ ```python
142
+ # Plot precipitation time series with trend analysis
143
+ fig_precip = cavapy.plot_time_series(
144
+ data['pr'],
145
+ title="Precipitation Time Series - Togo (1990-2000)",
146
+ trend_line=True,
147
+ ylabel="Annual Precipitation (mm)",
148
+ aggregation="sum",
149
+ figsize=(12, 6)
150
+ )
151
+ ```
152
+
153
+ <div align="center">
154
+ <img src="figures/time_series_precipitation.png" alt="Precipitation Trends" width="600">
155
+ <br><em>Example time series plot showing precipitation trends in Togo (1990-2011) with trend line</em>
156
+ </div>
157
+
158
+
@@ -5,17 +5,27 @@ from functools import partial
5
5
  import logging
6
6
  import warnings
7
7
 
8
- warnings.filterwarnings(
9
- "ignore",
10
- category=FutureWarning,
11
- message=".*geopandas.dataset module is deprecated.*",
12
- )
13
- import geopandas as gpd # noqa: E402
14
8
  import pandas as pd # noqa: E402
15
9
  import xarray as xr # noqa: E402
16
10
  import numpy as np # noqa: E402
17
- from xclim import sdba # noqa: E402
18
-
11
+ import xsdba as sdba # noqa: E402
12
+ import matplotlib.pyplot as plt # noqa: E402
13
+ import matplotlib.dates as mdates # noqa: E402
14
+ import seaborn as sns # noqa: E402
15
+ from datetime import datetime # noqa: E402
16
+ from typing import Union, List, Tuple, Optional # noqa: E402
17
+
18
+ import cartopy.crs as ccrs # noqa: E402
19
+ import cartopy.feature as cfeature # noqa: E402
20
+ import cartopy.io.shapereader as shpreader # noqa: E402
21
+
22
+ # Suppress cartopy download warnings for Natural Earth data
23
+ try:
24
+ from cartopy.io import DownloadWarning
25
+ warnings.filterwarnings('ignore', category=DownloadWarning)
26
+ except ImportError:
27
+ # Fallback to suppressing all UserWarnings from cartopy.io
28
+ warnings.filterwarnings('ignore', category=UserWarning, module='cartopy.io')
19
29
 
20
30
  logger = logging.getLogger("climate")
21
31
  logger.handlers = [] # Remove any existing handlers
@@ -289,6 +299,48 @@ def _validate_urls(
289
299
  log_obs.info(f"{ERA5_DATA_REMOTE_URL}")
290
300
 
291
301
 
302
+ def _get_country_bounds(country_name: str) -> tuple[float, float, float, float]:
303
+ """
304
+ Get country bounding box using cartopy's Natural Earth data.
305
+
306
+ Args:
307
+ country_name: Name of the country
308
+
309
+ Returns:
310
+ tuple: (minx, miny, maxx, maxy) bounding box
311
+
312
+ Raises:
313
+ ValueError: If country not found
314
+ """
315
+ # Use Natural Earth countries dataset via cartopy
316
+ countries_feature = cfeature.NaturalEarthFeature(
317
+ 'cultural', 'admin_0_countries', '50m'
318
+ )
319
+
320
+ # Get the actual shapefile path from the feature
321
+ shapefile_path = countries_feature.with_scale('50m').geometries()
322
+
323
+ # Search for the country using Natural Earth records
324
+ for country_record in shpreader.Reader(shpreader.natural_earth(resolution='50m', category='cultural', name='admin_0_countries')).records():
325
+ # Try multiple name fields for better matching
326
+ country_names = [
327
+ country_record.attributes.get('NAME', ''),
328
+ country_record.attributes.get('NAME_LONG', ''),
329
+ country_record.attributes.get('ADMIN', ''),
330
+ country_record.attributes.get('NAME_EN', '')
331
+ ]
332
+
333
+ if any(name.lower() == country_name.lower() for name in country_names if name):
334
+ return country_record.geometry.bounds
335
+
336
+ # If not found, check for capitalization issue
337
+ if country_name and country_name[0].islower():
338
+ capitalized = country_name.capitalize()
339
+ raise ValueError(f"Country '{country_name}' not found. Try capitalizing the first letter: '{capitalized}'")
340
+ else:
341
+ raise ValueError(f"Country '{country_name}' is unknown.")
342
+
343
+
292
344
  def _geo_localize(
293
345
  country: str = None,
294
346
  xlim: tuple[float, float] = None,
@@ -302,17 +354,8 @@ def _geo_localize(
302
354
  raise ValueError(
303
355
  "Specify either a country or bounding box limits (xlim, ylim), but not both."
304
356
  )
305
- # Load country shapefile and extract bounds
306
- world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
307
- country_shp = world[world.name == country]
308
- if country_shp.empty:
309
- # Check if it's a capitalization issue
310
- if country and country[0].islower():
311
- capitalized = country.capitalize()
312
- raise ValueError(f"Country '{country}' not found. Try capitalizing the first letter: '{capitalized}'")
313
- else:
314
- raise ValueError(f"Country '{country}' is unknown.")
315
- bounds = country_shp.total_bounds # [minx, miny, maxx, maxy]
357
+
358
+ bounds = _get_country_bounds(country)
316
359
  xlim, ylim = (bounds[0], bounds[2]), (bounds[1], bounds[3])
317
360
  elif not (xlim and ylim):
318
361
  raise ValueError(
@@ -810,6 +853,242 @@ def _download_data(
810
853
  return ds_cropped
811
854
 
812
855
 
856
+ # =============================================================================
857
+ # PLOTTING FUNCTIONS
858
+ # =============================================================================
859
+
860
+ def plot_spatial_map(
861
+ data: xr.DataArray,
862
+ time_period: Optional[Tuple[int, int]] = None,
863
+ aggregation: str = "mean",
864
+ title: Optional[str] = None,
865
+ cmap: str = "viridis",
866
+ figsize: Tuple[int, int] = (12, 8),
867
+ show_countries: bool = True,
868
+ save_path: Optional[str] = None,
869
+ **kwargs
870
+ ) -> plt.Figure:
871
+ """
872
+ Create a spatial map visualization of climate data.
873
+
874
+ Args:
875
+ data (xr.DataArray): Climate data array with latitude/longitude coordinates
876
+ time_period (tuple, optional): (start_year, end_year) to subset data. If None, uses all data
877
+ aggregation (str): Temporal aggregation method ('mean', 'sum', 'min', 'max', 'std')
878
+ title (str, optional): Plot title. If None, auto-generated
879
+ cmap (str): Colormap name
880
+ figsize (tuple): Figure size (width, height)
881
+ show_countries (bool): Whether to show country boundaries
882
+ save_path (str, optional): Path to save the plot
883
+ **kwargs: Additional arguments passed to the plot function
884
+
885
+ Returns:
886
+ matplotlib.figure.Figure: The created figure
887
+
888
+ Examples:
889
+ # Plot mean temperature for 2020-2030
890
+ fig = plot_spatial_map(data['tasmax'], time_period=(2020, 2030),
891
+ title="Mean Max Temperature 2020-2030")
892
+
893
+ # Plot precipitation sum with custom colormap
894
+ fig = plot_spatial_map(data['pr'], aggregation='sum', cmap='Blues')
895
+ """
896
+
897
+ # Subset data by time period if specified
898
+ plot_data = data.copy()
899
+ if time_period is not None:
900
+ start_year, end_year = time_period
901
+ plot_data = plot_data.sel(
902
+ time=slice(f"{start_year}-01-01", f"{end_year}-12-31")
903
+ )
904
+
905
+ # Apply temporal aggregation
906
+ if aggregation == "mean":
907
+ plot_data = plot_data.mean(dim="time")
908
+ elif aggregation == "sum":
909
+ plot_data = plot_data.sum(dim="time")
910
+ elif aggregation == "min":
911
+ plot_data = plot_data.min(dim="time")
912
+ elif aggregation == "max":
913
+ plot_data = plot_data.max(dim="time")
914
+ elif aggregation == "std":
915
+ plot_data = plot_data.std(dim="time")
916
+ else:
917
+ raise ValueError(f"Unsupported aggregation method: {aggregation}")
918
+
919
+ # Create figure with cartopy
920
+ fig, ax = plt.subplots(
921
+ figsize=figsize,
922
+ subplot_kw={'projection': ccrs.PlateCarree()}
923
+ )
924
+
925
+ # Plot data
926
+ im = plot_data.plot(
927
+ ax=ax,
928
+ cmap=cmap,
929
+ transform=ccrs.PlateCarree(),
930
+ add_colorbar=False,
931
+ **kwargs
932
+ )
933
+
934
+ # Add map features
935
+ ax.add_feature(cfeature.COASTLINE, linewidth=0.5)
936
+ if show_countries:
937
+ ax.add_feature(cfeature.BORDERS, linewidth=0.3, alpha=0.7)
938
+ ax.add_feature(cfeature.OCEAN, color='lightblue', alpha=0.3)
939
+ ax.add_feature(cfeature.LAND, color='lightgray', alpha=0.3)
940
+
941
+ # Set extent to data bounds with small buffer
942
+ lon_min, lon_max = plot_data.longitude.min().item(), plot_data.longitude.max().item()
943
+ lat_min, lat_max = plot_data.latitude.min().item(), plot_data.latitude.max().item()
944
+ buffer = 0.5
945
+ ax.set_extent([lon_min - buffer, lon_max + buffer,
946
+ lat_min - buffer, lat_max + buffer], ccrs.PlateCarree())
947
+
948
+ # Add gridlines with labels only on left and bottom
949
+ gl = ax.gridlines(draw_labels=True, alpha=0.3)
950
+ gl.top_labels = False # No labels on top
951
+ gl.right_labels = False # No labels on right
952
+ gl.left_labels = True # Labels on left (latitude)
953
+ gl.bottom_labels = True # Labels on bottom (longitude)
954
+
955
+ # Add colorbar
956
+ cbar = plt.colorbar(im, ax=ax, shrink=0.8, pad=0.02)
957
+ if hasattr(plot_data, 'units'):
958
+ cbar.set_label(f"{plot_data.name} ({plot_data.units})", rotation=270, labelpad=20)
959
+ else:
960
+ cbar.set_label(f"{plot_data.name}", rotation=270, labelpad=20)
961
+
962
+ # Set title
963
+ if title is None:
964
+ var_name = plot_data.name or "Climate Variable"
965
+ if time_period:
966
+ title = f"{aggregation.title()} {var_name} ({time_period[0]}-{time_period[1]})"
967
+ else:
968
+ title = f"{aggregation.title()} {var_name}"
969
+
970
+ ax.set_title(title, fontsize=14, pad=20)
971
+
972
+ plt.tight_layout()
973
+
974
+ if save_path:
975
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
976
+
977
+ return fig
978
+
979
+
980
+ def plot_time_series(
981
+ data: Union[xr.DataArray, List[xr.DataArray]],
982
+ aggregation: str = "mean",
983
+ labels: Optional[List[str]] = None,
984
+ title: Optional[str] = None,
985
+ ylabel: Optional[str] = None,
986
+ figsize: Tuple[int, int] = (12, 6),
987
+ trend_line: bool = False,
988
+ save_path: Optional[str] = None,
989
+ **kwargs
990
+ ) -> plt.Figure:
991
+ """
992
+ Create time series plots of climate data.
993
+
994
+ Args:
995
+ data (xr.DataArray or list): Single DataArray or list of DataArrays to plot
996
+ aggregation (str): Spatial aggregation method ('mean', 'sum', 'min', 'max', 'std')
997
+ labels (list, optional): Labels for multiple datasets
998
+ title (str, optional): Plot title
999
+ ylabel (str, optional): Y-axis label
1000
+ figsize (tuple): Figure size
1001
+ trend_line (bool): Whether to add trend line
1002
+ save_path (str, optional): Path to save the plot
1003
+ **kwargs: Additional arguments passed to plot function
1004
+
1005
+ Returns:
1006
+ matplotlib.figure.Figure: The created figure
1007
+
1008
+ Examples:
1009
+ # Plot single time series
1010
+ fig = plot_time_series(data['tasmax'], title="Temperature Trends")
1011
+
1012
+ # Compare multiple scenarios
1013
+ fig = plot_time_series([data1['pr'], data2['pr']],
1014
+ labels=['RCP2.6', 'RCP8.5'],
1015
+ title="Precipitation Comparison")
1016
+ """
1017
+
1018
+ # Ensure data is a list
1019
+ if isinstance(data, xr.DataArray):
1020
+ data_list = [data]
1021
+ labels = labels or [data.name or "Data"]
1022
+ else:
1023
+ data_list = data
1024
+ labels = labels or [f"Dataset {i+1}" for i in range(len(data_list))]
1025
+
1026
+ if len(data_list) != len(labels):
1027
+ raise ValueError("Number of labels must match number of datasets")
1028
+
1029
+ # Set up the plot
1030
+ fig, ax1 = plt.subplots(figsize=figsize)
1031
+
1032
+ # Process and plot each dataset
1033
+ for i, (dataset, label) in enumerate(zip(data_list, labels)):
1034
+ # Apply spatial aggregation
1035
+ if aggregation == "mean":
1036
+ ts_data = dataset.mean(dim=["latitude", "longitude"])
1037
+ elif aggregation == "sum":
1038
+ ts_data = dataset.sum(dim=["latitude", "longitude"])
1039
+ elif aggregation == "min":
1040
+ ts_data = dataset.min(dim=["latitude", "longitude"])
1041
+ elif aggregation == "max":
1042
+ ts_data = dataset.max(dim=["latitude", "longitude"])
1043
+ elif aggregation == "std":
1044
+ ts_data = dataset.std(dim=["latitude", "longitude"])
1045
+ else:
1046
+ raise ValueError(f"Unsupported aggregation method: {aggregation}")
1047
+
1048
+ # Convert to annual means for cleaner plotting
1049
+ annual_data = ts_data.groupby("time.year").mean()
1050
+
1051
+ # Plot the time series
1052
+ ax1.plot(annual_data.year, annual_data.values, label=label, linewidth=2, **kwargs)
1053
+
1054
+ # Add trend line if requested
1055
+ if trend_line:
1056
+ z = np.polyfit(annual_data.year, annual_data.values, 1)
1057
+ p = np.poly1d(z)
1058
+ ax1.plot(annual_data.year, p(annual_data.year),
1059
+ linestyle='--', alpha=0.7,
1060
+ color=ax1.lines[-1].get_color())
1061
+
1062
+ # Format main plot
1063
+ ax1.set_xlabel("Year", fontsize=12)
1064
+ if ylabel is None:
1065
+ if hasattr(data_list[0], 'units'):
1066
+ ylabel = f"{data_list[0].name} ({data_list[0].units})"
1067
+ else:
1068
+ ylabel = data_list[0].name or "Value"
1069
+ ax1.set_ylabel(ylabel, fontsize=12)
1070
+
1071
+ if len(data_list) > 1:
1072
+ ax1.legend()
1073
+
1074
+ ax1.grid(True, alpha=0.3)
1075
+
1076
+ # Set main title
1077
+ if title is None:
1078
+ var_name = data_list[0].name or "Climate Variable"
1079
+ title = f"{aggregation.title()} {var_name} Time Series"
1080
+
1081
+ ax1.set_title(title, fontsize=14, pad=20)
1082
+
1083
+ plt.tight_layout()
1084
+
1085
+ if save_path:
1086
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
1087
+
1088
+ return fig
1089
+
1090
+
1091
+
813
1092
  if __name__ == "__main__":
814
1093
  # Example 1: Get observational data
815
1094
  print("Getting observational data...")
@@ -835,3 +1114,24 @@ if __name__ == "__main__":
835
1114
  bias_correction=True
836
1115
  )
837
1116
  print("Projection data keys:", list(proj_data.keys()))
1117
+
1118
+ # Example 3: Test new country lookup functionality
1119
+ print("\nTesting country lookup functionality...")
1120
+ try:
1121
+ # Test cartopy-based country lookup
1122
+ bounds = _get_country_bounds("Togo")
1123
+ print(f"Country lookup successful - Togo bounds: {bounds}")
1124
+ except Exception as e:
1125
+ print(f"Country lookup failed: {e}")
1126
+
1127
+ # Example 4: Plotting demonstrations (commented out to avoid blocking)
1128
+ print("\nPlotting functionality is available!")
1129
+ print("Use plot_spatial_map() and plot_time_series() functions")
1130
+
1131
+ # Uncomment these lines to see actual plots:
1132
+ # fig1 = plot_spatial_map(obs_data['tasmax'], time_period=(2000, 2010),
1133
+ # title="Mean Max Temperature 2000-2010 (ERA5)", cmap="Reds")
1134
+ # fig2 = plot_time_series(obs_data['pr'], title="Precipitation Time Series (Togo)",
1135
+ # trend_line=True)
1136
+
1137
+ print("Example completed successfully!")
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "cavapy"
3
- version = "0.3.1"
3
+ version = "0.4.0"
4
4
  description = "CAVA Python package. Retrive climate data."
5
5
  authors = ["Riccardo Soldan <riccardosoldan@hotmail.it>", "Oleh Lokshyn <olokshyn@gmail.com>"]
6
6
  readme = "README.md"
@@ -14,14 +14,15 @@ classifiers = [
14
14
 
15
15
  [tool.poetry.dependencies]
16
16
  python = "^3.11"
17
- xclim = "^0.53.2"
18
- geopandas = "^0.14.4"
19
- matplotlib = "^3.9.2"
20
- netcdf4 = "^1.7.2"
21
- dask = "^2024.11.2"
22
- bottleneck = "^1.4.2"
23
- llvmlite = "0.43.0"
24
-
17
+ xclim = "*"
18
+ xsdba = "*"
19
+ matplotlib = "*"
20
+ netcdf4 = "*"
21
+ dask = "*"
22
+ bottleneck = "*"
23
+ llvmlite = "*"
24
+ cartopy = "*"
25
+ seaborn = "*"
25
26
 
26
27
 
27
28
  [build-system]
File without changes