pypromice 1.3.6__py3-none-any.whl → 1.4.1__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 (53) hide show
  1. pypromice/postprocess/bufr_to_csv.py +15 -3
  2. pypromice/postprocess/bufr_utilities.py +91 -18
  3. pypromice/postprocess/create_bufr_files.py +178 -0
  4. pypromice/postprocess/get_bufr.py +248 -397
  5. pypromice/postprocess/make_metadata_csv.py +214 -0
  6. pypromice/postprocess/real_time_utilities.py +41 -11
  7. pypromice/process/L0toL1.py +12 -5
  8. pypromice/process/L1toL2.py +69 -14
  9. pypromice/process/L2toL3.py +1034 -186
  10. pypromice/process/aws.py +139 -808
  11. pypromice/process/get_l2.py +90 -0
  12. pypromice/process/get_l2tol3.py +111 -0
  13. pypromice/process/join_l2.py +112 -0
  14. pypromice/process/join_l3.py +551 -120
  15. pypromice/process/load.py +161 -0
  16. pypromice/process/resample.py +147 -0
  17. pypromice/process/utilities.py +68 -0
  18. pypromice/process/write.py +503 -0
  19. pypromice/qc/github_data_issues.py +10 -16
  20. pypromice/qc/persistence.py +52 -30
  21. pypromice/resources/__init__.py +28 -0
  22. pypromice/{process/metadata.csv → resources/file_attributes.csv} +0 -2
  23. pypromice/resources/variable_aliases_GC-Net.csv +78 -0
  24. pypromice/resources/variables.csv +106 -0
  25. pypromice/station_configuration.py +118 -0
  26. pypromice/tx/get_l0tx.py +7 -4
  27. pypromice/tx/payload_formats.csv +1 -0
  28. pypromice/tx/tx.py +27 -6
  29. pypromice/utilities/__init__.py +0 -0
  30. pypromice/utilities/git.py +62 -0
  31. {pypromice-1.3.6.dist-info → pypromice-1.4.1.dist-info}/METADATA +4 -4
  32. pypromice-1.4.1.dist-info/RECORD +53 -0
  33. {pypromice-1.3.6.dist-info → pypromice-1.4.1.dist-info}/WHEEL +1 -1
  34. pypromice-1.4.1.dist-info/entry_points.txt +13 -0
  35. pypromice/postprocess/station_configurations.toml +0 -762
  36. pypromice/process/get_l3.py +0 -46
  37. pypromice/process/variables.csv +0 -92
  38. pypromice/qc/persistence_test.py +0 -150
  39. pypromice/test/test_config1.toml +0 -69
  40. pypromice/test/test_config2.toml +0 -54
  41. pypromice/test/test_email +0 -75
  42. pypromice/test/test_payload_formats.csv +0 -4
  43. pypromice/test/test_payload_types.csv +0 -7
  44. pypromice/test/test_percentile.py +0 -229
  45. pypromice/test/test_raw1.txt +0 -4468
  46. pypromice/test/test_raw_DataTable2.txt +0 -11167
  47. pypromice/test/test_raw_SlimTableMem1.txt +0 -1155
  48. pypromice/test/test_raw_transmitted1.txt +0 -15411
  49. pypromice/test/test_raw_transmitted2.txt +0 -28
  50. pypromice-1.3.6.dist-info/RECORD +0 -53
  51. pypromice-1.3.6.dist-info/entry_points.txt +0 -8
  52. {pypromice-1.3.6.dist-info → pypromice-1.4.1.dist-info}/LICENSE.txt +0 -0
  53. {pypromice-1.3.6.dist-info → pypromice-1.4.1.dist-info}/top_level.txt +0 -0
@@ -9,20 +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
17
  # period is given in hours, 2 persistent 10 min values will be flagged if period < 0.333
18
18
  DEFAULT_VARIABLE_THRESHOLDS = {
19
- "t": {"max_diff": 0.0001, "period": 2},
20
- "p": {"max_diff": 0.0001, "period": 2},
21
- 'gps_lat_lon':{"max_diff": 0.000001, "period": 6}, # gets special handling to remove simultaneously constant gps_lat and gps_lon
22
- 'gps_alt':{"max_diff": 0.0001, "period": 6},
23
- 't_rad':{"max_diff": 0.0001, "period": 2},
24
- "rh": {"max_diff": 0.0001, "period": 2}, # gets special handling to allow constant 100%
25
- "wspd": {"max_diff": 0.0001, "period": 6},
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},
26
46
  }
27
47
 
28
48
 
@@ -61,11 +81,12 @@ def persistence_qc(
61
81
 
62
82
  if variable_thresholds is None:
63
83
  variable_thresholds = DEFAULT_VARIABLE_THRESHOLDS
64
-
65
- logger.info(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}")
66
87
 
67
88
  for k in variable_thresholds.keys():
68
- if k in ['t','p','rh','wspd','wdir', 'z_boom']:
89
+ if k in ["t", "p", "rh", "wspd", "wdir", "z_boom"]:
69
90
  var_all = [
70
91
  k + "_u",
71
92
  k + "_l",
@@ -79,29 +100,28 @@ def persistence_qc(
79
100
  for v in var_all:
80
101
  if v in df:
81
102
  mask = find_persistent_regions(df[v], period, max_diff)
82
- if 'rh' in v:
83
- mask = mask & (df[v]<99)
103
+ if "rh" in v:
104
+ mask = mask & (df[v] < 99)
84
105
  n_masked = mask.sum()
85
106
  n_samples = len(mask)
86
- logger.info(
107
+ logger.debug(
87
108
  f"Applying persistent QC in {v}. Filtering {n_masked}/{n_samples} samples"
88
109
  )
89
110
  # setting outliers to NaN
90
111
  df.loc[mask, v] = np.nan
91
- elif v == 'gps_lat_lon':
92
- mask = (
93
- find_persistent_regions(df['gps_lon'], period, max_diff)
94
- & find_persistent_regions(df['gps_lat'], period, max_diff)
95
- )
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)
96
116
 
97
117
  n_masked = mask.sum()
98
118
  n_samples = len(mask)
99
- logger.info(
119
+ logger.debug(
100
120
  f"Applying persistent QC in {v}. Filtering {n_masked}/{n_samples} samples"
101
121
  )
102
122
  # setting outliers to NaN
103
- df.loc[mask, 'gps_lon'] = np.nan
104
- df.loc[mask, 'gps_lat'] = np.nan
123
+ df.loc[mask, "gps_lon"] = np.nan
124
+ df.loc[mask, "gps_lat"] = np.nan
105
125
 
106
126
  # Back to xarray, and re-assign the original attrs
107
127
  ds_out = df.to_xarray()
@@ -133,19 +153,21 @@ def count_consecutive_persistent_values(
133
153
  ) -> pd.Series:
134
154
  diff = data.ffill().diff().abs() # forward filling all NaNs!
135
155
  mask: pd.Series = diff < max_diff
136
- return duration_consecutive_true(mask)
156
+ return get_duration_consecutive_true(mask)
137
157
 
138
158
 
139
- def duration_consecutive_true(
159
+ def get_duration_consecutive_true(
140
160
  series: pd.Series,
141
161
  ) -> pd.Series:
142
162
  """
143
- From a boolean series, calculates the duration, in hours, of the periods with 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.
144
166
 
145
167
  Examples
146
168
  --------
147
- >>> duration_consecutive_true(pd.Series([False, True, False, False, True, True, True, False, True]))
148
- 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])
149
171
 
150
172
  Parameters
151
173
  ----------
@@ -158,9 +180,9 @@ def duration_consecutive_true(
158
180
  Integer pandas Series or DataFrame with values representing the number of connective true values.
159
181
 
160
182
  """
161
- # assert series.dtype == bool
162
- cumsum = ((series.index - series.index[0]).total_seconds()/3600).to_series(index=series.index)
163
183
  is_first = series.astype("int").diff() == 1
164
- offset = (is_first * cumsum).replace(0, np.nan).fillna(method="ffill").fillna(0)
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)
165
187
 
166
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,62 @@
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 | Path) -> str:
11
+ if isinstance(file_path, str):
12
+ file_path = Path(file_path)
13
+ if file_path.is_dir():
14
+ repo_path = file_path
15
+ else:
16
+ repo_path = file_path.parent
17
+
18
+ try:
19
+ # Ensure the file path is relative to the repository
20
+
21
+ # Get the latest commit hash for the file
22
+ commit_hash = (
23
+ subprocess.check_output(
24
+ [
25
+ "git",
26
+ "-C",
27
+ repo_path,
28
+ "log",
29
+ "-n",
30
+ "1",
31
+ "--pretty=format:%H",
32
+ ],
33
+ stderr=subprocess.STDOUT,
34
+ )
35
+ .strip()
36
+ .decode("utf-8")
37
+ )
38
+
39
+ # Check if the file is dirty (has uncommitted changes)
40
+ diff_output = (
41
+ subprocess.check_output(
42
+ ["git", "-C", repo_path, "diff"],
43
+ stderr=subprocess.STDOUT,
44
+ )
45
+ .strip()
46
+ .decode("utf-8")
47
+ )
48
+
49
+ # If diff_output is not empty, the file has uncommitted changes
50
+ is_dirty = len(diff_output) > 0
51
+
52
+ if is_dirty:
53
+ logger.warning(f"Warning: The file {file_path} is dirty compared to the last commit. {commit_hash}")
54
+ return f'{commit_hash} (dirty)'
55
+ if commit_hash == "":
56
+ logger.warning(f"Warning: The file {file_path} is not under version control.")
57
+ return 'unknown'
58
+
59
+ return commit_hash
60
+ except subprocess.CalledProcessError as e:
61
+ logger.warning(f"Error: {e.output.decode('utf-8')}")
62
+ return 'unknown'