loone-data-prep 1.3.0__py3-none-any.whl → 1.3.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.
- loone_data_prep/dbhydro_insights.py +195 -0
- loone_data_prep/flow_data/S65E_total.py +57 -57
- loone_data_prep/flow_data/forecast_bias_correction.py +1 -1
- loone_data_prep/flow_data/get_forecast_flows.py +19 -105
- loone_data_prep/flow_data/get_inflows.py +18 -8
- loone_data_prep/flow_data/get_outflows.py +16 -7
- loone_data_prep/flow_data/hydro.py +62 -91
- loone_data_prep/utils.py +243 -30
- loone_data_prep/water_level_data/get_all.py +52 -44
- loone_data_prep/water_level_data/hydro.py +49 -68
- loone_data_prep/water_quality_data/get_inflows.py +69 -27
- loone_data_prep/water_quality_data/get_lake_wq.py +130 -33
- loone_data_prep/water_quality_data/wq.py +114 -88
- loone_data_prep/weather_data/get_all.py +5 -3
- loone_data_prep/weather_data/weather.py +117 -180
- {loone_data_prep-1.3.0.dist-info → loone_data_prep-1.3.1.dist-info}/METADATA +2 -8
- {loone_data_prep-1.3.0.dist-info → loone_data_prep-1.3.1.dist-info}/RECORD +20 -19
- {loone_data_prep-1.3.0.dist-info → loone_data_prep-1.3.1.dist-info}/WHEEL +1 -1
- {loone_data_prep-1.3.0.dist-info → loone_data_prep-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {loone_data_prep-1.3.0.dist-info → loone_data_prep-1.3.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities for interacting with the DBHYDRO Insights database services.
|
|
3
|
+
|
|
4
|
+
This module provides functions for fetching data from endpoints used
|
|
5
|
+
by the South Florida Water Management District's DBHYDRO Insights app.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
import requests
|
|
10
|
+
from typing import Literal, Tuple
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_dbhydro_station_metadata(station_id: str) -> dict | None:
|
|
14
|
+
"""
|
|
15
|
+
Fetches metadata for a specific station from the DBHYDRO_SiteStation service.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
station_id (str): The ID of the station for which to fetch metadata. Examples: 'FISHP', 'L OKEE', etc.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
dict: A dictionary containing the metadata of the station, or None if the request fails.
|
|
22
|
+
"""
|
|
23
|
+
# Build the request URL with the provided station ID
|
|
24
|
+
request_url = 'https://geoweb.sfwmd.gov/agsext2/rest/services/MonitoringLocations/DBHYDRO_SiteStation/MapServer/4/query'
|
|
25
|
+
|
|
26
|
+
params = {
|
|
27
|
+
'f': 'json',
|
|
28
|
+
'outFields': '*',
|
|
29
|
+
'spatialRel': 'esriSpatialRelIntersects',
|
|
30
|
+
'where': f"(STATION = '{station_id}')"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Send the GET request to the specified URL with the parameters
|
|
34
|
+
try:
|
|
35
|
+
response = requests.get(request_url, params=params)
|
|
36
|
+
except requests.exceptions.RequestException:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
# Successful Request
|
|
40
|
+
if response.status_code == 200:
|
|
41
|
+
# Parse the JSON response
|
|
42
|
+
json = response.json()
|
|
43
|
+
|
|
44
|
+
# No data given back for given station ID
|
|
45
|
+
if not json['features']:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
# Data given back, return the JSON response
|
|
49
|
+
return json
|
|
50
|
+
|
|
51
|
+
# Failure
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_dbhydro_continuous_timeseries_metadata(
|
|
56
|
+
station_ids: list[str],
|
|
57
|
+
categories: list[str] | None = ['ALL'],
|
|
58
|
+
parameters: list[str] | None = ['ALL'],
|
|
59
|
+
statistics: list[str] | None = ['ALL'],
|
|
60
|
+
recorders: list[str] | None = ['ALL'],
|
|
61
|
+
frequencies: list[str] | None = ['ALL']
|
|
62
|
+
) -> dict | None:
|
|
63
|
+
"""Fetches metadata for continuous time series data from the DBHYDRO Insights service.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
station_ids (list[str]): List of station IDs to query.
|
|
67
|
+
categories (list[str] | None): List of categories to filter by. Defaults to ['ALL'].
|
|
68
|
+
parameters (list[str] | None): List of parameters to filter by. Defaults to ['ALL'].
|
|
69
|
+
statistics (list[str] | None): List of statistics to filter by. Defaults to ['ALL'].
|
|
70
|
+
recorders (list[str] | None): List of recorders to filter by. Defaults to ['ALL'].
|
|
71
|
+
frequencies (list[str] | None): List of frequencies to filter by. Defaults to ['ALL'].
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
dict | None: The JSON response from the API if successful, otherwise None.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
Exception: If the request fails.
|
|
78
|
+
"""
|
|
79
|
+
# Build the request URL
|
|
80
|
+
request_url = 'https://insightsdata.api.sfwmd.gov/v1/insights-data/cont/ts'
|
|
81
|
+
|
|
82
|
+
# Build the locations list
|
|
83
|
+
locations = []
|
|
84
|
+
|
|
85
|
+
for station_id in station_ids:
|
|
86
|
+
# Build the location dictionary for this station_id
|
|
87
|
+
location = {
|
|
88
|
+
'name': station_id,
|
|
89
|
+
'type': 'STATION',
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Add location to the locations list
|
|
93
|
+
locations.append(location)
|
|
94
|
+
|
|
95
|
+
# Build the data payload
|
|
96
|
+
data = {
|
|
97
|
+
'query': {
|
|
98
|
+
'locations': locations,
|
|
99
|
+
'parameters': parameters,
|
|
100
|
+
'category': categories,
|
|
101
|
+
'statistic': statistics,
|
|
102
|
+
'recorder': recorders,
|
|
103
|
+
'frequency': frequencies,
|
|
104
|
+
'dbkeys': ['ALL'],
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Send the POST request to the specified URL with the parameters
|
|
109
|
+
response = requests.post(request_url, json=data)
|
|
110
|
+
|
|
111
|
+
# Successful Request
|
|
112
|
+
if response.status_code == 200:
|
|
113
|
+
# Parse the JSON response
|
|
114
|
+
json = response.json()
|
|
115
|
+
|
|
116
|
+
# No data given back for given station ID
|
|
117
|
+
if not json['results']:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
# Data given back, return the JSON response
|
|
121
|
+
return json
|
|
122
|
+
|
|
123
|
+
# Failure
|
|
124
|
+
raise Exception(f"Request failed with status code {response.status_code}: {response.text}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def get_dbhydro_water_quality_metadata(stations: list[Tuple[str,Literal['SITE', 'STATION']]], test_numbers: list[int]) -> dict | None:
|
|
128
|
+
"""Fetches metadata for water quality data from the DBHYDRO Insights service.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
stations (list[Tuple[str, Literal['SITE', 'STATION']]]): List of tuples containing station names and station types ('SITE' or 'STATION') to get water quality metadata for.
|
|
132
|
+
test_numbers (list[int]): List of test numbers to get data for. Test numbers map to parameters. Example: 25 maps to 'PHOSPHATE, TOTAL AS P'.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
dict | None: The JSON response from the API if successful, otherwise None.
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
Exception: If the request fails.
|
|
139
|
+
"""
|
|
140
|
+
# Build the request URL
|
|
141
|
+
request_url = 'https://insightsdata.api.sfwmd.gov/v1/insights-data/chem/ts'
|
|
142
|
+
|
|
143
|
+
# Build the locations list
|
|
144
|
+
locations = []
|
|
145
|
+
|
|
146
|
+
for station in stations:
|
|
147
|
+
# Build the location dictionary for this station/site
|
|
148
|
+
location = {
|
|
149
|
+
'name': station[0],
|
|
150
|
+
'type': station[1],
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Add location to the locations list
|
|
154
|
+
locations.append(location)
|
|
155
|
+
|
|
156
|
+
# Build the query parameters
|
|
157
|
+
query_parameters = {
|
|
158
|
+
'offset': 0,
|
|
159
|
+
'limit': 1000,
|
|
160
|
+
'sort': 'project,location,parameterDesc,matrix,method',
|
|
161
|
+
'startDate': '19000101',
|
|
162
|
+
'endDate': datetime.now().strftime("%Y%m%d"),
|
|
163
|
+
'period': '',
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# Build the data payload
|
|
167
|
+
payload = {
|
|
168
|
+
'query': {
|
|
169
|
+
'locations': locations,
|
|
170
|
+
'matrices': ['ALL'],
|
|
171
|
+
'methods': ['ALL'],
|
|
172
|
+
'paramGroups': ['ALL'],
|
|
173
|
+
'parameters': [str(num) for num in test_numbers],
|
|
174
|
+
'projects': ['ALL'],
|
|
175
|
+
'sampleTypes': ['ALL'],
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# Send the POST request to the specified URL with the parameters
|
|
180
|
+
response = requests.post(request_url, params=query_parameters, json=payload)
|
|
181
|
+
|
|
182
|
+
# Successful Request
|
|
183
|
+
if response.status_code == 200:
|
|
184
|
+
# Parse the JSON response
|
|
185
|
+
json = response.json()
|
|
186
|
+
|
|
187
|
+
# No data given back for given station ID
|
|
188
|
+
if not json['results']:
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
# Data given back, return the JSON response
|
|
192
|
+
return json
|
|
193
|
+
|
|
194
|
+
# Failure
|
|
195
|
+
raise Exception(f"Request failed with status code {response.status_code}: {response.text}")
|
|
@@ -1,74 +1,73 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from retry import retry
|
|
3
|
-
from rpy2.robjects import r
|
|
4
|
-
from rpy2.rinterface_lib.embedded import RRuntimeError
|
|
5
3
|
import pandas as pd
|
|
4
|
+
from loone_data_prep.utils import df_replace_missing_with_nan, get_dbhydro_api
|
|
6
5
|
|
|
7
6
|
|
|
8
|
-
@retry(
|
|
7
|
+
@retry(Exception, tries=5, delay=15, max_delay=60, backoff=2)
|
|
9
8
|
def get(
|
|
10
9
|
workspace,
|
|
11
10
|
date_min: str = "1972-01-01",
|
|
12
11
|
date_max: str = "2023-06-30"
|
|
13
12
|
) -> None:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
write.csv(result, file = '{workspace}/S65E_total.csv')
|
|
60
|
-
"""
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
_reformat_s65e_total_file(workspace)
|
|
13
|
+
"""Retrieve total flow data for S65E structure (S65E_S + S65EX1_S) and save to CSV.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
workspace (str): Path to workspace where data will be downloaded.
|
|
17
|
+
date_min (str): Minimum date for data retrieval in 'YYYY-MM-DD' format.
|
|
18
|
+
date_max (str): Maximum date for data retrieval in 'YYYY-MM-DD' format.
|
|
19
|
+
"""
|
|
20
|
+
# Get a DbHydroApi instance
|
|
21
|
+
api = get_dbhydro_api()
|
|
22
|
+
|
|
23
|
+
# S65E_S
|
|
24
|
+
s65e_s = api.get_daily_data(['91656'], 'id', date_min, date_max, 'NGVD29', False)
|
|
25
|
+
|
|
26
|
+
if not s65e_s.has_data():
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
df_s65e_s = s65e_s.to_dataframe(True)
|
|
30
|
+
df_s65e_s = df_replace_missing_with_nan(df_s65e_s) # Replace flagged 0 values and -99999.0 with NaN
|
|
31
|
+
df_s65e_s.reset_index(inplace=True) # Reset index so datetime is a column
|
|
32
|
+
df_s65e_s['value'] = df_s65e_s['value'] * (0.0283168466 * 86400) # Convert flow from cfs to cmd
|
|
33
|
+
df_s65e_s = df_s65e_s[['datetime', 'value']].copy() # Grab only the columns we need
|
|
34
|
+
df_s65e_s.rename(columns={'datetime': 'date', 'value': f'S65E_S_FLOW_cfs'}, inplace=True) # Rename columns to expected names
|
|
35
|
+
|
|
36
|
+
# S65EX1_S
|
|
37
|
+
s65ex1_s = api.get_daily_data(['AL760'], 'id', date_min, date_max, 'NGVD29', False)
|
|
38
|
+
|
|
39
|
+
if not s65ex1_s.has_data():
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
df_s65ex1_s = s65ex1_s.to_dataframe(True)
|
|
43
|
+
df_s65ex1_s = df_replace_missing_with_nan(df_s65ex1_s) # Replace flagged 0 values and -99999.0 with NaN
|
|
44
|
+
df_s65ex1_s.reset_index(inplace=True) # Reset index so datetime is a column
|
|
45
|
+
df_s65ex1_s['value'] = df_s65ex1_s['value'] * (0.0283168466 * 86400) # Convert flow from cfs to cmd
|
|
46
|
+
df_s65ex1_s = df_s65ex1_s[['datetime', 'value']].copy() # Grab only the columns we need
|
|
47
|
+
df_s65ex1_s.rename(columns={'datetime': 'date', 'value': f'S65EX1_S_FLOW_cfs'}, inplace=True) # Rename columns to expected names
|
|
48
|
+
|
|
49
|
+
# Combine the data from both stations into a single dataframe
|
|
50
|
+
df = pd.merge(df_s65e_s, df_s65ex1_s, on='date', how='outer', suffixes=('_S65E_S', '_S65EX1_S'))
|
|
51
|
+
|
|
52
|
+
# Reformat the data to the expected layout
|
|
53
|
+
df = _reformat_s65e_total_df(df)
|
|
54
|
+
|
|
55
|
+
# Write the data to a file
|
|
56
|
+
df.to_csv(f"{workspace}/S65E_total.csv")
|
|
57
|
+
|
|
64
58
|
|
|
65
59
|
def _reformat_s65e_total_file(workspace: str):
|
|
66
60
|
# Read in the data
|
|
67
61
|
df = pd.read_csv(f"{workspace}/S65E_total.csv")
|
|
68
62
|
|
|
69
|
-
#
|
|
70
|
-
df
|
|
63
|
+
# Reformat the data
|
|
64
|
+
df = _reformat_s65e_total_df(df)
|
|
71
65
|
|
|
66
|
+
# Write the updated data back to the file
|
|
67
|
+
df.to_csv(f"{workspace}/S65E_total.csv")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _reformat_s65e_total_df(df: pd.DataFrame) -> pd.DataFrame:
|
|
72
71
|
# Convert date column to datetime
|
|
73
72
|
df['date'] = pd.to_datetime(df['date'], format='%d-%b-%Y')
|
|
74
73
|
|
|
@@ -81,8 +80,9 @@ def _reformat_s65e_total_file(workspace: str):
|
|
|
81
80
|
# Drop rows that are missing all their values
|
|
82
81
|
df.dropna(how='all', inplace=True)
|
|
83
82
|
|
|
84
|
-
#
|
|
85
|
-
df
|
|
83
|
+
# Return the reformatted dataframe
|
|
84
|
+
return df
|
|
85
|
+
|
|
86
86
|
|
|
87
87
|
if __name__ == "__main__":
|
|
88
88
|
workspace = sys.argv[1].rstrip("/")
|
|
@@ -38,7 +38,7 @@ def get_bias_corrected_data(
|
|
|
38
38
|
# Prepare the observed data by filling NaN values with the 10yr average
|
|
39
39
|
prepared_od = prep_observed_data(observed_data)
|
|
40
40
|
historical_data = geoglows.data.retro_daily(reach_id)
|
|
41
|
-
# Get the historical simulation data for the given reach ID
|
|
41
|
+
# Get the historical simulation data for the given reach ID
|
|
42
42
|
# I am reading the observed data that we queried earlier instead of caching it
|
|
43
43
|
# historical_data = None
|
|
44
44
|
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
import pandas as pd
|
|
4
|
-
import rpy2.robjects as ro
|
|
5
|
-
from rpy2.robjects import pandas2ri
|
|
6
4
|
import geoglows
|
|
7
5
|
import datetime
|
|
8
|
-
from loone_data_prep.utils import get_dbkeys
|
|
9
6
|
from loone_data_prep.flow_data.forecast_bias_correction import (
|
|
10
7
|
get_bias_corrected_data,
|
|
11
8
|
)
|
|
@@ -63,51 +60,6 @@ FORECAST_DATE = (datetime.datetime.now()).strftime("%Y%m%d")
|
|
|
63
60
|
GEOGLOWS_ENDPOINT = "https://geoglows.ecmwf.int/api/"
|
|
64
61
|
|
|
65
62
|
|
|
66
|
-
def get_stations_latitude_longitude(station_ids: list[str]):
|
|
67
|
-
"""Gets the latitudes and longitudes of the given stations.
|
|
68
|
-
|
|
69
|
-
Args:
|
|
70
|
-
station_ids (list[str]): The ids of the stations to get the
|
|
71
|
-
latitudes/longitudes of
|
|
72
|
-
|
|
73
|
-
Returns:
|
|
74
|
-
(dict[str, tuple[numpy.float64, numpy.float64]]): A dictionary of
|
|
75
|
-
format dict<station_id:(latitude,longitude)>
|
|
76
|
-
|
|
77
|
-
If a station's latitude/longitude fails to download then its station_id
|
|
78
|
-
won't be a key in the returned dictionary.
|
|
79
|
-
"""
|
|
80
|
-
# The dict that holds the data that gets returned
|
|
81
|
-
station_data = {}
|
|
82
|
-
|
|
83
|
-
# Get the station/dbkey data
|
|
84
|
-
r_dataframe = get_dbkeys(
|
|
85
|
-
station_ids=station_ids,
|
|
86
|
-
category="SW",
|
|
87
|
-
param="",
|
|
88
|
-
stat="",
|
|
89
|
-
recorder="",
|
|
90
|
-
detail_level="full",
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
# Convert the r dataframe to a pandas dataframe
|
|
94
|
-
with (ro.default_converter + pandas2ri.converter).context():
|
|
95
|
-
pd_dataframe = ro.conversion.get_conversion().rpy2py(r_dataframe)
|
|
96
|
-
|
|
97
|
-
# Filter out extra rows for each station from the dataframe
|
|
98
|
-
pd_dataframe.drop_duplicates(subset="Station", keep="first", inplace=True)
|
|
99
|
-
|
|
100
|
-
# Get latitude/longitude of each station
|
|
101
|
-
for index in pd_dataframe.index:
|
|
102
|
-
station = pd_dataframe["Station"][index]
|
|
103
|
-
latitude = pd_dataframe["Latitude"][index]
|
|
104
|
-
longitude = pd_dataframe["Longitude"][index]
|
|
105
|
-
|
|
106
|
-
station_data[station] = latitude, longitude
|
|
107
|
-
|
|
108
|
-
return station_data
|
|
109
|
-
|
|
110
|
-
|
|
111
63
|
def get_reach_id(latitude: float, longitude: float):
|
|
112
64
|
"""Gets the reach id for the given latitude/longitude.
|
|
113
65
|
|
|
@@ -273,70 +225,32 @@ def _format_stats_DataFrame(dataframe: pd.core.frame.DataFrame):
|
|
|
273
225
|
dataframe.index = dataframe.index.normalize()
|
|
274
226
|
|
|
275
227
|
# Convert m^3/s data to m^3/h
|
|
276
|
-
dataframe = dataframe
|
|
228
|
+
dataframe = dataframe * SECONDS_IN_HOUR
|
|
277
229
|
|
|
278
230
|
# Make negative values 0
|
|
279
231
|
dataframe.clip(0, inplace=True)
|
|
280
232
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
# 25th Percentile Column (Average)
|
|
297
|
-
column_25percentile = dataframe[["flow_25p"]].copy()
|
|
298
|
-
column_25percentile = column_25percentile.groupby(
|
|
299
|
-
[column_25percentile.index]
|
|
300
|
-
).mean()
|
|
301
|
-
|
|
302
|
-
# Min Column (Min)
|
|
303
|
-
column_min = dataframe[["flow_min"]].copy()
|
|
304
|
-
column_min = column_min.groupby([column_min.index]).min()
|
|
305
|
-
|
|
306
|
-
# Convert values in each column from m^3/h to m^3/d
|
|
307
|
-
column_max = column_max.transform(lambda x: x * HOURS_IN_DAY)
|
|
308
|
-
column_75percentile = column_75percentile.transform(
|
|
309
|
-
lambda x: x * HOURS_IN_DAY
|
|
310
|
-
)
|
|
311
|
-
column_average = column_average.transform(lambda x: x * HOURS_IN_DAY)
|
|
312
|
-
column_25percentile = column_25percentile.transform(
|
|
313
|
-
lambda x: x * HOURS_IN_DAY
|
|
233
|
+
grouped = dataframe.groupby(dataframe.index).mean()
|
|
234
|
+
# Convert from m^3/h → m^3/d
|
|
235
|
+
grouped = grouped * HOURS_IN_DAY
|
|
236
|
+
|
|
237
|
+
# Rename columns
|
|
238
|
+
grouped = grouped.rename(
|
|
239
|
+
columns={
|
|
240
|
+
"flow_max": "flow_max_m^3/d",
|
|
241
|
+
"flow_75p": "flow_75%_m^3/d",
|
|
242
|
+
"flow_avg": "flow_avg_m^3/d",
|
|
243
|
+
"flow_med": "flow_med_m^3/d",
|
|
244
|
+
"flow_25p": "flow_25%_m^3/d",
|
|
245
|
+
"flow_min": "flow_min_m^3/d",
|
|
246
|
+
}
|
|
314
247
|
)
|
|
315
|
-
column_min = column_min.transform(lambda x: x * HOURS_IN_DAY)
|
|
316
|
-
|
|
317
|
-
# Append modified columns into one pandas DataFrame
|
|
318
|
-
dataframe_result = pd.DataFrame()
|
|
319
|
-
dataframe_result.index = dataframe.groupby([dataframe.index]).mean().index
|
|
320
|
-
dataframe_result["flow_max_m^3/d"] = column_max["flow_max"].tolist()
|
|
321
|
-
dataframe_result["flow_75%_m^3/d"] = column_75percentile[
|
|
322
|
-
"flow_75p"
|
|
323
|
-
].tolist()
|
|
324
|
-
dataframe_result["flow_avg_m^3/d"] = column_average[
|
|
325
|
-
"flow_avg"
|
|
326
|
-
].tolist()
|
|
327
|
-
dataframe_result["flow_25%_m^3/d"] = column_25percentile[
|
|
328
|
-
"flow_25p"
|
|
329
|
-
].tolist()
|
|
330
|
-
dataframe_result["flow_min_m^3/d"] = column_min["flow_min"].tolist()
|
|
331
248
|
|
|
332
|
-
# Format
|
|
333
|
-
|
|
249
|
+
# Format index as date string and rename
|
|
250
|
+
grouped.index = grouped.index.strftime("%Y-%m-%d")
|
|
251
|
+
grouped.index.name = "date"
|
|
334
252
|
|
|
335
|
-
|
|
336
|
-
dataframe_result.rename_axis("date", inplace=True)
|
|
337
|
-
|
|
338
|
-
# Return resulting DataFrame
|
|
339
|
-
return dataframe_result
|
|
253
|
+
return grouped
|
|
340
254
|
|
|
341
255
|
|
|
342
256
|
def main(
|
|
@@ -45,20 +45,22 @@ def main(workspace: str, dbkeys: dict = DBKEYS) -> dict:
|
|
|
45
45
|
Returns:
|
|
46
46
|
dict: Success or error message
|
|
47
47
|
"""
|
|
48
|
+
# Make a copy of the dbkeys dictionary because key value pairs will be removed as they are successfully downloaded
|
|
49
|
+
dbkeys = dbkeys.copy()
|
|
48
50
|
|
|
49
51
|
# Retrieve inflow data
|
|
50
52
|
for dbkey, station in dbkeys.copy().items():
|
|
51
|
-
file_name = f"{station}_FLOW_cmd.csv"
|
|
53
|
+
file_name = f"{station.replace(' ', '_')}_FLOW_cmd.csv"
|
|
52
54
|
date_latest = find_last_date_in_csv(workspace, file_name)
|
|
53
55
|
|
|
54
56
|
# File with data for this dbkey does NOT already exist (or possibly some other error occurred)
|
|
55
57
|
if date_latest is None:
|
|
56
58
|
# Download all the data
|
|
57
59
|
print(f'Downloading all inflow data for {station}')
|
|
58
|
-
hydro.get(workspace, dbkey)
|
|
60
|
+
hydro.get(workspace=workspace, dbkey=dbkey, station=station)
|
|
59
61
|
else:
|
|
60
62
|
# Check whether the latest data is already up to date.
|
|
61
|
-
if dbhydro_data_is_latest(date_latest):
|
|
63
|
+
if dbhydro_data_is_latest(date_latest, dbkey):
|
|
62
64
|
# Notify that the data is already up to date
|
|
63
65
|
print(f'Downloading of new inflow data skipped for Station {station} (dbkey: {dbkey}). Data is already up to date.')
|
|
64
66
|
|
|
@@ -67,8 +69,15 @@ def main(workspace: str, dbkeys: dict = DBKEYS) -> dict:
|
|
|
67
69
|
continue
|
|
68
70
|
|
|
69
71
|
# Download only the new data
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
date_next = (pd.to_datetime(date_latest) + pd.Timedelta(days=1)).strftime("%Y-%m-%d")
|
|
73
|
+
print(f'Downloading new inflow data for {station} starting from date {date_next}')
|
|
74
|
+
hydro.get(workspace=workspace, dbkey=dbkey, date_min=date_next, station=station)
|
|
75
|
+
|
|
76
|
+
# Check if the station name contains a space
|
|
77
|
+
if ' ' in station:
|
|
78
|
+
# Replace space with underscore in the station name
|
|
79
|
+
station_previous = station
|
|
80
|
+
station = station.replace(' ', '_')
|
|
72
81
|
|
|
73
82
|
# Make sure both our original data and newly downloaded data exist
|
|
74
83
|
df_original_path = os.path.join(workspace, f"{station}_FLOW_cmd.csv")
|
|
@@ -94,7 +103,7 @@ def main(workspace: str, dbkeys: dict = DBKEYS) -> dict:
|
|
|
94
103
|
S65E_total.get(workspace, date_max=datetime.now().strftime("%Y-%m-%d"))
|
|
95
104
|
else:
|
|
96
105
|
# Check whether the latest data is already up to date.
|
|
97
|
-
if dbhydro_data_is_latest(date_latest):
|
|
106
|
+
if dbhydro_data_is_latest(date_latest, '91656') and dbhydro_data_is_latest(date_latest, 'AL760'):
|
|
98
107
|
# Notify that the data is already up to date
|
|
99
108
|
print(f'Downloading of new inflow data skipped for S65E_total. Data is already up to date.')
|
|
100
109
|
else:
|
|
@@ -104,8 +113,9 @@ def main(workspace: str, dbkeys: dict = DBKEYS) -> dict:
|
|
|
104
113
|
|
|
105
114
|
try:
|
|
106
115
|
# Download only the new data
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
date_next = (pd.to_datetime(date_latest) + pd.Timedelta(days=1)).strftime("%Y-%m-%d")
|
|
117
|
+
print(f'Downloading new S65E_total data starting from date {date_next}')
|
|
118
|
+
S65E_total.get(workspace, date_min=date_next, date_max=datetime.now().strftime("%Y-%m-%d"))
|
|
109
119
|
|
|
110
120
|
# Merge the new data with the original data
|
|
111
121
|
df_original = pd.read_csv(os.path.join(workspace, original_file_name), index_col=0)
|
|
@@ -56,8 +56,8 @@ def _get_outflow_data_from_station_ids(workspace: str, station_ids: list) -> dic
|
|
|
56
56
|
dict: Success or error message
|
|
57
57
|
"""
|
|
58
58
|
# Get dbkeys from station ids
|
|
59
|
-
dbkeys =
|
|
60
|
-
dbkeys.extend(
|
|
59
|
+
dbkeys = get_dbkeys(station_ids, "SW", "FLOW", "MEAN", "PREF")
|
|
60
|
+
dbkeys.extend(get_dbkeys(station_ids, "SW", "FLOW", "MEAN", "DRV"))
|
|
61
61
|
|
|
62
62
|
for dbkey in dbkeys:
|
|
63
63
|
hydro.get(workspace, dbkey, "2000-01-01")
|
|
@@ -94,6 +94,8 @@ def main(workspace: str, dbkeys: dict = DBKEYS, station_ids: list = STATION_IDS)
|
|
|
94
94
|
Returns:
|
|
95
95
|
dict: Success or error message
|
|
96
96
|
"""
|
|
97
|
+
# Make a copy of the dbkeys dictionary because key value pairs will be removed as they are successfully downloaded
|
|
98
|
+
dbkeys = dbkeys.copy()
|
|
97
99
|
|
|
98
100
|
# No dbkeys given, attempt to get data from station ids
|
|
99
101
|
if dbkeys is None:
|
|
@@ -102,16 +104,16 @@ def main(workspace: str, dbkeys: dict = DBKEYS, station_ids: list = STATION_IDS)
|
|
|
102
104
|
# Get outflow data from dbkeys
|
|
103
105
|
for dbkey, station in dbkeys.copy().items():
|
|
104
106
|
# Get the date of the latest data in the csv file (if any)
|
|
105
|
-
date_latest = find_last_date_in_csv(workspace, f"{station}_FLOW_cmd.csv")
|
|
107
|
+
date_latest = find_last_date_in_csv(workspace, f"{station.replace(' ', '_')}_FLOW_cmd.csv")
|
|
106
108
|
|
|
107
109
|
# File with data for this dbkey does NOT already exist (or possibly some other error occurred)
|
|
108
110
|
if date_latest is None:
|
|
109
111
|
# Download all data
|
|
110
112
|
print(f'Downloading all outflow data for {station}')
|
|
111
|
-
hydro.get(workspace, dbkey, "2000-01-01")
|
|
113
|
+
hydro.get(workspace=workspace, dbkey=dbkey, date_min="2000-01-01", station=station)
|
|
112
114
|
else:
|
|
113
115
|
# Check whether the latest data is already up to date.
|
|
114
|
-
if dbhydro_data_is_latest(date_latest):
|
|
116
|
+
if dbhydro_data_is_latest(date_latest, dbkey):
|
|
115
117
|
# Notify that the data is already up to date
|
|
116
118
|
print(f'Downloading of new outflow data skipped for Station {station} (dbkey: {dbkey}). Data is already up to date.')
|
|
117
119
|
|
|
@@ -120,8 +122,15 @@ def main(workspace: str, dbkeys: dict = DBKEYS, station_ids: list = STATION_IDS)
|
|
|
120
122
|
continue
|
|
121
123
|
|
|
122
124
|
# Download only the new data
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
date_next = (pd.to_datetime(date_latest) + pd.Timedelta(days=1)).strftime("%Y-%m-%d")
|
|
126
|
+
print(f'Downloading new outflow data for {station} starting from date {date_next}')
|
|
127
|
+
hydro.get(workspace=workspace, dbkey=dbkey, date_min=date_next, station=station)
|
|
128
|
+
|
|
129
|
+
# Check if the station name contains a space
|
|
130
|
+
if ' ' in station:
|
|
131
|
+
# Replace space with underscore in the station name
|
|
132
|
+
station_previous = station
|
|
133
|
+
station = station.replace(' ', '_')
|
|
125
134
|
|
|
126
135
|
# Make sure both our original data and newly downloaded data exist
|
|
127
136
|
df_old_path = os.path.join(workspace, f"{station}_FLOW_cmd.csv")
|