pypromice 1.3.5__py3-none-any.whl → 1.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 pypromice might be problematic. Click here for more details.

Files changed (55) hide show
  1. pypromice/get/get.py +19 -19
  2. pypromice/postprocess/bufr_to_csv.py +6 -1
  3. pypromice/postprocess/bufr_utilities.py +91 -18
  4. pypromice/postprocess/create_bufr_files.py +178 -0
  5. pypromice/postprocess/get_bufr.py +248 -397
  6. pypromice/postprocess/make_metadata_csv.py +214 -0
  7. pypromice/postprocess/real_time_utilities.py +41 -11
  8. pypromice/process/L0toL1.py +12 -5
  9. pypromice/process/L1toL2.py +159 -30
  10. pypromice/process/L2toL3.py +1034 -187
  11. pypromice/process/aws.py +131 -752
  12. pypromice/process/get_l2.py +90 -0
  13. pypromice/process/get_l2tol3.py +111 -0
  14. pypromice/process/join_l2.py +112 -0
  15. pypromice/process/join_l3.py +551 -120
  16. pypromice/process/load.py +161 -0
  17. pypromice/process/resample.py +128 -0
  18. pypromice/process/utilities.py +68 -0
  19. pypromice/process/write.py +503 -0
  20. pypromice/qc/github_data_issues.py +10 -16
  21. pypromice/qc/percentiles/thresholds.csv +2 -2
  22. pypromice/qc/persistence.py +71 -25
  23. pypromice/resources/__init__.py +28 -0
  24. pypromice/{process/metadata.csv → resources/file_attributes.csv} +0 -2
  25. pypromice/resources/variable_aliases_GC-Net.csv +78 -0
  26. pypromice/resources/variables.csv +106 -0
  27. pypromice/station_configuration.py +118 -0
  28. pypromice/tx/get_l0tx.py +7 -4
  29. pypromice/tx/payload_formats.csv +1 -0
  30. pypromice/tx/tx.py +27 -6
  31. pypromice/utilities/__init__.py +0 -0
  32. pypromice/utilities/git.py +61 -0
  33. {pypromice-1.3.5.dist-info → pypromice-1.4.0.dist-info}/METADATA +12 -21
  34. pypromice-1.4.0.dist-info/RECORD +53 -0
  35. {pypromice-1.3.5.dist-info → pypromice-1.4.0.dist-info}/WHEEL +1 -1
  36. pypromice-1.4.0.dist-info/entry_points.txt +13 -0
  37. pypromice/postprocess/station_configurations.toml +0 -762
  38. pypromice/process/get_l3.py +0 -46
  39. pypromice/process/variables.csv +0 -92
  40. pypromice/qc/persistence_test.py +0 -150
  41. pypromice/test/test_config1.toml +0 -69
  42. pypromice/test/test_config2.toml +0 -54
  43. pypromice/test/test_email +0 -75
  44. pypromice/test/test_payload_formats.csv +0 -4
  45. pypromice/test/test_payload_types.csv +0 -7
  46. pypromice/test/test_percentile.py +0 -229
  47. pypromice/test/test_raw1.txt +0 -4468
  48. pypromice/test/test_raw_DataTable2.txt +0 -11167
  49. pypromice/test/test_raw_SlimTableMem1.txt +0 -1155
  50. pypromice/test/test_raw_transmitted1.txt +0 -15411
  51. pypromice/test/test_raw_transmitted2.txt +0 -28
  52. pypromice-1.3.5.dist-info/RECORD +0 -53
  53. pypromice-1.3.5.dist-info/entry_points.txt +0 -8
  54. {pypromice-1.3.5.dist-info → pypromice-1.4.0.dist-info}/LICENSE.txt +0 -0
  55. {pypromice-1.3.5.dist-info → pypromice-1.4.0.dist-info}/top_level.txt +0 -0
@@ -9,16 +9,40 @@ __all__ = [
9
9
  "persistence_qc",
10
10
  "find_persistent_regions",
11
11
  "count_consecutive_persistent_values",
12
- "count_consecutive_true",
12
+ "get_duration_consecutive_true",
13
13
  ]
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
17
+ # period is given in hours, 2 persistent 10 min values will be flagged if period < 0.333
17
18
  DEFAULT_VARIABLE_THRESHOLDS = {
18
- "t": {"max_diff": 0.0001, "period": 2},
19
- "p": {"max_diff": 0.0001, "period": 2},
20
- # Relative humidity can be very stable around 100%.
21
- #"rh": {"max_diff": 0.0001, "period": 2},
19
+ "t_i": {"max_diff": 0.0001, "period": 2},
20
+ "t_u": {"max_diff": 0.0001, "period": 2},
21
+ "t_l": {"max_diff": 0.0001, "period": 2},
22
+ "p_i": {"max_diff": 0.0001, "period": 2},
23
+ # "p_u": {"max_diff": 0.0001, "period": 2},
24
+ # "p_l": {"max_diff": 0.0001, "period": 2},
25
+ "gps_lat_lon": {
26
+ "max_diff": 0.000001,
27
+ "period": 6,
28
+ }, # gets special handling to remove simultaneously constant gps_lat and gps_lon
29
+ "gps_alt": {"max_diff": 0.0001, "period": 6},
30
+ "t_rad": {"max_diff": 0.0001, "period": 2},
31
+ "rh_i": {
32
+ "max_diff": 0.0001,
33
+ "period": 2,
34
+ }, # gets special handling to allow constant 100%
35
+ "rh_u": {
36
+ "max_diff": 0.0001,
37
+ "period": 2,
38
+ }, # gets special handling to allow constant 100%
39
+ "rh_l": {
40
+ "max_diff": 0.0001,
41
+ "period": 2,
42
+ }, # gets special handling to allow constant 100%
43
+ "wspd_i": {"max_diff": 0.0001, "period": 6},
44
+ "wspd_u": {"max_diff": 0.0001, "period": 6},
45
+ "wspd_l": {"max_diff": 0.0001, "period": 6},
22
46
  }
23
47
 
24
48
 
@@ -57,21 +81,27 @@ def persistence_qc(
57
81
 
58
82
  if variable_thresholds is None:
59
83
  variable_thresholds = DEFAULT_VARIABLE_THRESHOLDS
60
-
61
- logger.debug(f"Running persistence_qc using {variable_thresholds}")
84
+ logger.debug(f"Running persistence_qc using {variable_thresholds}")
85
+ else:
86
+ logger.info(f"Running persistence_qc using custom thresholds:\n {variable_thresholds}")
62
87
 
63
88
  for k in variable_thresholds.keys():
64
- var_all = [
65
- k + "_u",
66
- k + "_l",
67
- k + "_i",
68
- ] # apply to upper, lower boom, and instant
89
+ if k in ["t", "p", "rh", "wspd", "wdir", "z_boom"]:
90
+ var_all = [
91
+ k + "_u",
92
+ k + "_l",
93
+ k + "_i",
94
+ ] # apply to upper, lower boom, and instant
95
+ else:
96
+ var_all = [k]
69
97
  max_diff = variable_thresholds[k]["max_diff"] # loading persistent limit
70
98
  period = variable_thresholds[k]["period"] # loading diff period
71
99
 
72
100
  for v in var_all:
73
101
  if v in df:
74
102
  mask = find_persistent_regions(df[v], period, max_diff)
103
+ if "rh" in v:
104
+ mask = mask & (df[v] < 99)
75
105
  n_masked = mask.sum()
76
106
  n_samples = len(mask)
77
107
  logger.debug(
@@ -79,6 +109,19 @@ def persistence_qc(
79
109
  )
80
110
  # setting outliers to NaN
81
111
  df.loc[mask, v] = np.nan
112
+ elif v == "gps_lat_lon":
113
+ mask = find_persistent_regions(
114
+ df["gps_lon"], period, max_diff
115
+ ) & find_persistent_regions(df["gps_lat"], period, max_diff)
116
+
117
+ n_masked = mask.sum()
118
+ n_samples = len(mask)
119
+ logger.debug(
120
+ f"Applying persistent QC in {v}. Filtering {n_masked}/{n_samples} samples"
121
+ )
122
+ # setting outliers to NaN
123
+ df.loc[mask, "gps_lon"] = np.nan
124
+ df.loc[mask, "gps_lat"] = np.nan
82
125
 
83
126
  # Back to xarray, and re-assign the original attrs
84
127
  ds_out = df.to_xarray()
@@ -110,33 +153,36 @@ def count_consecutive_persistent_values(
110
153
  ) -> pd.Series:
111
154
  diff = data.ffill().diff().abs() # forward filling all NaNs!
112
155
  mask: pd.Series = diff < max_diff
113
- return count_consecutive_true(mask)
156
+ return get_duration_consecutive_true(mask)
114
157
 
115
158
 
116
- def count_consecutive_true(
117
- series: Union[pd.Series, pd.DataFrame]
118
- ) -> Union[pd.Series, pd.DataFrame]:
159
+ def get_duration_consecutive_true(
160
+ series: pd.Series,
161
+ ) -> pd.Series:
119
162
  """
120
- Convert boolean series to integer series where the values represent the number of connective true values.
163
+ From a boolean series, calculates the duration, in hours, of the periods with concecutive true values.
164
+
165
+ The first value will be set to NaN, as it is not possible to calculate the duration of a single value.
121
166
 
122
167
  Examples
123
168
  --------
124
- >>> count_consecutive_true(pd.Series([False, True, False, False, True, True, True, False, True]))
125
- pd.Series([0, 1, 0, 0, 1, 2, 3, 0, 1])
169
+ >>> get_duration_consecutive_true(pd.Series([False, True, False, False, True, True, True, False, True]))
170
+ pd.Series([np.nan, 1, 0, 0, 1, 2, 3, 0, 1])
126
171
 
127
172
  Parameters
128
173
  ----------
129
- series
174
+ pd.Series
130
175
  Boolean pandas Series or DataFrame
131
176
 
132
177
  Returns
133
178
  -------
134
- consecutive_true_count
179
+ pd.Series
135
180
  Integer pandas Series or DataFrame with values representing the number of connective true values.
136
181
 
137
182
  """
138
- # assert series.dtype == bool
139
- cumsum = series.cumsum()
140
183
  is_first = series.astype("int").diff() == 1
141
- offset = (is_first * cumsum).replace(0, np.nan).fillna(method="ffill").fillna(0)
142
- return ((cumsum - offset + 1) * series).astype("int")
184
+ delta_time = (series.index.to_series().diff().dt.total_seconds() / 3600)
185
+ cumsum = delta_time.cumsum()
186
+ offset = (is_first * (cumsum - delta_time)).replace(0, np.nan).ffill().fillna(0)
187
+
188
+ return (cumsum - offset) * series
@@ -0,0 +1,28 @@
1
+ import csv
2
+ from pathlib import Path
3
+ from typing import Dict, Union
4
+
5
+ import pandas as pd
6
+
7
+ DEFAULT_METADATA_PATH = (Path(__file__).parent / "file_attributes.csv").absolute()
8
+ DEFAULT_VARIABLES_PATH = (Path(__file__).parent / "variables.csv").absolute()
9
+ DEFAULT_VARIABLES_ALIASES_GCNET_PATH = (Path(__file__).parent / "variable_aliases_GC-Net.csv").absolute()
10
+
11
+ def load_metadata(path: Union[None, str, Path] = None) -> Dict[str, str]:
12
+ """
13
+ Load metadata table from csv file
14
+ """
15
+ if path is None:
16
+ path = DEFAULT_METADATA_PATH
17
+ with open(path, "r") as f:
18
+ csv_reader = csv.reader(f)
19
+ return {row[0]: row[1] for row in csv_reader}
20
+
21
+
22
+ def load_variables(path: Union[None, str, Path] = None) -> pd.DataFrame:
23
+ """
24
+ Load variables table from csv file
25
+ """
26
+ if path is None:
27
+ path = DEFAULT_VARIABLES_PATH
28
+ return pd.read_csv(path, index_col=0, comment="#")
@@ -40,11 +40,9 @@ metadata_link,
40
40
  naming_authority,dk.geus.promice
41
41
  platform,
42
42
  platform_vocabulary,GCMD:GCMD Keywords
43
- processing_level,Level 3
44
43
  product_status,beta
45
44
  product_version,4
46
45
  program,PROMICE
47
- project,PROMICE
48
46
  publisher_email,info@promice.dk
49
47
  publisher_institution,GEUS
50
48
  publisher_name,GEUS
@@ -0,0 +1,78 @@
1
+ GEUS_name,old_name
2
+ time,timestamp
3
+ p_u,P
4
+ p_l,
5
+ t_u,TA2
6
+ t_l,TA1
7
+ rh_u,RH2
8
+ rh_u_cor,RH2_cor
9
+ qh_u,Q2
10
+ rh_l,RH1
11
+ rh_l_cor,RH1_cor
12
+ qh_l,Q1
13
+ wspd_u,VW2
14
+ wspd_l,VW1
15
+ wdir_u,DW2
16
+ wdir_l,DW1
17
+ dsr,
18
+ dsr_cor,ISWR
19
+ usr,
20
+ usr_cor,OSWR
21
+ albedo,Alb
22
+ dlr,
23
+ ulr,
24
+ cc,
25
+ t_surf,
26
+ dlhf_u,LHF
27
+ dlhf_l,
28
+ dshf_u,SHF
29
+ dshf_l,
30
+ z_boom_u,HW2
31
+ z_boom_l,HW1
32
+ precip_u,
33
+ precip_u_cor,
34
+ precip_l,
35
+ precip_l_cor,
36
+ t_i_1,TS1
37
+ t_i_2,TS2
38
+ t_i_3,TS3
39
+ t_i_4,TS4
40
+ t_i_5,TS5
41
+ t_i_6,TS6
42
+ t_i_7,TS7
43
+ t_i_8,TS8
44
+ t_i_9,TS9
45
+ t_i_10,TS10
46
+ t_i_11,
47
+ tilt_x,
48
+ tilt_y,
49
+ rot,
50
+ lat,latitude
51
+ lon,longitude
52
+ alt,elevation
53
+ gps_time,
54
+ gps_geounit,
55
+ gps_hdop,
56
+ batt_v,V
57
+ fan_dc_u,
58
+ fan_dc_l,
59
+ t_rad,
60
+ msg_lat,
61
+ msg_lon,
62
+ z_surf_1,HS1
63
+ z_surf_2,HS2
64
+ z_surf_1_adj_flag,HS1_adj_flag
65
+ z_surf_2_adj_flag,HS2_adj_flag
66
+ z_surf_combined,HS_combined
67
+ d_t_i_1,DTS1
68
+ d_t_i_2,DTS2
69
+ d_t_i_3,DTS3
70
+ d_t_i_4,DTS4
71
+ d_t_i_5,DTS5
72
+ d_t_i_6,DTS6
73
+ d_t_i_7,DTS7
74
+ d_t_i_8,DTS8
75
+ d_t_i_9,DTS9
76
+ d_t_i_10,DTS10
77
+ d_t_i_11,
78
+ t_i_10m,TS_10m
@@ -0,0 +1,106 @@
1
+ field,standard_name,long_name,units,coverage_content_type,coordinates,instantaneous_hourly,where_to_find,lo,hi,OOL,station_type,L0,L2,L3,max_decimals
2
+ time,time,Time,yyyy-mm-dd HH:MM:SS,physicalMeasurement,time,,,,,,all,1,1,1,
3
+ rec,record,Record,-,referenceInformation,time,,L0 or L2,,,,all,1,1,0,0
4
+ p_u,air_pressure,Air pressure (upper boom),hPa,physicalMeasurement,time,FALSE,,650,1100,z_pt z_pt_cor dshf_u dlhf_u qh_u,all,1,1,1,4
5
+ p_l,air_pressure,Air pressure (lower boom),hPa,physicalMeasurement,time,FALSE,,650,1100,dshf_l dlhf_l qh_l,two-boom,1,1,1,4
6
+ t_u,air_temperature,Air temperature (upper boom),degrees_C,physicalMeasurement,time,FALSE,,-80,40,rh_u_cor cc dsr_cor usr_cor z_boom z_stake dshf_u dlhf_u qh_u,all,1,1,1,4
7
+ t_l,air_temperature,Air temperature (lower boom),degrees_C,physicalMeasurement,time,FALSE,,-80,40,rh_l_cor z_boom_l dshf_l dlhf_l qh_l,two-boom,1,1,1,4
8
+ rh_u,relative_humidity,Relative humidity (upper boom),%,physicalMeasurement,time,FALSE,,0,100,rh_u_cor,all,1,1,1,4
9
+ rh_u_cor,relative_humidity_corrected,Relative humidity (upper boom) - corrected,%,modelResult,time,FALSE,L2 or later,0,150,dshf_u dlhf_u qh_u,all,0,1,1,4
10
+ qh_u,specific_humidity,Specific humidity (upper boom),kg/kg,modelResult,time,FALSE,L2 or later,0,100,,all,0,1,1,4
11
+ rh_l,relative_humidity,Relative humidity (lower boom),%,physicalMeasurement,time,FALSE,,0,100,rh_l_cor,two-boom,1,1,1,4
12
+ rh_l_cor,relative_humidity_corrected,Relative humidity (lower boom) - corrected,%,modelResult,time,FALSE,L2 or later,0,150,dshf_l dlhf_l qh_l,two-boom,0,1,1,4
13
+ qh_l,specific_humidity,Specific humidity (lower boom),kg/kg,modelResult,time,FALSE,L2 or later,0,100,,two-boom,0,1,1,4
14
+ wspd_u,wind_speed,Wind speed (upper boom),m s-1,physicalMeasurement,time,FALSE,,0,100,"wdir_u wspd_x_u wspd_y_u dshf_u dlhf_u qh_u, precip_u",all,1,1,1,4
15
+ wspd_l,wind_speed,Wind speed (lower boom),m s-1,physicalMeasurement,time,FALSE,,0,100,"wdir_l wspd_x_l wspd_y_l dshf_l dlhf_l qh_l , precip_l",two-boom,1,1,1,4
16
+ wdir_u,wind_from_direction,Wind from direction (upper boom),degrees,physicalMeasurement,time,FALSE,,1,360,wspd_x_u wspd_y_u,all,1,1,1,4
17
+ wdir_std_u,wind_from_direction_standard_deviation,Wind from direction (standard deviation),degrees,qualityInformation,time,FALSE,L0 or L2,,,,one-boom,1,1,0,4
18
+ wdir_l,wind_from_direction,Wind from direction (lower boom),degrees,physicalMeasurement,time,FALSE,,1,360,wspd_x_l wspd_y_l,two-boom,1,1,1,4
19
+ wspd_x_u,wind_speed_from_x_direction,Wind speed from x direction (upper boom),m s-1,modelResult,time,FALSE,L0 or L2,-100,100,wdir_u wspd_u,all,0,1,1,4
20
+ wspd_y_u,wind_speed_from_y_direction,Wind speed from y direction (upper boom),m s-1,modelResult,time,FALSE,L0 or L2,-100,100,wdir_u wspd_u,all,0,1,1,4
21
+ wspd_x_l,wind_speed_from_x_direction,Wind speed from x direction (lower boom),m s-1,modelResult,time,FALSE,L0 or L2,-100,100,wdir_l wspd_l,two-boom,0,1,1,4
22
+ wspd_y_l,wind_speed_from_y_direction,Wind speed from y direction (lower boom),m s-1,modelResult,time,FALSE,L0 or L2,-100,100,wdir_l wspd_l,two-boom,0,1,1,4
23
+ dsr,surface_downwelling_shortwave_flux,Downwelling shortwave radiation,W m-2,physicalMeasurement,time,FALSE,,-10,1500,albedo dsr_cor usr_cor,all,1,1,1,4
24
+ dsr_cor,surface_downwelling_shortwave_flux_corrected,Downwelling shortwave radiation - corrected,W m-2,modelResult,time,FALSE,L2 or later,,,,all,0,1,1,4
25
+ usr,surface_upwelling_shortwave_flux,Upwelling shortwave radiation,W m-2,physicalMeasurement,time,FALSE,,-10,1000,albedo dsr_cor usr_cor,all,1,1,1,4
26
+ usr_cor,surface_upwelling_shortwave_flux_corrected,Upwelling shortwave radiation - corrected,W m-2,modelResult,time,FALSE,L2 or later,0,1000,,all,0,1,1,4
27
+ albedo,surface_albedo,Albedo,-,modelResult,time,FALSE,L2 or later,,,,all,0,1,1,4
28
+ dlr,surface_downwelling_longwave_flux,Downwelling longwave radiation,W m-2,physicalMeasurement,time,FALSE,,50,500,albedo dsr_cor usr_cor cc t_surf,all,1,1,1,4
29
+ ulr,surface_upwelling_longwave_flux,Upwelling longwave radiation,W m-2,physicalMeasurement,time,FALSE,,50,500,t_surf,all,1,1,1,4
30
+ cc,cloud_area_fraction,Cloud cover,%,modelResult,time,FALSE,L2 or later,,,,all,0,1,1,4
31
+ t_surf,surface_temperature,Surface temperature,C,modelResult,time,FALSE,L2 or later,-80,40,dshf_u dlhf_u qh_u,all,0,1,1,4
32
+ dlhf_u,surface_downward_latent_heat_flux,Latent heat flux (upper boom),W m-2,modelResult,time,FALSE,L3 or later,,,,all,0,0,1,4
33
+ dlhf_l,surface_downward_latent_heat_flux,Latent heat flux (lower boom),W m-2,modelResult,time,FALSE,L3 or later,,,,two-boom,0,0,1,4
34
+ dshf_u,surface_downward_sensible_heat_flux,Sensible heat flux (upper boom),W m-2,modelResult,time,FALSE,L3 or later,,,,all,0,0,1,4
35
+ dshf_l,surface_downward_sensible_heat_flux,Sensible heat flux (lower boom),W m-2,modelResult,time,FALSE,L3 or later,,,,two-boom,0,0,1,4
36
+ z_boom_u,distance_to_surface_from_boom,Upper boom height,m,physicalMeasurement,time,TRUE,,0.3,10,dshf_u dlhf_u qh_u,all,1,1,1,4
37
+ z_boom_q_u,distance_to_surface_from_boom_quality,Upper boom height (quality),-,qualityInformation,time,TRUE,L0 or L2,,,,all,1,1,0,4
38
+ z_boom_l,distance_to_surface_from_boom,Lower boom height,m,physicalMeasurement,time,TRUE,,0.3,5,dshf_l dlhf_l qh_l,two-boom,1,1,1,4
39
+ z_boom_q_l,distance_to_surface_from_boom_quality,Lower boom height (quality),-,qualityInformation,time,TRUE,L0 or L2,,,,two-boom,1,1,0,4
40
+ z_stake,distance_to_surface_from_stake_assembly,Stake height,m,physicalMeasurement,time,TRUE,,0.3,8,,one-boom,1,1,1,4
41
+ z_stake_q,distance_to_surface_from_stake_assembly_quality,Stake height (quality),-,qualityInformation,time,TRUE,L0 or L2,,,,one-boom,1,1,0,4
42
+ z_pt,depth_of_pressure_transducer_in_ice,Depth of pressure transducer in ice,m,physicalMeasurement,time,FALSE,,0,30,z_pt_cor,one-boom,1,1,1,4
43
+ z_pt_cor,depth_of_pressure_transducer_in_ice_corrected,Depth of pressure transducer in ice - corrected,m,modelResult,time,FALSE,L2 or later,0,30,,one-boom,0,1,1,4
44
+ z_surf_combined,height_of_surface_combined,"Surface height combined from multiple sensors, relative to ice surface height at installation",m,modelResult,time,FALSE,L3,,,,all,0,0,1,4
45
+ z_ice_surf,height_of_ice_surface,"Ice surface height, relative to ice surface height at installation and calculated from pt_cor and z_stake",m,modelResult,time,FALSE,L3,,,,one-boom,0,0,1,4
46
+ snow_height,height_of_snow,"Snow surface height, relative to ice surface",m,modelResult,time,FALSE,L3,0,,,one-boom,0,0,1,4
47
+ precip_u,precipitation,Precipitation (upper boom) (cumulative solid & liquid),mm,physicalMeasurement,time,TRUE,,0,,precip_u_cor precip_u_rate,all,1,1,1,4
48
+ precip_u_cor,precipitation_corrected,Precipitation (upper boom) (cumulative solid & liquid) – corrected,mm,modelResult,time,TRUE,L2 or later,0,,,all,0,1,1,4
49
+ precip_u_rate,precipitation_rate,Precipitation rate (upper boom) (cumulative solid & liquid) – corrected,mm,modelResult,time,TRUE,L2 or later,0,,,all,0,1,1,4
50
+ precip_l,precipitation,Precipitation (lower boom) (cumulative solid & liquid),mm,physicalMeasurement,time,TRUE,,0,,precip_l_cor precip_l_rate,two-boom,1,1,1,4
51
+ precip_l_cor,precipitation_corrected,Precipitation (lower boom) (cumulative solid & liquid) – corrected,mm,modelResult,time,TRUE,L2 or later,0,,,two-boom,0,1,1,4
52
+ precip_l_rate,precipitation_rate,Precipitation rate (lower boom) (cumulative solid & liquid) – corrected,mm,modelResult,time,TRUE,L2 or later,0,,,two-boom,0,1,1,4
53
+ t_i_1,ice_temperature_at_t1,Ice temperature at sensor 1,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
54
+ t_i_2,ice_temperature_at_t2,Ice temperature at sensor 2,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
55
+ t_i_3,ice_temperature_at_t3,Ice temperature at sensor 3,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
56
+ t_i_4,ice_temperature_at_t4,Ice temperature at sensor 4,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
57
+ t_i_5,ice_temperature_at_t5,Ice temperature at sensor 5,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
58
+ t_i_6,ice_temperature_at_t6,Ice temperature at sensor 6,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
59
+ t_i_7,ice_temperature_at_t7,Ice temperature at sensor 7,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
60
+ t_i_8,ice_temperature_at_t8,Ice temperature at sensor 8,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
61
+ t_i_9,ice_temperature_at_t9,Ice temperature at sensor 9,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,two-boom,1,1,1,4
62
+ t_i_10,ice_temperature_at_t10,Ice temperature at sensor 10,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,two-boom,1,1,1,4
63
+ t_i_11,ice_temperature_at_t11,Ice temperature at sensor 11,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,two-boom,1,1,1,4
64
+ d_t_i_1,depth_of_thermistor_1,Depth of thermistor 1,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
65
+ d_t_i_2,depth_of_thermistor_2,Depth of thermistor 2,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
66
+ d_t_i_3,depth_of_thermistor_3,Depth of thermistor 3,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
67
+ d_t_i_4,depth_of_thermistor_4,Depth of thermistor 4,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
68
+ d_t_i_5,depth_of_thermistor_5,Depth of thermistor 5,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
69
+ d_t_i_6,depth_of_thermistor_6,Depth of thermistor 6,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
70
+ d_t_i_7,depth_of_thermistor_7,Depth of thermistor 7,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
71
+ d_t_i_8,depth_of_thermistor_8,Depth of thermistor 8,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
72
+ d_t_i_9,depth_of_thermistor_9,Depth of thermistor 9,m,modelResult,time,FALSE,L3,-10,100,,two-boom,0,0,1,4
73
+ d_t_i_10,depth_of_thermistor_10,Depth of thermistor 10,m,modelResult,time,FALSE,L3,-10,100,,two-boom,0,0,1,4
74
+ d_t_i_11,depth_of_thermistor_11,Depth of thermistor 11,m,modelResult,time,FALSE,L3,-10,100,,two-boom,0,0,1,4
75
+ t_i_10m,10m_subsurface_temperature,10 m subsurface temperature,degrees_C,modelResult,time,FALSE,L3,-70,0,,all,0,0,1,4
76
+ tilt_x,platform_view_angle_x,Tilt to east,degrees,physicalMeasurement,time,FALSE,,-30,30,dsr_cor usr_cor albedo,all,1,1,1,4
77
+ tilt_y,platform_view_angle_y,Tilt to north,degrees,physicalMeasurement,time,FALSE,,-30,30,dsr_cor usr_cor albedo,all,1,1,1,4
78
+ rot,platform_azimuth_angle,Station rotation from true North,degrees,physicalMeasurement,time,FALSE,,0,360,,all,1,1,1,2
79
+ gps_lat,gps_latitude,Latitude,degrees_north,physicalMeasurement,time,TRUE,,50,83,,all,1,1,1,6
80
+ gps_lon,gps_longitude,Longitude,degrees_east,physicalMeasurement,time,TRUE,,5,70,,all,1,1,1,6
81
+ gps_alt,gps_altitude,Altitude above mean sea level (orthometric height),m,physicalMeasurement,time,TRUE,,0,3000,,all,1,1,1,2
82
+ gps_time,gps_time,GPS time,s,physicalMeasurement,time,TRUE,L0 or L2,0,240000,,all,1,1,0,
83
+ gps_geoid,gps_geoid_separation,Height of EGM96 geoid over WGS84 ellipsoid,m,physicalMeasurement,time,TRUE,L0 or L2,,,,one-boom,1,1,0,
84
+ gps_geounit,gps_geounit,GeoUnit,-,qualityInformation,time,TRUE,L0 or L2,,,,all,1,1,0,
85
+ gps_hdop,gps_hdop,GPS horizontal dillution of precision (HDOP),m,qualityInformation,time,TRUE,L0 or L2,,,,all,1,1,0,2
86
+ gps_numsat,gps_numsat,GPS number of satellites,-,qualityInformation,time,TRUE,L0 or L2,,,,,1,1,0,0
87
+ gps_q,gps_q,Quality,-,qualityInformation,time,TRUE,L0 or L2,,,,,1,1,0,
88
+ lat,latitude_postprocessed,smoothed and interpolated latitude of station,degrees_north,modelResult,time,TRUE,L3,,,,all,0,0,1,6
89
+ lon,longitude_postprocessed,smoothed and interpolated longitude of station,degrees_east,modelResult,time,TRUE,L3,,,,all,0,0,1,6
90
+ alt,altitude_postprocessed,smoothed and interpolated altitude of station above mean sea level (orthometric height),m,modelResult,time,TRUE,L3,,,,all,0,0,1,2
91
+ batt_v,battery_voltage,Battery voltage,V,physicalMeasurement,time,TRUE,,0,30,,all,1,1,1,2
92
+ batt_v_ini,,,-,physicalMeasurement,time,TRUE,L0 or L2,0,30,,,1,1,0,2
93
+ batt_v_ss,battery_voltage_at_sample_start,Battery voltage (sample start),V,physicalMeasurement,time,TRUE,L0 or L2,0,30,,,1,1,0,2
94
+ fan_dc_u,fan_current,Fan current (upper boom),mA,physicalMeasurement,time,TRUE,L0 or L2,0,200,,all,1,1,0,2
95
+ fan_dc_l,fan_current,Fan current (lower boom),mA,physicalMeasurement,time,TRUE,,0,200,,two-boom,1,1,0,2
96
+ freq_vw,frequency_of_precipitation_wire_vibration,Frequency of vibrating wire in precipitation gauge,Hz,physicalMeasurement,time,TRUE,L0 or L2,0,10000,precip_u,,1,1,0,
97
+ t_log,temperature_of_logger,Logger temperature,degrees_C,physicalMeasurement,time,TRUE,,-80,40,,one-boom,1,1,0,4
98
+ t_rad,temperature_of_radiation_sensor,Radiation sensor temperature,degrees_C,physicalMeasurement,time,FALSE,,-80,40,t_surf dlr ulr,all,1,1,1,4
99
+ p_i,air_pressure,Air pressure (instantaneous) minus 1000,hPa,physicalMeasurement,time,TRUE,,-350,100,,all,1,1,1,4
100
+ t_i,air_temperature,Air temperature (instantaneous),degrees_C,physicalMeasurement,time,TRUE,,-80,40,,all,1,1,1,4
101
+ rh_i,relative_humidity,Relative humidity (instantaneous),%,physicalMeasurement,time,TRUE,,0,150,rh_i_cor,all,1,1,1,4
102
+ rh_i_cor,relative_humidity_corrected,Relative humidity (instantaneous) – corrected,%,modelResult,time,TRUE,L2 or later,0,100,,all,0,1,1,4
103
+ wspd_i,wind_speed,Wind speed (instantaneous),m s-1,physicalMeasurement,time,TRUE,,0,100,wdir_i wspd_x_i wspd_y_i,all,1,1,1,4
104
+ wdir_i,wind_from_direction,Wind from direction (instantaneous),degrees,physicalMeasurement,time,TRUE,,1,360,wspd_x_i wspd_y_i,all,1,1,1,4
105
+ wspd_x_i,wind_speed_from_x_direction,Wind speed from x direction (instantaneous),m s-1,modelResult,time,TRUE,L2 or later,-100,100,wdir_i wspd_i,all,0,1,1,4
106
+ wspd_y_i,wind_speed_from_y_direction,Wind speed from y direction (instantaneous),m s-1,modelResult,time,TRUE,L2 or later,-100,100,wdir_i wspd_i,all,0,1,1,4
@@ -0,0 +1,118 @@
1
+ import logging
2
+ from pathlib import Path
3
+ from typing import Optional, Dict, Mapping, Sequence
4
+
5
+ import attrs
6
+ import toml
7
+
8
+
9
+ @attrs.define
10
+ class StationConfiguration:
11
+ """
12
+ Helper class for storing station specific configurations with respect to
13
+
14
+ * Installation specific distance measurements such as height differences between instruments
15
+ * Reference strings such as stid, station_site and wmo_id
16
+ * BUFR export specific parameters
17
+
18
+ # TODO: The station related meta data should be fetched from a station specific configuration files in the future or
19
+ # from header data in data source.
20
+ """
21
+
22
+ stid: str
23
+ station_site: str = None
24
+ project: Optional[str] = None
25
+ station_type: Optional[str] = None
26
+ wmo_id: Optional[str] = None
27
+ barometer_from_gps: Optional[float] = None
28
+ anemometer_from_sonic_ranger: Optional[float] = None
29
+ temperature_from_sonic_ranger: Optional[float] = None
30
+ height_of_gps_from_station_ground: Optional[float] = None
31
+ sonic_ranger_from_gps: Optional[float] = None
32
+ static_height_of_gps_from_mean_sea_level: Optional[float] = None
33
+ station_relocation: Sequence[str] = attrs.field(factory=list)
34
+ location_type: Optional[str] = None
35
+
36
+ # The station data will be exported to BUFR if True. Otherwise, it will only export latest position
37
+ export_bufr: bool = False
38
+ comment: Optional[str] = None
39
+
40
+ # skip specific variables for stations
41
+ # If a variable has known bad data, use this collection to skip the variable
42
+ # Note that if a station is not reporting both air temp and pressure it will be skipped,
43
+ # as currently implemented in csv2bufr.min_data_check().
44
+ # ['p_i'], # EXAMPLE
45
+ skipped_variables: Sequence[str] = attrs.field(factory=list)
46
+
47
+ positions_update_timestamp_only: bool = False
48
+
49
+ @classmethod
50
+ def load_toml(cls, path, skip_unexpected_fields=False):
51
+ config_fields = {field.name for field in attrs.fields(cls)}
52
+ input_dict = toml.load(path)
53
+ unexpected_fields = set(input_dict.keys()) - config_fields
54
+ if unexpected_fields:
55
+ if skip_unexpected_fields:
56
+ logging.info(
57
+ f"Skipping unexpected fields in toml file {path}: "
58
+ + ", ".join(unexpected_fields)
59
+ )
60
+ for field in unexpected_fields:
61
+ input_dict.pop(field)
62
+ else:
63
+ raise ValueError(f"Unexpected fields: {unexpected_fields}")
64
+
65
+ return cls(**input_dict)
66
+
67
+ def dump_toml(self, path: Path):
68
+ with path.open("w") as fp:
69
+ toml.dump(self.as_dict(), fp)
70
+
71
+ def as_dict(self) -> Dict:
72
+ return attrs.asdict(self)
73
+
74
+
75
+ def load_station_configuration_mapping(
76
+ configurations_root_dir: Path,
77
+ **kwargs,
78
+ ) -> Mapping[str, StationConfiguration]:
79
+ """
80
+ Load station configurations from toml files in configurations_root_dir
81
+
82
+ Parameters
83
+ ----------
84
+ configurations_root_dir
85
+ Root directory containing toml files
86
+ kwargs
87
+ Additional arguments to pass to StationConfiguration.load_toml
88
+
89
+ Returns
90
+ -------
91
+ Mapping from stid to StationConfiguration
92
+
93
+ """
94
+ return {
95
+ config_file.stem: StationConfiguration.load_toml(config_file, **kwargs)
96
+ for config_file in configurations_root_dir.glob("*.toml")
97
+ }
98
+
99
+
100
+ def write_station_configuration_mapping(
101
+ station_configurations: Mapping[str, StationConfiguration],
102
+ configurations_root_dir: Path,
103
+ ) -> None:
104
+ """
105
+ Write station configurations to toml files in configurations_root_dir
106
+
107
+ Parameters
108
+ ----------
109
+ station_configurations
110
+ Mapping from stid to StationConfiguration
111
+ configurations_root_dir
112
+ Output directory
113
+
114
+ """
115
+ configurations_root_dir.mkdir(parents=True, exist_ok=True)
116
+ for stid, station_configuration in station_configurations.items():
117
+ with (configurations_root_dir / f"{stid}.toml").open("w") as fp:
118
+ toml.dump(station_configuration.as_dict(), fp)
pypromice/tx/get_l0tx.py CHANGED
@@ -6,7 +6,7 @@ import os, imaplib, email, toml, re, unittest
6
6
  from glob import glob
7
7
  from datetime import datetime, timedelta
8
8
 
9
- from pypromice.tx import getMail, L0tx, sortLines
9
+ from pypromice.tx import getMail, L0tx, sortLines, isModified
10
10
 
11
11
 
12
12
  def parse_arguments_l0tx():
@@ -134,9 +134,12 @@ def get_l0tx():
134
134
  #----------------------------------
135
135
 
136
136
  if out_dir is not None:
137
- # Sort L0tx files and add tails
138
- print(out_dir+'/'+args.name+'*.txt')
139
- for f in glob(out_dir+'/'+args.name+'*.txt'):
137
+
138
+ # Find modified files (within the last hour)
139
+ mfiles = [mfile for mfile in glob(out_dir+'/'+args.name+'*.txt') if isModified(mfile, 1)]
140
+
141
+ # Sort L0tx files and add tails
142
+ for f in mfiles:
140
143
 
141
144
  # Sort lines in L0tx file and remove duplicates
142
145
  in_dirn, in_fn = os.path.split(f)
@@ -66,6 +66,7 @@ type,expected_values,format,description,flags,notes
66
66
  90,47,tfffffffffffffffffffffffffffffffffgneffffffffff,THE GC-NET VERSION!,don’t use,GC-NET stations
67
67
  90,48, tfffffffffffffffffffffffffffffffffgnefffffffffff,THE GC-NET VERSION!,don’t use,GC-NET stations
68
68
  90,48,tfffffffffffffffffffffffffffffffffgnefffffffffff,THE GC-NET VERSION!,,GC-NET stations
69
+ 95,46,tfffffffffffffffffffffffffffffffgnefffffffffff,THE GC-NET VERSION!,,GC-NET stations
69
70
  220,29,tfffffffffffffffffffffffnefff,ZAMG Freya 2015 summer message,,ZAMG Freya aws
70
71
  221,0,,,,ZAMG Freya aws
71
72
  222,29,tfffffffffffffffffffffffnefff,ZAMG Freya 2015 winter message,,ZAMG Freya aws
pypromice/tx/tx.py CHANGED
@@ -886,13 +886,13 @@ def sortLines(in_file, out_file, replace_unsorted=True): #
886
886
  lines = in_f.readlines()
887
887
 
888
888
  # Remove duplicate lines and sort
889
- unique_lines = findDuplicates(lines)
889
+ unique_lines = findDuplicates(lines.copy())
890
890
  unique_lines.sort()
891
-
892
- # Write sorted file
893
- with open(out_file, 'w') as out_f:
894
- # out_f.write(headers)
895
- out_f.writelines(unique_lines)
891
+ if lines != unique_lines:
892
+ # Write sorted file
893
+ with open(out_file, 'w') as out_f:
894
+ # out_f.write(headers)
895
+ out_f.writelines(unique_lines)
896
896
 
897
897
  # Replace input file with new sorted file
898
898
  if replace_unsorted:
@@ -930,6 +930,27 @@ def addTail(in_file, out_dir, aws_name, header_names='', lines_limit=100):
930
930
  out_f.writelines(tail)
931
931
  print(f'Tails file written to {out_pn}')
932
932
 
933
+ def isModified(filename, time_threshold=1):
934
+ '''Return flag denoting if file is modified within a certain timeframe
935
+
936
+ Parameters
937
+ ----------
938
+ filename : str
939
+ File path
940
+ time_threshold : int
941
+ Time threshold (provided in hours)
942
+
943
+ Returns
944
+ -------
945
+ bool
946
+ Flag denoting if modified (True) or not (False)
947
+ '''
948
+ delta = time.time() - os.path.getmtime(filename)
949
+ delta = delta / (60*60)
950
+ if delta < time_threshold:
951
+ return True
952
+ return False
953
+
933
954
  def _loadTestMsg():
934
955
  '''Load test .msg email file'''
935
956
  with pkg_resources.resource_stream('pypromice', 'test/test_email') as stream:
File without changes
@@ -0,0 +1,61 @@
1
+ import subprocess
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ def get_commit_hash_and_check_dirty(file_path) -> str:
11
+ repo_path = Path(file_path).parent
12
+
13
+ try:
14
+ # Ensure the file path is relative to the repository
15
+ relative_file_path = os.path.relpath(file_path, repo_path)
16
+
17
+ # Get the latest commit hash for the file
18
+ commit_hash = (
19
+ subprocess.check_output(
20
+ [
21
+ "git",
22
+ "-C",
23
+ repo_path,
24
+ "log",
25
+ "-n",
26
+ "1",
27
+ "--pretty=format:%H",
28
+ #"--",
29
+ #relative_file_path,
30
+ ],
31
+ stderr=subprocess.STDOUT,
32
+ )
33
+ .strip()
34
+ .decode("utf-8")
35
+ )
36
+
37
+ # Check if the file is dirty (has uncommitted changes)
38
+ diff_output = (
39
+ subprocess.check_output(
40
+ ["git", "-C", repo_path, "diff"],
41
+ stderr=subprocess.STDOUT,
42
+ )
43
+ .strip()
44
+ .decode("utf-8")
45
+ )
46
+
47
+ # If diff_output is not empty, the file has uncommitted changes
48
+ is_dirty = len(diff_output) > 0
49
+
50
+ if is_dirty:
51
+ logger.warning(f"Warning: The file {file_path} is dirty compared to the last commit. {commit_hash}")
52
+ return 'unknown'
53
+ if commit_hash == "":
54
+ logger.warning(f"Warning: The file {file_path} is not under version control.")
55
+ return 'unknown'
56
+
57
+ print(f"Commit hash: {commit_hash}")
58
+ return commit_hash
59
+ except subprocess.CalledProcessError as e:
60
+ logger.warning(f"Error: {e.output.decode('utf-8')}")
61
+ return 'unknown'