pyVPRM 5.2__tar.gz → 5.3__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.
Files changed (57) hide show
  1. {pyvprm-5.2/pyVPRM.egg-info → pyvprm-5.3}/PKG-INFO +2 -1
  2. {pyvprm-5.2 → pyvprm-5.3}/README.md +1 -0
  3. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/VPRM.py +150 -0
  4. pyvprm-5.3/pyVPRM/__init__.py +4 -0
  5. pyvprm-5.3/pyVPRM/flux_tower_libs/FFP_footprint_class.py +108 -0
  6. pyvprm-5.3/pyVPRM/flux_tower_libs/KM_footprint_class.py +101 -0
  7. pyvprm-5.3/pyVPRM/flux_tower_libs/base_footprint_class.py +351 -0
  8. {pyvprm-5.2/pyVPRM/lib → pyvprm-5.3/pyVPRM/flux_tower_libs}/flux_tower_class.py +119 -6
  9. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/lib/functions.py +225 -30
  10. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/base_manager.py +2 -2
  11. pyvprm-5.3/pyVPRM/sat_managers/lads_downloader.py +366 -0
  12. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/modis.py +0 -1
  13. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/sentinel2.py +1 -3
  14. pyvprm-5.3/pyVPRM/vprm_models/model_params/__init__.py +0 -0
  15. {pyvprm-5.2 → pyvprm-5.3/pyVPRM.egg-info}/PKG-INFO +2 -1
  16. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM.egg-info/SOURCES.txt +6 -1
  17. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM.egg-info/requires.txt +1 -0
  18. {pyvprm-5.2 → pyvprm-5.3}/setup.py +2 -1
  19. pyvprm-5.2/pyVPRM/__init__.py +0 -3
  20. {pyvprm-5.2 → pyvprm-5.3}/LICENSE +0 -0
  21. {pyvprm-5.2 → pyvprm-5.3}/MANIFEST.in +0 -0
  22. {pyvprm-5.2/pyVPRM/lib → pyvprm-5.3/pyVPRM/flux_tower_libs}/__init__.py +0 -0
  23. {pyvprm-5.2/pyVPRM/meteorologies → pyvprm-5.3/pyVPRM/lib}/__init__.py +0 -0
  24. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/lib/downmodis.py +0 -0
  25. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/lib/fancy_plot.py +0 -0
  26. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/lib/fluxnet_info/fluxnet_sites.pkl +0 -0
  27. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/lib/fluxnet_info/site_infos.txt +0 -0
  28. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/lib/reproject_fiona.py +0 -0
  29. {pyvprm-5.2/pyVPRM/sat_managers → pyvprm-5.3/pyVPRM/meteorologies}/__init__.py +0 -0
  30. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/meteorologies/era5_class_dkrz.py +0 -0
  31. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/meteorologies/era5_class_draft.py +0 -0
  32. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/meteorologies/era5_land_destinE.py +0 -0
  33. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/meteorologies/era5_monthly_xr.py +0 -0
  34. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/meteorologies/met_base_class.py +0 -0
  35. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/meteorologies/met_local_measurement.py +0 -0
  36. {pyvprm-5.2/pyVPRM/vprm_configs → pyvprm-5.3/pyVPRM/sat_managers}/__init__.py +0 -0
  37. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/city.py +0 -0
  38. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/copernicus.py +0 -0
  39. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/esa_world_cover.py +0 -0
  40. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/mapbiomas.py +0 -0
  41. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/proba_v.py +0 -0
  42. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/synmap.py +0 -0
  43. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/viirs.py +0 -0
  44. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/sat_managers/viirs09ga.py +0 -0
  45. {pyvprm-5.2/pyVPRM/vprm_models/model_params → pyvprm-5.3/pyVPRM/vprm_configs}/__init__.py +0 -0
  46. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/vprm_configs/copernicus_land_cover.yaml +0 -0
  47. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/vprm_configs/esa_world_cover.yaml +0 -0
  48. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/vprm_configs/synmap.yaml +0 -0
  49. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/vprm_models/__init__.py +0 -0
  50. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/vprm_models/vprm_base_model.py +0 -0
  51. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/vprm_models/vprm_base_model_no_xeric.py +0 -0
  52. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/vprm_models/vprm_modified_model.py +0 -0
  53. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM/vprm_models/vprm_nn_model.py +0 -0
  54. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM.egg-info/dependency_links.txt +0 -0
  55. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM.egg-info/not-zip-safe +0 -0
  56. {pyvprm-5.2 → pyvprm-5.3}/pyVPRM.egg-info/top_level.txt +0 -0
  57. {pyvprm-5.2 → pyvprm-5.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyVPRM
3
- Version: 5.2
3
+ Version: 5.3
4
4
  Summary: Vegetation Photosynthesis and Respiration Model
5
5
  Home-page: https://github.com/tglauch/pyVPRM/
6
6
  Author: Theo Glauch
@@ -38,6 +38,7 @@ Requires-Dist: requests
38
38
  Requires-Dist: statsmodels
39
39
  Requires-Dist: loguru
40
40
  Requires-Dist: fiona
41
+ Requires-Dist: pykalman
41
42
  Dynamic: author
42
43
  Dynamic: author-email
43
44
  Dynamic: classifier
@@ -3,6 +3,7 @@
3
3
  [![License](https://img.shields.io/pypi/l/pyVPRM.svg)](https://pypi.org/project/pyVPRM/)
4
4
  [![Downloads](https://img.shields.io/pypi/dm/pyVPRM.svg)](https://pypi.org/project/pyVPRM/)
5
5
  ![GitHub stars](https://img.shields.io/github/stars/tglauch/pyVPRM?style=social)
6
+ ![Python Version](https://img.shields.io/pypi/pyversions/pyVPRM)
6
7
 
7
8
 
8
9
  <figure>
@@ -10,6 +10,7 @@ from pyVPRM.sat_managers.base_manager import satellite_data_manager
10
10
  from pyVPRM.lib.functions import (
11
11
  add_corners_to_1d_grid,
12
12
  do_lowess_smoothing,
13
+ do_kalman_smoothing,
13
14
  make_xesmf_grid,
14
15
  to_esmf_grid,
15
16
  replace_inf_runs_ignore_nans
@@ -964,6 +965,155 @@ class vprm_preprocessor:
964
965
  )
965
966
  return
966
967
 
968
+ def kalman(self, keys, times=None, lonlats=None, n_cpus=None, smooth_all=False):
969
+ """
970
+ Performs the lowess smoothing
971
+
972
+ Parameters:
973
+ lonlats (str): If given the smearing is only performed at the
974
+ given lats and lons
975
+ Returns:
976
+ None
977
+ """
978
+ self.sat_imgs.sat_img.load()
979
+
980
+ if n_cpus is None:
981
+ n_cpus = self.n_cpus
982
+ if isinstance(times, pd.core.indexes.datetimes.DatetimeIndex):
983
+ times = list(times)
984
+ if isinstance(times, list):
985
+ times = np.array(sorted(times))
986
+ if (times[-1] > self.timestamp_end) | (times[0] < self.timestamp_start):
987
+ logger.info(
988
+ "You have provied some timestamps that are not covered from satellite images.\
989
+ They will be ignored in the following, to avoid unreliable results"
990
+ )
991
+ times = times[
992
+ (times <= self.timestamp_end) & (times >= self.timestamp_start)
993
+ ]
994
+ xvals = [
995
+ int(
996
+ np.round(
997
+ (i - self.timestamp_start).total_seconds() / (24 * 60 * 60)
998
+ )
999
+ )
1000
+ for i in times
1001
+ ]
1002
+ elif isinstance(times, str):
1003
+ if times == "daily":
1004
+ xvals = np.arange(self.tot_num_days+1)
1005
+ else:
1006
+ logger.info("{} is not a valid str for times".format(times))
1007
+ return
1008
+ else:
1009
+ xvals = self.sat_imgs.sat_img["time"]
1010
+ logger.info("Lowess timestamps {}".format(xvals))
1011
+
1012
+ if (self.flux_tower_instances is not None) and (smooth_all is False): # Is flux tower sites are given
1013
+ if "timestamps" in list(self.sat_imgs.sat_img.data_vars):
1014
+ for key in keys:
1015
+ self.sat_imgs.sat_img = self.sat_imgs.sat_img.assign(
1016
+ {
1017
+ key: (
1018
+ ["time_gap_filled", "site_names"],
1019
+ np.array(
1020
+ [
1021
+ do_kalman_smoothing(
1022
+ self.sat_imgs.sat_img.sel(site_names=i)[
1023
+ key
1024
+ ].values,
1025
+ timestamps=self.sat_imgs.sat_img.sel(
1026
+ site_names=i
1027
+ )["timestamps"].values,
1028
+ )
1029
+ for i in self.sat_imgs.sat_img.site_names.values
1030
+ ]
1031
+ ).T,
1032
+ )
1033
+ }
1034
+ )
1035
+ else:
1036
+ for key in keys:
1037
+ self.sat_imgs.sat_img = self.sat_imgs.sat_img.assign(
1038
+ {
1039
+ key: (
1040
+ ["time_gap_filled", "site_names"],
1041
+ np.array(
1042
+ [
1043
+ do_kalman_smoothing(
1044
+ self.sat_imgs.sat_img.sel(site_names=i)[
1045
+ key
1046
+ ].values,
1047
+ timestamps=self.sat_imgs.sat_img[
1048
+ "time"
1049
+ ].values,
1050
+ )
1051
+ for i in self.sat_imgs.sat_img.site_names.values
1052
+ ]
1053
+ ).T,
1054
+ )
1055
+ }
1056
+ )
1057
+
1058
+ elif lonlats is None: # If smoothing the entire array
1059
+ if "timestamps" in list(self.sat_imgs.sat_img.data_vars):
1060
+ for key in keys:
1061
+ self.sat_imgs.sat_img = self.sat_imgs.sat_img.assign(
1062
+ {
1063
+ key: (
1064
+ ["time_gap_filled", "y", "x"],
1065
+ np.array(
1066
+ Parallel(n_jobs=n_cpus, max_nbytes=None)(
1067
+ delayed(do_kalman_smoothing)(
1068
+ self.sat_imgs.sat_img[key][:, :, i].values,
1069
+ timestamps=self.sat_imgs.sat_img[
1070
+ "timestamps"
1071
+ ][:, :, i].values,
1072
+ )
1073
+ for i, x_coord in enumerate(
1074
+ self.sat_imgs.sat_img.x.values
1075
+ )
1076
+ )
1077
+ ).T,
1078
+ )
1079
+ }
1080
+ )
1081
+ else:
1082
+ for key in keys:
1083
+ self.sat_imgs.sat_img = self.sat_imgs.sat_img.assign(
1084
+ {
1085
+ key: (
1086
+ ["time_gap_filled", "y", "x"],
1087
+ np.array(
1088
+ Parallel(n_jobs=n_cpus, max_nbytes=None)(
1089
+ delayed(do_kalman_smoothing)(
1090
+ self.sat_imgs.sat_img[key][:, :, i].values,
1091
+ timestamps=self.sat_imgs.sat_img[
1092
+ "time"
1093
+ ].values,
1094
+ )
1095
+ for i, x_coord in enumerate(
1096
+ self.sat_imgs.sat_img.x.values
1097
+ )
1098
+ )
1099
+ ).T,
1100
+ )
1101
+ }
1102
+ )
1103
+
1104
+ else:
1105
+ logger.info("Not implemented")
1106
+ # Originally had a function to smooth only at specific lat/long.
1107
+ # That doesn't make sense anymore, because the time dimension will change through lowess smoothing.
1108
+ # If this is your plan then try to crop the sat image first and then do the lowess filtering.
1109
+
1110
+ self.time_key = "time_gap_filled"
1111
+ self.sat_imgs.sat_img = self.sat_imgs.sat_img.assign_coords(
1112
+ {"time_gap_filled": list(np.arange(self.tot_num_days+1))}
1113
+ )
1114
+ self.sat_imgs.sat_img = self.sat_imgs.sat_img.sel({"time_gap_filled": list(xvals)})
1115
+ return
1116
+
967
1117
  def clip_values(self, key, min_val, max_val, to_nan=False):
968
1118
  if to_nan:
969
1119
  self.sat_imgs.sat_img[key].values[
@@ -0,0 +1,4 @@
1
+ __version__ = "5.3"
2
+
3
+
4
+
@@ -0,0 +1,108 @@
1
+ import os
2
+ import numpy as np
3
+ import pandas as pd
4
+ import xarray as xr
5
+ from scipy.special import gamma
6
+ from pyVPRM.flux_tower_libs.base_footprint_class import base_footprint_manager
7
+
8
+
9
+ class FFP_footprint_manager(base_footprint_manager):
10
+ def __init__(self, time_stamps, flux_tower_manager, calculation_grid_side_length, calculation_grid_pixels_per_side, era5_instance = None):
11
+ super().__init__(time_stamps, flux_tower_manager, calculation_grid_side_length, calculation_grid_pixels_per_side, era5_instance)
12
+ self.footprint_model = 'FFP'
13
+
14
+ def print_parameters(self):
15
+ print('Parameters of model', self.footprint_model)
16
+ print('u')
17
+ print(self.u)
18
+ print('ZL')
19
+ print(self.ZL)
20
+ print('u dir')
21
+ print(self.u_dir)
22
+ print('u*')
23
+ print(self.u_star)
24
+ print('sigma v')
25
+ print(self.sigma_v)
26
+ print('boundary layer h')
27
+ print(self.h_pbl)
28
+ print('z')
29
+ print(self.z)
30
+ print('t')
31
+ print(self.time_stamps)
32
+
33
+ def calculate_footprints(self):
34
+ k_karman = 0.4
35
+ #calculate secondary parameters
36
+ X_star = self.X_calculation_grid_rotated/np.expand_dims(self.z, axis =(1,2)) * np.expand_dims((1-self.z/self.h_pbl)*(self.u/self.u_star*k_karman)**-1, axis=(1,2))
37
+ p = np.where(self.ZL<=0, 0.8, 0.55)
38
+ ps1 = np.minimum(1, np.abs(self.ZL)**-1*10**-5+p)
39
+
40
+
41
+ #calculate crosswind_integrated_flux
42
+ a = 1.452
43
+ b = -1.991
44
+ c = 1.462
45
+ d = 0.136
46
+ k_karman = 0.4
47
+ Fy_star = a*(X_star-d)**b*np.exp(-c/(X_star-d))
48
+ crosswind_integrated_flux = Fy_star*np.expand_dims((1-self.z/self.h_pbl)*self.u_star/(self.z*self.u*k_karman), axis=(1,2))
49
+ if not np.all(np.logical_or(np.isnan(crosswind_integrated_flux), crosswind_integrated_flux==0)):
50
+ crosswind_integrated_flux = np.where(np.isnan(crosswind_integrated_flux), 0, crosswind_integrated_flux)
51
+
52
+ #calculate crosswind_distribution
53
+ a_c = 2.17
54
+ b_c = 1.66
55
+ c_c = 20.0
56
+ sigma_y_star = a_c*(b_c*(X_star)**2/(1+c_c*X_star))**.5
57
+ sigma_y = sigma_y_star*np.expand_dims((self.z*self.sigma_v)/(ps1*self.u_star), axis=(1,2))
58
+ crosswind_distribution = 1/((2*np.pi)**.5*sigma_y) * np.exp(-self.Y_calculation_grid_rotated**2/(2*sigma_y**2))
59
+ crosswind_distribution = np.where(np.isnan(crosswind_distribution), 0, crosswind_distribution)
60
+
61
+ #calculate footprints
62
+ footprint = crosswind_distribution*crosswind_integrated_flux
63
+ integrated_footprint = np.nansum(footprint, axis=(1,2))*self.side_length_pixel**2
64
+
65
+ #calculate peak estimator
66
+ x_peak = 0.87 * self.z * (1-self.z/self.h_pbl)**-1 * (self.u/self.u_star*k_karman)
67
+
68
+ #calculate fetch estimator
69
+ x_70 = (-c/np.log(0.7)+d) * self.z * (1-self.z/self.h_pbl)**-1 * (self.u/self.u_star*k_karman)
70
+ x_90 = (-c/np.log(0.9)+d) * self.z * (1-self.z/self.h_pbl)**-1 * (self.u/self.u_star*k_karman)
71
+
72
+ #store in xarray
73
+ self.footprint_on_calculation_grid = xr.Dataset(
74
+ data_vars=dict(
75
+ footprint = (["t", "y", "x"], footprint),
76
+ integrated = (["t"], integrated_footprint),
77
+ x_peak = (["t"], x_peak),
78
+ x_70 = (["t"], x_70),
79
+ x_90 = (["t"], x_90),
80
+
81
+ ),
82
+ coords=dict(
83
+ t=("t", self.time_stamps),
84
+ y=("y", self.ys_for_calculation_grid),
85
+ x=("x", self.xs_for_calculation_grid),
86
+ ),
87
+ )
88
+
89
+
90
+ def calculate_crosswind_integrated_flux(self):
91
+ k_karman = 0.4
92
+ #calculate secondary parameters
93
+ X_star = self.xs_for_calculation_grid/np.expand_dims(self.z, axis =(1)) * np.expand_dims((1-self.z/self.h_pbl)*(self.u/self.u_star*k_karman)**-1, axis=(1))
94
+ p = np.where(self.ZL<=0, 0.8, 0.55)
95
+ ps1 = np.minimum(1, np.abs(self.ZL)**-1*10**-5+p)
96
+
97
+
98
+ #calculate crosswind_integrated_flux
99
+ a = 1.452
100
+ b = -1.991
101
+ c = 1.462
102
+ d = 0.136
103
+ k_karman = 0.4
104
+ Fy_star = a*(X_star-d)**b*np.exp(-c/(X_star-d))
105
+ crosswind_integrated_flux = Fy_star*np.expand_dims((1-self.z/self.h_pbl)*self.u_star/(self.z*self.u*k_karman), axis=(1))
106
+ if not np.all(np.logical_or(np.isnan(crosswind_integrated_flux), crosswind_integrated_flux==0)):
107
+ crosswind_integrated_flux = np.where(np.isnan(crosswind_integrated_flux), 0, crosswind_integrated_flux)
108
+ self.crosswind_integrated_flux = crosswind_integrated_flux
@@ -0,0 +1,101 @@
1
+ import os
2
+ import numpy as np
3
+ import pandas as pd
4
+ import xarray as xr
5
+ from scipy.special import gamma
6
+ from pyVPRM.flux_tower_libs.base_footprint_class import base_footprint_manager
7
+
8
+ class KM_footprint_manager(base_footprint_manager):
9
+ def __init__(self, time_stamps, flux_tower_manager, calculation_grid_side_length, calculation_grid_pixels_per_side):
10
+ super().__init__(time_stamps, flux_tower_manager, calculation_grid_side_length, calculation_grid_pixels_per_side)
11
+ self.footprint_model = 'KM'
12
+
13
+ def print_parameters(self):
14
+ print('Parameters of model', self.footprint_model)
15
+ print('u')
16
+ print(self.u)
17
+ print('L')
18
+ print(self.ZL)
19
+ print('u dir')
20
+ print(self.u_dir)
21
+ print('u*')
22
+ print(self.u_star)
23
+ print('sigma v')
24
+ print(self.sigma_v)
25
+ print('t')
26
+ print(self.time_stamps)
27
+
28
+
29
+ def calculate_footprints(self):
30
+ #print('calculate KM footprint')
31
+ k_karman = 0.4
32
+ #calculate secondary parameters
33
+ stability_c = np.where(self.ZL > 0, (1+5*self.ZL), (1-16*self.ZL)**(-1/2))
34
+ stability_m = np.where(self.ZL > 0, (1+5*self.ZL), (1-16*self.ZL)**(-1/4))
35
+ n = np.where(self.ZL > 0, 1/(1+5*self.ZL), (1-24*self.ZL)/(1-16*self.ZL))
36
+ #stability_c = np.where(self.L > 0, (1+5*self.z/self.L), (1-16*self.z/self.L)**(-1/2))
37
+ #stability_m = np.where(self.L > 0, (1+5*self.z/self.L), (1-16*self.z/self.L)**(-1/4))
38
+ #n = np.where(self.L > 0, 1/(1+5*self.z/self.L), (1-24*self.z/self.L)/(1-16*self.z/self.L))
39
+ kappa = k_karman*self.u_star*self.z/(stability_c*self.z**n)
40
+ m = self.u_star*stability_m/(k_karman*self.u)
41
+ r = 2+m-n
42
+ mu = (1+m)/r
43
+ #U = self.u/self.z**m
44
+ U = self.u_star*stability_m/(k_karman*m*self.z**m)
45
+ xi = U*self.z**r/(r**2*kappa)
46
+
47
+ #calculate crosswind_integrated_flux
48
+ crosswind_integrated_flux = np.expand_dims(xi**mu/gamma(mu), axis=(1,2)) / self.X_calculation_grid_rotated**np.expand_dims((1+mu), axis=(1,2)) * np.exp(-np.expand_dims(xi, axis=(1,2))/self.X_calculation_grid_rotated)
49
+ if not np.all(np.logical_or(np.isnan(crosswind_integrated_flux), crosswind_integrated_flux==0)):
50
+ crosswind_integrated_flux = np.where(np.isnan(crosswind_integrated_flux), 0, crosswind_integrated_flux)
51
+
52
+ #calculate crosswind_distribution
53
+ u_bar = np.expand_dims(gamma(mu)/gamma(1/r)*(r**2*kappa/U)**(m/r)*U, axis=(1,2))*self.X_calculation_grid_rotated**np.expand_dims((m/r), axis=(1,2))
54
+ sigma_y = self.X_calculation_grid_rotated*np.expand_dims(self.sigma_v, axis=(1,2))/u_bar
55
+ crosswind_distribution = 1/((2*np.pi)**.5*sigma_y) * np.exp(-self.Y_calculation_grid_rotated**2/(2*sigma_y**2))
56
+ #if not np.all(np.logical_or(np.isnan(crosswind_distribution), crosswind_distribution==0)):
57
+ crosswind_distribution = np.where(np.isnan(crosswind_distribution), 0, crosswind_distribution)
58
+
59
+ #calculate footprints:
60
+ footprint = crosswind_distribution*crosswind_integrated_flux
61
+ integrated_footprint = np.nansum(footprint, axis=(1,2))*self.side_length_pixel**2
62
+
63
+ #calculate peak estimator
64
+ x_peak = xi/(1+mu)
65
+
66
+ #store in xarray
67
+ self.footprint_on_calculation_grid = xr.Dataset(
68
+ data_vars=dict(
69
+ footprint = (["t", "y", "x"], footprint),
70
+ integrated = (["t"], integrated_footprint),
71
+ x_peak = (["t"], x_peak),
72
+ ),
73
+ coords=dict(
74
+ t=("t", self.time_stamps),
75
+ y=("y", self.ys_for_calculation_grid),
76
+ x=("x", self.xs_for_calculation_grid),
77
+ ),
78
+ )
79
+
80
+ def calculate_crosswind_integrated_flux(self):
81
+ k_karman = 0.4
82
+ #calculate secondary parameters
83
+ stability_c = np.where(self.ZL > 0, (1+5*self.ZL), (1-16*self.ZL)**(-1/2))
84
+ stability_m = np.where(self.ZL > 0, (1+5*self.ZL), (1-16*self.ZL)**(-1/4))
85
+ n = np.where(self.ZL > 0, 1/(1+5*self.ZL), (1-24*self.ZL)/(1-16*self.ZL))
86
+ #stability_c = np.where(self.L > 0, (1+5*self.z/self.L), (1-16*self.z/self.L)**(-1/2))
87
+ #stability_m = np.where(self.L > 0, (1+5*self.z/self.L), (1-16*self.z/self.L)**(-1/4))
88
+ #n = np.where(self.L > 0, 1/(1+5*self.z/self.L), (1-24*self.z/self.L)/(1-16*self.z/self.L))
89
+ kappa = k_karman*self.u_star*self.z/(stability_c*self.z**n)
90
+ m = self.u_star*stability_m/(k_karman*self.u)
91
+ r = 2+m-n
92
+ mu = (1+m)/r
93
+ #U = self.u/self.z**m
94
+ U = self.u_star*stability_m/(k_karman*m*self.z**m)
95
+ xi = U*self.z**r/(r**2*kappa)
96
+
97
+ #calculate crosswind_integrated_flux
98
+ crosswind_integrated_flux = np.expand_dims(xi**mu/gamma(mu), axis=(1)) / self.xs_for_calculation_grid**np.expand_dims((1+mu), axis=(1)) * np.exp(-np.expand_dims(xi, axis=(1))/self.xs_for_calculation_grid)
99
+ if not np.all(np.logical_or(np.isnan(crosswind_integrated_flux), crosswind_integrated_flux==0)):
100
+ crosswind_integrated_flux = np.where(np.isnan(crosswind_integrated_flux), 0, crosswind_integrated_flux)
101
+ self.crosswind_integrated_flux = crosswind_integrated_flux