cavapy 0.3.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
Potentially problematic release.
This version of cavapy might be problematic. Click here for more details.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: cavapy
|
|
3
|
-
Version: 0.
|
|
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
|
|
16
|
-
Requires-Dist:
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist: llvmlite
|
|
19
|
-
Requires-Dist: matplotlib
|
|
20
|
-
Requires-Dist: netcdf4
|
|
21
|
-
Requires-Dist:
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
cavapy.py,sha256=BiF7OWwEydne_4_V42ZsQrM5-9NziBzpwBJFmVMLckE,42707
|
|
2
|
+
cavapy-0.4.0.dist-info/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
3
|
+
cavapy-0.4.0.dist-info/METADATA,sha256=Mv3uW3Gyw8xTdfJj5xlhXkf_qG5FPXSyQe8GERBjDk4,8409
|
|
4
|
+
cavapy-0.4.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
5
|
+
cavapy-0.4.0.dist-info/RECORD,,
|
cavapy.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
306
|
-
|
|
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!")
|
cavapy-0.3.1.dist-info/RECORD
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
cavapy.py,sha256=LQsW2c6j7ND_Hl4r5-R60DHNoBFAZ3I3O8w_jzKfMg0,31385
|
|
2
|
-
cavapy-0.3.1.dist-info/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
3
|
-
cavapy-0.3.1.dist-info/METADATA,sha256=2n55bBuV-Rdy5W9uYEn2cz5lnAAlqNLyYHCd-VZW9_c,6902
|
|
4
|
-
cavapy-0.3.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
5
|
-
cavapy-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|