pyVPRM 3.0__tar.gz → 5.1__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.
- {pyvprm-3.0/pyVPRM.egg-info → pyvprm-5.1}/PKG-INFO +13 -2
- {pyvprm-3.0 → pyvprm-5.1}/README.md +29 -8
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/VPRM.py +105 -50
- pyvprm-5.1/pyVPRM/__init__.py +2 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/lib/flux_tower_class.py +41 -14
- pyvprm-5.1/pyVPRM/lib/functions.py +495 -0
- pyvprm-5.1/pyVPRM/lib/reproject_fiona.py +64 -0
- pyvprm-5.1/pyVPRM/meteorologies/era5_land_destinE.py +200 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/meteorologies/era5_monthly_xr.py +44 -4
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/base_manager.py +13 -8
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/modis.py +18 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/sentinel2.py +12 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/viirs.py +5 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/vprm_configs/copernicus_land_cover.yaml +32 -32
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/vprm_configs/esa_world_cover.yaml +38 -22
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/vprm_models/vprm_base.py +7 -4
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/vprm_models/vprm_base_no_xeric.py +2 -2
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/vprm_models/vprm_modified.py +2 -2
- pyvprm-5.1/pyVPRM/vprm_models/vprm_nn.py +149 -0
- {pyvprm-3.0 → pyvprm-5.1/pyVPRM.egg-info}/PKG-INFO +13 -2
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM.egg-info/SOURCES.txt +2 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM.egg-info/requires.txt +1 -0
- {pyvprm-3.0 → pyvprm-5.1}/setup.py +2 -1
- pyvprm-3.0/pyVPRM/__init__.py +0 -1
- pyvprm-3.0/pyVPRM/lib/functions.py +0 -471
- pyvprm-3.0/pyVPRM/vprm_models/vprm_nn.py +0 -130
- {pyvprm-3.0 → pyvprm-5.1}/LICENSE +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/MANIFEST.in +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/lib/__init__.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/lib/downmodis.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/lib/fancy_plot.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/lib/fluxnet_info/fluxnet_sites.pkl +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/lib/fluxnet_info/site_infos.txt +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/meteorologies/__init__.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/meteorologies/era5_class_dkrz.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/meteorologies/era5_class_draft.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/meteorologies/met_base_class.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/meteorologies/met_local_measurement.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/__init__.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/city.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/copernicus.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/esa_world_cover.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/mapbiomas.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/proba_v.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/synmap.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/sat_managers/viirs09ga.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/vprm_configs/__init__.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/vprm_configs/synmap.yaml +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/vprm_models/__init__.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM/vprm_models/model_params/__init__.py +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM.egg-info/dependency_links.txt +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM.egg-info/not-zip-safe +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/pyVPRM.egg-info/top_level.txt +0 -0
- {pyvprm-3.0 → pyvprm-5.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyVPRM
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.1
|
|
4
4
|
Summary: Vegetation Photosynthesis and Respiration Model
|
|
5
5
|
Home-page: https://github.com/tglauch/pyVPRM/
|
|
6
6
|
Author: Theo Glauch
|
|
@@ -36,5 +36,16 @@ Requires-Dist: lxml
|
|
|
36
36
|
Requires-Dist: requests
|
|
37
37
|
Requires-Dist: statsmodels
|
|
38
38
|
Requires-Dist: loguru
|
|
39
|
+
Requires-Dist: fiona
|
|
40
|
+
Dynamic: author
|
|
41
|
+
Dynamic: author-email
|
|
42
|
+
Dynamic: classifier
|
|
43
|
+
Dynamic: description
|
|
44
|
+
Dynamic: home-page
|
|
45
|
+
Dynamic: keywords
|
|
46
|
+
Dynamic: license
|
|
47
|
+
Dynamic: license-file
|
|
48
|
+
Dynamic: requires-dist
|
|
49
|
+
Dynamic: summary
|
|
39
50
|
|
|
40
51
|
A tool to calculate the CO2 exchange flux between atmosphere and terrestrial biosphere using the Vegetation Photosynthesis and Respiration Model
|
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
[](https://doi.org/10.5281/zenodo.14216613)
|
|
2
2
|
|
|
3
|
+
<figure>
|
|
4
|
+
<img src="https://github.com/tglauch/pyVPRM/assets/29706254/ba2565e6-1434-4a95-8086-936462f8d05d", height=150pt>
|
|
5
|
+
</figure>
|
|
6
|
+
|
|
7
|
+
A description of the model is published in Geoscientific Model Development (GMD) https://gmd.copernicus.org/articles/18/4713/2025/. If you use this package for you scientific work please cite as follows
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Glauch, T., Marshall, J., Gerbig, C., Botía, S., Gałkowski, M., Vardag, S. N., & Butz, A. (2025). pyVPRM: A next-generation vegetation photosynthesis and respiration model for the post-MODIS era. Geoscientific Model Development, 18(14), 4713–4742. https://doi.org/10.5194/gmd-18-4713-2025
|
|
11
|
+
```
|
|
3
12
|
|
|
13
|
+
or using BibTeX:
|
|
14
|
+
```
|
|
15
|
+
@Article{gmd-18-4713-2025,
|
|
16
|
+
AUTHOR = {Glauch, T. and Marshall, J. and Gerbig, C. and Bot\'{\i}a, S. and Ga{\l}kowski, M. and Vardag, S. N. and Butz, A.},
|
|
17
|
+
TITLE = {\textit{pyVPRM}: a next-generation vegetation photosynthesis and respiration model for the post-MODIS era},
|
|
18
|
+
JOURNAL = {Geoscientific Model Development},
|
|
19
|
+
VOLUME = {18},
|
|
20
|
+
YEAR = {2025},
|
|
21
|
+
NUMBER = {14},
|
|
22
|
+
PAGES = {4713--4742},
|
|
23
|
+
URL = {https://gmd.copernicus.org/articles/18/4713/2025/},
|
|
24
|
+
DOI = {10.5194/gmd-18-4713-2025}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
4
27
|
|
|
5
|
-
|
|
28
|
+
In case of any questions please write an E-Mail to theo.glauch@dlr.de.
|
|
6
29
|
|
|
7
30
|
# About
|
|
8
31
|
|
|
9
32
|
|
|
10
|
-
`pyVPRM` is a software package to calculate the CO2 exchange flux between atmosphere and terrestrial biosphere using the Vegetation Photosynthesis and Respriation Model (VPRM). It takes into account both, the primary productivity (GPP) as well as the respiration. The net flux between both of them is the net ecosystem exchange (NEE). The implementation is flexible and can be run with different satellite products (Sentinel-2, MODIS, VIIRS,...), land cover products (Copernicus Land Cover Service, ESA 10-m World Cover Map, MapBiomas) and
|
|
33
|
+
`pyVPRM` is a software package to calculate the CO2 exchange flux between atmosphere and terrestrial biosphere using the Vegetation Photosynthesis and Respriation Model (VPRM). It takes into account both, the primary productivity (GPP) as well as the respiration. The net flux between both of them is the net ecosystem exchange (NEE). The implementation is flexible and can be run with different satellite products (Sentinel-2, MODIS, VIIRS,...), land cover products (Copernicus Land Cover Service, ESA 10-m World Cover Map, MapBiomas) and meteorological drivers like ECMWFS ERA5 Reanalysis. Through its modular structure it is also easily extendable.
|
|
11
34
|
|
|
12
35
|
Among others it can be used for
|
|
13
36
|
|
|
@@ -15,16 +38,14 @@ Among others it can be used for
|
|
|
15
38
|
2. Making CO2 flux predictions for a given region of interest
|
|
16
39
|
3. Generating input files to run VPRM in the Weather Research and Forecasting Model (WRF)
|
|
17
40
|
|
|
18
|
-
In case of any questions please write an E-Mail to theo.glauch@dlr.de.
|
|
19
|
-
|
|
20
41
|
## Example Net Ecosystem Fluxes generated with pyVPRM
|
|
21
42
|
|
|
22
43
|
<figure>
|
|
23
|
-
<img src="https://github.com/user-attachments/assets/
|
|
44
|
+
<img src="https://github.com/user-attachments/assets/c099ef1f-6c5a-445c-bc6e-f6697c47b641", height=300pt>
|
|
24
45
|
</figure>
|
|
25
46
|
|
|
26
47
|
<figure>
|
|
27
|
-
<img src="https://github.com/user-attachments/assets/
|
|
48
|
+
<img src="https://github.com/user-attachments/assets/26913805-f188-477a-9a85-08911a165b1e", height=300pt>
|
|
28
49
|
</figure>
|
|
29
50
|
|
|
30
51
|
|
|
@@ -47,7 +68,7 @@ conda install esmpy
|
|
|
47
68
|
Afterwards install ```pyVPRM``` using pip via
|
|
48
69
|
|
|
49
70
|
```
|
|
50
|
-
pip install
|
|
71
|
+
pip install pyVPRM
|
|
51
72
|
```
|
|
52
73
|
|
|
53
74
|
## Start your own project
|
|
@@ -63,7 +84,7 @@ In order to start your own project you need to at least follow theses steps:
|
|
|
63
84
|
Remarks:
|
|
64
85
|
- If there is not yet an interface for your satellite data or land cover map, implement a new subclass in `pyVPRM/sat_managers/`
|
|
65
86
|
- For new land cover maps you need to additionaly provide a mapping of the land cover classes to the VPRM classes in a config file which is stored in `pyVPRM/vprm_configs`
|
|
66
|
-
- Open access to land cover maps: Copernicus: https://
|
|
87
|
+
- Open access to land cover maps: Copernicus: https://land.copernicus.eu/en/products/global-dynamic-land-cover/copernicus-global-land-service-land-cover-100m-collection-3-epoch-2019-globe#download | ESA World Cover: https://viewer.esa-worldcover.org
|
|
67
88
|
- Open access to satellite data: MODIS,VIIRS: https://e4ftl01.cr.usgs.gov | Sentinel-2: https://scihub.copernicus.eu/
|
|
68
89
|
|
|
69
90
|
## Examples
|
|
@@ -12,6 +12,7 @@ from pyVPRM.lib.functions import (
|
|
|
12
12
|
do_lowess_smoothing,
|
|
13
13
|
make_xesmf_grid,
|
|
14
14
|
to_esmf_grid,
|
|
15
|
+
replace_inf_runs_ignore_nans
|
|
15
16
|
)
|
|
16
17
|
from scipy.ndimage import uniform_filter
|
|
17
18
|
from pyproj import Transformer
|
|
@@ -38,11 +39,11 @@ regridder_options["conservative"] = "conserve"
|
|
|
38
39
|
|
|
39
40
|
class vprm:
|
|
40
41
|
"""
|
|
41
|
-
|
|
42
|
+
Main class for the Vegetation Photosynthesis and Respiration Model
|
|
42
43
|
"""
|
|
43
44
|
|
|
44
45
|
def __init__(
|
|
45
|
-
self, vprm_config_path, land_cover_map=None, verbose=False, n_cpus=1,
|
|
46
|
+
self, vprm_config_path, land_cover_map=None, verbose=False, n_cpus=1, flux_tower_instances=None
|
|
46
47
|
):
|
|
47
48
|
"""
|
|
48
49
|
Initialize a class instance
|
|
@@ -51,7 +52,7 @@ class vprm:
|
|
|
51
52
|
land_cover_map (xarray): A pre calculated map with the land cover types
|
|
52
53
|
verbose (bool): Set true for additional output when debugging
|
|
53
54
|
n_cpus: Number of CPUs
|
|
54
|
-
|
|
55
|
+
flux_tower_instances: For fitting. Provide a list of flux_tower_instances.
|
|
55
56
|
|
|
56
57
|
Returns:
|
|
57
58
|
The lowess smoothed array
|
|
@@ -60,13 +61,10 @@ class vprm:
|
|
|
60
61
|
logger.info("Running with pyVPRM version {}".format(pyVPRM.__version__))
|
|
61
62
|
self.sat_imgs = []
|
|
62
63
|
|
|
63
|
-
self.
|
|
64
|
-
if self.sites is not None:
|
|
65
|
-
self.lonlats = [i.get_lonlat() for i in sites]
|
|
64
|
+
self.set_flux_tower_inst(flux_tower_instances)
|
|
66
65
|
self.n_cpus = n_cpus
|
|
67
66
|
self.counter = 0
|
|
68
67
|
self.fit_params_dict = None
|
|
69
|
-
self.res = None
|
|
70
68
|
|
|
71
69
|
self.new = True
|
|
72
70
|
self.timestamps = []
|
|
@@ -85,30 +83,36 @@ class vprm:
|
|
|
85
83
|
|
|
86
84
|
with open(vprm_config_path, "r") as stream:
|
|
87
85
|
try:
|
|
88
|
-
|
|
86
|
+
self.vprm_cfg = yaml.safe_load(stream)
|
|
89
87
|
except yaml.YAMLError as exc:
|
|
90
88
|
logger.info(exc)
|
|
91
89
|
|
|
92
90
|
self.temp_coefficients = dict()
|
|
93
91
|
self.map_to_vprm_class = dict()
|
|
94
|
-
for key in
|
|
95
|
-
if "tmin" in
|
|
96
|
-
self.temp_coefficients[
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
for key in self.vprm_cfg:
|
|
93
|
+
if "tmin" in self.vprm_cfg[key].keys():
|
|
94
|
+
self.temp_coefficients[self.vprm_cfg[key]["vprm_class"]] = [
|
|
95
|
+
self.vprm_cfg[key]["tmin"],
|
|
96
|
+
self.vprm_cfg[key]["topt"],
|
|
97
|
+
self.vprm_cfg[key]["tmax"],
|
|
98
|
+
self.vprm_cfg[key]["tlow"],
|
|
101
99
|
]
|
|
102
|
-
for c in
|
|
103
|
-
self.map_to_vprm_class[c] =
|
|
100
|
+
for c in self.vprm_cfg[key]["class_numbers"]:
|
|
101
|
+
self.map_to_vprm_class[c] = self.vprm_cfg[key]["vprm_class"]
|
|
104
102
|
return
|
|
105
103
|
|
|
104
|
+
def set_flux_tower_inst(self, flux_tower_instances):
|
|
105
|
+
self.flux_tower_instances = flux_tower_instances
|
|
106
|
+
if self.flux_tower_instances is not None:
|
|
107
|
+
self.lonlats = [i.get_lonlat() for i in flux_tower_instances]
|
|
108
|
+
return
|
|
109
|
+
|
|
106
110
|
def to_wrf_output(
|
|
107
111
|
self,
|
|
108
112
|
out_grid,
|
|
109
113
|
weights_for_regridder=None,
|
|
110
114
|
regridder_save_path=None,
|
|
111
|
-
driver="
|
|
115
|
+
driver="xESMF",
|
|
112
116
|
interp_method="conservative",
|
|
113
117
|
n_cpus=None,
|
|
114
118
|
mpi=True,
|
|
@@ -144,7 +148,7 @@ class vprm:
|
|
|
144
148
|
logger.info(
|
|
145
149
|
"Need to generate the weights for the regridder. This can be very slow and memory intensive"
|
|
146
150
|
)
|
|
147
|
-
if driver == "
|
|
151
|
+
if driver == "xESMF":
|
|
148
152
|
regridder = xe.Regridder(src_grid, ds_out, interp_method)
|
|
149
153
|
if regridder_save_path is not None:
|
|
150
154
|
regridder.to_netcdf(regridder_save_path)
|
|
@@ -182,7 +186,7 @@ class vprm:
|
|
|
182
186
|
# os.remove(dest_temp_path)
|
|
183
187
|
weights_for_regridder = regridder_save_path
|
|
184
188
|
else:
|
|
185
|
-
logger.info("Driver needs to be
|
|
189
|
+
logger.info("Driver needs to be xESMF or ESMF_RegridWeightGen")
|
|
186
190
|
if weights_for_regridder is not None:
|
|
187
191
|
regridder = xe.Regridder(
|
|
188
192
|
src_grid,
|
|
@@ -277,7 +281,12 @@ class vprm:
|
|
|
277
281
|
"evi_max": ds_t_max_evi,
|
|
278
282
|
"evi_min": ds_t_min_evi,
|
|
279
283
|
}
|
|
280
|
-
|
|
284
|
+
|
|
285
|
+
veg_classes_def = 'VPRM class definition: '
|
|
286
|
+
for k in self.vprm_cfg.keys():
|
|
287
|
+
veg_classes_def += '{} {}, '.format(self.vprm_cfg[k]['vprm_class'], k)
|
|
288
|
+
veg_classes_def = veg_classes_def[:-2]
|
|
289
|
+
|
|
281
290
|
for key in ret_dict.keys():
|
|
282
291
|
ret_dict[key] = ret_dict[key].assign_attrs(
|
|
283
292
|
title="VPRM input data for WRF: {}".format(key),
|
|
@@ -289,7 +298,7 @@ class vprm:
|
|
|
289
298
|
institution2="Deutsches Zentrum für Luft- und Raumfahrt (DLR)",
|
|
290
299
|
contact="theo.glauch@dlr.de",
|
|
291
300
|
date_created=str(datetime.now()),
|
|
292
|
-
comment=
|
|
301
|
+
comment=veg_classes_def,
|
|
293
302
|
)
|
|
294
303
|
return ret_dict
|
|
295
304
|
|
|
@@ -302,10 +311,12 @@ class vprm:
|
|
|
302
311
|
b_swir=None,
|
|
303
312
|
drop_bands=False,
|
|
304
313
|
which_evi=None,
|
|
314
|
+
add_ndvi=False,
|
|
305
315
|
timestamp_key=None,
|
|
306
316
|
mask_bad_pixels=True,
|
|
307
317
|
mask_clouds=True,
|
|
308
318
|
mask_snow=True,
|
|
319
|
+
mask_water=True,
|
|
309
320
|
):
|
|
310
321
|
"""
|
|
311
322
|
Add a new satellite image and calculate EVI and LSWI if desired
|
|
@@ -375,12 +386,20 @@ class vprm:
|
|
|
375
386
|
temp_lswi = xr.where((temp_lswi < -1) | (temp_lswi > 1), np.nan, temp_lswi)
|
|
376
387
|
handler.sat_img["evi"] = temp_evi
|
|
377
388
|
handler.sat_img["lswi"] = temp_lswi
|
|
389
|
+
if add_ndvi:
|
|
390
|
+
nir = handler.sat_img[b_nir]
|
|
391
|
+
red = handler.sat_img[b_red]
|
|
392
|
+
temp_ndvi = (nir-red)/(nir+red)
|
|
393
|
+
handler.sat_img['ndvi'] = temp_ndvi
|
|
394
|
+
|
|
378
395
|
if timestamp_key is not None:
|
|
379
396
|
handler.sat_img = handler.sat_img.rename({timestamp_key: "timestamps"})
|
|
380
397
|
|
|
381
398
|
bands_to_mask = []
|
|
382
399
|
if which_evi in ["evi", "evi2"]:
|
|
383
400
|
bands_to_mask = ["evi", "lswi"]
|
|
401
|
+
if add_ndvi:
|
|
402
|
+
bands_to_mask.append('ndvi')
|
|
384
403
|
else:
|
|
385
404
|
for btm in [b_nir, b_red, b_blue, b_swir]:
|
|
386
405
|
if btm is not None:
|
|
@@ -390,6 +409,11 @@ class vprm:
|
|
|
390
409
|
handler.mask_snow()
|
|
391
410
|
else:
|
|
392
411
|
handler.mask_snow(bands_to_mask)
|
|
412
|
+
if mask_water:
|
|
413
|
+
if bands_to_mask == []:
|
|
414
|
+
handler.mask_water()
|
|
415
|
+
else:
|
|
416
|
+
handler.mask_water(bands_to_mask)
|
|
393
417
|
if mask_clouds:
|
|
394
418
|
if bands_to_mask == []:
|
|
395
419
|
handler.mask_clouds()
|
|
@@ -443,6 +467,7 @@ class vprm:
|
|
|
443
467
|
x, y = t.transform(ll[0], ll[1])
|
|
444
468
|
x_ind = np.argmin(np.abs(x - xs))
|
|
445
469
|
y_ind = np.argmin(np.abs(y - ys))
|
|
470
|
+
arsz = 3 # Hard Coded. Means only 3 by 3 pixes around the lonlat location are smoothed
|
|
446
471
|
for key in keys:
|
|
447
472
|
logger.info(key)
|
|
448
473
|
self.sat_imgs.sat_img[key][
|
|
@@ -464,20 +489,21 @@ class vprm:
|
|
|
464
489
|
interp_method="nearest",
|
|
465
490
|
)
|
|
466
491
|
self.sat_imgs.sat_img = self.sat_imgs.sat_img.assign_coords(
|
|
467
|
-
{"site_names": [i.get_site_name() for i in self.
|
|
492
|
+
{"site_names": [i.get_site_name() for i in self.flux_tower_instances]}
|
|
468
493
|
)
|
|
469
494
|
|
|
470
|
-
def sort_and_merge_by_timestamp(self):
|
|
495
|
+
def sort_and_merge_by_timestamp(self, min_lenght_snow_period=21):
|
|
471
496
|
"""
|
|
472
497
|
Called after adding the satellite images with 'add_sat_img'. Sorts the satellite
|
|
473
498
|
images by timestamp and merges everything to one satellite_data_manager.
|
|
474
499
|
|
|
475
500
|
Parameters:
|
|
476
|
-
|
|
501
|
+
min_lenght_snow_period defines how long a period should be until gaps are
|
|
502
|
+
filled to stabilize lowess fits
|
|
477
503
|
Returns:
|
|
478
504
|
None
|
|
479
505
|
"""
|
|
480
|
-
if self.
|
|
506
|
+
if self.flux_tower_instances is None:
|
|
481
507
|
x_time_y = 0
|
|
482
508
|
for h in self.sat_imgs:
|
|
483
509
|
size_dict = dict(h.sat_img.sizes)
|
|
@@ -531,19 +557,33 @@ class vprm:
|
|
|
531
557
|
{"timestamps": (dims, tismp)}
|
|
532
558
|
)
|
|
533
559
|
self.time_key = "time"
|
|
560
|
+
|
|
561
|
+
N = int(min_lenght_snow_period / np.diff(self.sat_imgs.sat_img['time']).mean())
|
|
534
562
|
if "evi" in list(self.sat_imgs.sat_img.data_vars):
|
|
535
|
-
self.sat_imgs.sat_img["evi"] =
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
)
|
|
563
|
+
self.sat_imgs.sat_img["evi"] = replace_inf_runs_ignore_nans(self.sat_imgs.sat_img["evi"],
|
|
564
|
+
N = N,
|
|
565
|
+
time_dim = self.time_key)
|
|
566
|
+
# self.sat_imgs.sat_img["evi"] = xr.where(
|
|
567
|
+
# (self.sat_imgs.sat_img["evi"] == np.inf),
|
|
568
|
+
# self.sat_imgs.sat_img["evi"].min(dim=self.time_key),
|
|
569
|
+
# self.sat_imgs.sat_img["evi"],
|
|
570
|
+
# )
|
|
540
571
|
|
|
541
572
|
if "lswi" in list(self.sat_imgs.sat_img.data_vars):
|
|
542
|
-
self.sat_imgs.sat_img["lswi"] =
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
)
|
|
573
|
+
self.sat_imgs.sat_img["lswi"] = replace_inf_runs_ignore_nans(self.sat_imgs.sat_img["lswi"],
|
|
574
|
+
N = N,
|
|
575
|
+
time_dim = self.time_key)
|
|
576
|
+
# self.sat_imgs.sat_img["lswi"] = xr.where(
|
|
577
|
+
# (self.sat_imgs.sat_img["lswi"] == np.inf),
|
|
578
|
+
# self.sat_imgs.sat_img["lswi"].min(dim=self.time_key),
|
|
579
|
+
# self.sat_imgs.sat_img["lswi"],
|
|
580
|
+
# )
|
|
581
|
+
|
|
582
|
+
if "ndvi" in list(self.sat_imgs.sat_img.data_vars):
|
|
583
|
+
self.sat_imgs.sat_img["ndvi"] = replace_inf_runs_ignore_nans(self.sat_imgs.sat_img["ndvi"],
|
|
584
|
+
N = N,
|
|
585
|
+
time_dim = self.time_key)
|
|
586
|
+
|
|
547
587
|
return
|
|
548
588
|
|
|
549
589
|
def clip_to_box(self, sat_to_crop):
|
|
@@ -617,9 +657,13 @@ class vprm:
|
|
|
617
657
|
)
|
|
618
658
|
src_grid.to_netcdf(src_temp_path)
|
|
619
659
|
ds_out.to_netcdf(dest_temp_path)
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
660
|
+
logger.debug(f"wrote source regridder: {src_temp_path}")
|
|
661
|
+
logger.debug(f"wrote dest regridder: {dest_temp_path}")
|
|
662
|
+
exec_str = (
|
|
663
|
+
"ESMF_RegridWeightGen --source {} --destination {} "
|
|
664
|
+
"--weight {} -m conserve -r --netcdf4 --src_regional"
|
|
665
|
+
" --dest_regional --ignore_unmapped"
|
|
666
|
+
).format(src_temp_path, dest_temp_path, regridder_save_path)
|
|
623
667
|
if mpi is True:
|
|
624
668
|
exec_str = "mpirun -np {} ".format(n_cpus) + exec_str
|
|
625
669
|
if not logs:
|
|
@@ -740,7 +784,7 @@ class vprm:
|
|
|
740
784
|
self.min_lswi = copy.deepcopy(self.prototype)
|
|
741
785
|
self.min_max_evi = copy.deepcopy(self.prototype)
|
|
742
786
|
shortcut = self.sat_imgs.sat_img
|
|
743
|
-
# if self.
|
|
787
|
+
# if self.flux_tower_instances is None:
|
|
744
788
|
self.min_lswi.sat_img["min_lswi"] = shortcut["lswi"].min(
|
|
745
789
|
self.time_key, skipna=True
|
|
746
790
|
)
|
|
@@ -760,7 +804,8 @@ class vprm:
|
|
|
760
804
|
)
|
|
761
805
|
return
|
|
762
806
|
|
|
763
|
-
def lowess(self, keys, lonlats=None, times=False, frac=0.25, it=3,
|
|
807
|
+
def lowess(self, keys, lonlats=None, times=False, frac=0.25, it=3,
|
|
808
|
+
n_cpus=None, smooth_all=False):
|
|
764
809
|
"""
|
|
765
810
|
Performs the lowess smoothing
|
|
766
811
|
|
|
@@ -804,7 +849,7 @@ class vprm:
|
|
|
804
849
|
xvals = self.sat_imgs.sat_img["time"]
|
|
805
850
|
logger.info("Lowess timestamps {}".format(xvals))
|
|
806
851
|
|
|
807
|
-
if self.
|
|
852
|
+
if (self.flux_tower_instances is not None) and (smooth_all is False): # Is flux tower sites are given
|
|
808
853
|
if "timestamps" in list(self.sat_imgs.sat_img.data_vars):
|
|
809
854
|
for key in keys:
|
|
810
855
|
self.sat_imgs.sat_img = self.sat_imgs.sat_img.assign(
|
|
@@ -920,13 +965,21 @@ class vprm:
|
|
|
920
965
|
)
|
|
921
966
|
return
|
|
922
967
|
|
|
923
|
-
def clip_values(self, key, min_val, max_val):
|
|
924
|
-
|
|
925
|
-
self.sat_imgs.sat_img[key].values
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
self.sat_imgs.sat_img[key].values
|
|
929
|
-
|
|
968
|
+
def clip_values(self, key, min_val, max_val, to_nan=False):
|
|
969
|
+
if to_nan:
|
|
970
|
+
self.sat_imgs.sat_img[key].values[
|
|
971
|
+
self.sat_imgs.sat_img[key].values < min_val
|
|
972
|
+
] = np.nan
|
|
973
|
+
self.sat_imgs.sat_img[key].values[
|
|
974
|
+
self.sat_imgs.sat_img[key].values > max_val
|
|
975
|
+
] = np.nan
|
|
976
|
+
else:
|
|
977
|
+
self.sat_imgs.sat_img[key].values[
|
|
978
|
+
self.sat_imgs.sat_img[key].values < min_val
|
|
979
|
+
] = min_val
|
|
980
|
+
self.sat_imgs.sat_img[key].values[
|
|
981
|
+
self.sat_imgs.sat_img[key].values > max_val
|
|
982
|
+
] = max_val
|
|
930
983
|
return
|
|
931
984
|
|
|
932
985
|
def clip_non_finite(self, data_var, val, sel):
|
|
@@ -1053,9 +1106,11 @@ class vprm:
|
|
|
1053
1106
|
days_after_first_image = (
|
|
1054
1107
|
datetime_utc - self.timestamp_start
|
|
1055
1108
|
).total_seconds() / (24 * 60 * 60)
|
|
1109
|
+
# Calculate a list of time differences between the xarray time and the datetime_utc both with respect to timestamp_start.
|
|
1110
|
+
# Take the index (argmin) with smallest difference
|
|
1056
1111
|
counter_new = np.argmin(
|
|
1057
1112
|
np.abs(self.sat_imgs.sat_img[self.time_key].values - days_after_first_image)
|
|
1058
|
-
)
|
|
1113
|
+
)
|
|
1059
1114
|
if (days_after_first_image < 0) | (
|
|
1060
1115
|
days_after_first_image > self.sat_imgs.sat_img[self.time_key][-1]
|
|
1061
1116
|
):
|
|
@@ -103,7 +103,7 @@ class brazil_flux_data(flux_tower_data):
|
|
|
103
103
|
"par_fill",
|
|
104
104
|
"ust",
|
|
105
105
|
]
|
|
106
|
-
if use_vars
|
|
106
|
+
if use_vars == "all":
|
|
107
107
|
self.vars = "all"
|
|
108
108
|
site_info_dict = dict()
|
|
109
109
|
site_info_dict["K67"] = dict(lat=-2.857, lon=-54.959, veg_class="EF")
|
|
@@ -188,7 +188,7 @@ class ameri_fluxnet(flux_tower_data):
|
|
|
188
188
|
self.data_path = data_path
|
|
189
189
|
|
|
190
190
|
super().__init__(t_start, t_stop, ssrd_key, t2m_key, site_name)
|
|
191
|
-
if (use_vars is None) | (use_vars
|
|
191
|
+
if (use_vars is None) | (use_vars == "all"):
|
|
192
192
|
self.vars = "all"
|
|
193
193
|
else:
|
|
194
194
|
self.vars = use_vars
|
|
@@ -205,7 +205,7 @@ class ameri_fluxnet(flux_tower_data):
|
|
|
205
205
|
return
|
|
206
206
|
|
|
207
207
|
def add_tower_data(self):
|
|
208
|
-
if self.vars
|
|
208
|
+
if self.vars == "all":
|
|
209
209
|
idata = pd.read_csv(
|
|
210
210
|
glob.glob(os.path.join(self.data_path, "*.csv"))[0], skiprows=2
|
|
211
211
|
)
|
|
@@ -286,7 +286,7 @@ class fluxnet(flux_tower_data):
|
|
|
286
286
|
"TA_F_QC",
|
|
287
287
|
"SW_IN_F_QC",
|
|
288
288
|
]
|
|
289
|
-
elif use_vars
|
|
289
|
+
elif use_vars == "all":
|
|
290
290
|
self.vars = "all"
|
|
291
291
|
else:
|
|
292
292
|
self.vars = use_vars
|
|
@@ -351,6 +351,7 @@ class icos(flux_tower_data):
|
|
|
351
351
|
use_vars=None,
|
|
352
352
|
t_start=None,
|
|
353
353
|
t_stop=None,
|
|
354
|
+
icos_2025=True,
|
|
354
355
|
):
|
|
355
356
|
|
|
356
357
|
self.data_path = data_path
|
|
@@ -380,18 +381,44 @@ class icos(flux_tower_data):
|
|
|
380
381
|
"TA_F_QC",
|
|
381
382
|
"SW_IN_F_QC",
|
|
382
383
|
]
|
|
383
|
-
elif use_vars
|
|
384
|
+
elif use_vars == "all":
|
|
384
385
|
self.vars = "all"
|
|
385
386
|
else:
|
|
386
387
|
self.vars = use_vars
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
388
|
+
if icos_2025:
|
|
389
|
+
site_info = pd.read_csv(
|
|
390
|
+
os.path.join(
|
|
391
|
+
os.path.dirname(self.data_path).replace('FLUXNET_HH_L2', 'ARCHIVE_L2'),
|
|
392
|
+
"ICOSETC_{}_SITEINFO_L2.csv".format(self.site_name),
|
|
393
|
+
),
|
|
394
|
+
on_bad_lines="skip",
|
|
395
|
+
)
|
|
396
|
+
self.footprint = pd.read_csv(
|
|
397
|
+
os.path.join(
|
|
398
|
+
os.path.dirname(self.data_path).replace('FLUXNET_HH_L2', 'ARCHIVE_L2'),
|
|
399
|
+
"ICOSETC_{}_FLUXES_L2.csv".format(self.site_name),
|
|
400
|
+
),
|
|
401
|
+
on_bad_lines="skip",
|
|
402
|
+
)
|
|
403
|
+
if 'FETCH_50' in self.footprint.keys():
|
|
404
|
+
self.fetch50 = np.percentile(self.footprint['FETCH_50'][self.footprint['FETCH_50']>0], 90)
|
|
405
|
+
else:
|
|
406
|
+
self.fetch50 = None
|
|
407
|
+
|
|
408
|
+
if 'FETCH_90' in self.footprint.keys():
|
|
409
|
+
self.fetch90 = np.percentile(self.footprint['FETCH_90'][self.footprint['FETCH_90']>0], 50)
|
|
410
|
+
else:
|
|
411
|
+
self.fetch90 = None
|
|
412
|
+
else:
|
|
413
|
+
site_info = pd.read_csv(
|
|
414
|
+
os.path.join(
|
|
415
|
+
os.path.dirname(self.data_path),
|
|
416
|
+
"ICOSETC_{}_SITEINFO_L2.csv".format(self.site_name),
|
|
417
|
+
),
|
|
418
|
+
on_bad_lines="skip",
|
|
419
|
+
)
|
|
420
|
+
self.fetch90 = None # Not yet implemented
|
|
421
|
+
|
|
395
422
|
self.land_cover_type = site_info.loc[site_info["VARIABLE"] == "IGBP"][
|
|
396
423
|
"DATAVALUE"
|
|
397
424
|
].values[0]
|
|
@@ -405,7 +432,7 @@ class icos(flux_tower_data):
|
|
|
405
432
|
return
|
|
406
433
|
|
|
407
434
|
def add_tower_data(self):
|
|
408
|
-
if self.vars
|
|
435
|
+
if self.vars == "all":
|
|
409
436
|
idata = pd.read_csv(self.data_path, on_bad_lines="skip")
|
|
410
437
|
else:
|
|
411
438
|
idata = pd.read_csv(
|