weatherdb 1.1.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- docker/Dockerfile +30 -0
- docker/docker-compose.yaml +58 -0
- docker/docker-compose_test.yaml +24 -0
- docker/start-docker-test.sh +6 -0
- docs/requirements.txt +10 -0
- docs/source/Changelog.md +2 -0
- docs/source/License.rst +7 -0
- docs/source/Methode.md +161 -0
- docs/source/_static/custom.css +8 -0
- docs/source/_static/favicon.ico +0 -0
- docs/source/_static/logo.png +0 -0
- docs/source/api/api.rst +15 -0
- docs/source/api/cli.rst +8 -0
- docs/source/api/weatherDB.broker.rst +10 -0
- docs/source/api/weatherDB.config.rst +7 -0
- docs/source/api/weatherDB.db.rst +23 -0
- docs/source/api/weatherDB.rst +22 -0
- docs/source/api/weatherDB.station.rst +56 -0
- docs/source/api/weatherDB.stations.rst +46 -0
- docs/source/api/weatherDB.utils.rst +22 -0
- docs/source/conf.py +137 -0
- docs/source/index.rst +33 -0
- docs/source/setup/Configuration.md +127 -0
- docs/source/setup/Hosting.md +9 -0
- docs/source/setup/Install.md +49 -0
- docs/source/setup/Quickstart.md +183 -0
- docs/source/setup/setup.rst +12 -0
- weatherdb/__init__.py +24 -0
- weatherdb/_version.py +1 -0
- weatherdb/alembic/README.md +8 -0
- weatherdb/alembic/alembic.ini +80 -0
- weatherdb/alembic/config.py +9 -0
- weatherdb/alembic/env.py +100 -0
- weatherdb/alembic/script.py.mako +26 -0
- weatherdb/alembic/versions/V1.0.0_initial_database_creation.py +898 -0
- weatherdb/alembic/versions/V1.0.2_more_charachters_for_settings+term_station_ma_raster.py +88 -0
- weatherdb/alembic/versions/V1.0.5_fix-ma-raster-values.py +152 -0
- weatherdb/alembic/versions/V1.0.6_update-views.py +22 -0
- weatherdb/broker.py +667 -0
- weatherdb/cli.py +214 -0
- weatherdb/config/ConfigParser.py +663 -0
- weatherdb/config/__init__.py +5 -0
- weatherdb/config/config_default.ini +162 -0
- weatherdb/db/__init__.py +3 -0
- weatherdb/db/connections.py +374 -0
- weatherdb/db/fixtures/RichterParameters.json +34 -0
- weatherdb/db/models.py +402 -0
- weatherdb/db/queries/get_quotient.py +155 -0
- weatherdb/db/views.py +165 -0
- weatherdb/station/GroupStation.py +710 -0
- weatherdb/station/StationBases.py +3108 -0
- weatherdb/station/StationET.py +111 -0
- weatherdb/station/StationP.py +807 -0
- weatherdb/station/StationPD.py +98 -0
- weatherdb/station/StationT.py +164 -0
- weatherdb/station/__init__.py +13 -0
- weatherdb/station/constants.py +21 -0
- weatherdb/stations/GroupStations.py +519 -0
- weatherdb/stations/StationsBase.py +1021 -0
- weatherdb/stations/StationsBaseTET.py +30 -0
- weatherdb/stations/StationsET.py +17 -0
- weatherdb/stations/StationsP.py +128 -0
- weatherdb/stations/StationsPD.py +24 -0
- weatherdb/stations/StationsT.py +21 -0
- weatherdb/stations/__init__.py +11 -0
- weatherdb/utils/TimestampPeriod.py +369 -0
- weatherdb/utils/__init__.py +3 -0
- weatherdb/utils/dwd.py +350 -0
- weatherdb/utils/geometry.py +69 -0
- weatherdb/utils/get_data.py +285 -0
- weatherdb/utils/logging.py +126 -0
- weatherdb-1.1.0.dist-info/LICENSE +674 -0
- weatherdb-1.1.0.dist-info/METADATA +765 -0
- weatherdb-1.1.0.dist-info/RECORD +77 -0
- weatherdb-1.1.0.dist-info/WHEEL +5 -0
- weatherdb-1.1.0.dist-info/entry_points.txt +2 -0
- weatherdb-1.1.0.dist-info/top_level.txt +3 -0
weatherdb/db/models.py
ADDED
@@ -0,0 +1,402 @@
|
|
1
|
+
import sqlalchemy as sa
|
2
|
+
from sqlalchemy.sql import func
|
3
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
4
|
+
from sqlalchemy.orm import DeclarativeBase
|
5
|
+
from typing_extensions import Annotated
|
6
|
+
from datetime import timedelta, timezone
|
7
|
+
from typing import Optional
|
8
|
+
from geoalchemy2 import Geometry
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"MetaP",
|
12
|
+
"MetaPD",
|
13
|
+
"MetaET",
|
14
|
+
"MetaT",
|
15
|
+
"RawFiles",
|
16
|
+
"DroppedStations",
|
17
|
+
"ParameterVariables",
|
18
|
+
"RichterValues",
|
19
|
+
"StationMATimeserie",
|
20
|
+
"StationMARaster",
|
21
|
+
"NeededDownloadTime",
|
22
|
+
"Settings"
|
23
|
+
]
|
24
|
+
|
25
|
+
|
26
|
+
# define custom types
|
27
|
+
sint = Annotated[int, 2]
|
28
|
+
str3 = Annotated[str, 3]
|
29
|
+
str4 = Annotated[str, 4]
|
30
|
+
str7 = Annotated[str, 7]
|
31
|
+
str30 = Annotated[str, 30]
|
32
|
+
str50 = Annotated[str, 50]
|
33
|
+
|
34
|
+
class UTCDateTime(sa.types.TypeDecorator):
|
35
|
+
impl = sa.types.DateTime
|
36
|
+
cache_ok = True
|
37
|
+
|
38
|
+
def process_bind_param(self, value, engine):
|
39
|
+
if value is None:
|
40
|
+
return
|
41
|
+
if value.utcoffset() is None:
|
42
|
+
raise ValueError(
|
43
|
+
'Got naive datetime while timezone-aware is expected'
|
44
|
+
)
|
45
|
+
return value.astimezone(timezone.utc)
|
46
|
+
|
47
|
+
def process_result_value(self, value, engine):
|
48
|
+
if value is not None:
|
49
|
+
return value.replace(tzinfo=timezone.utc)
|
50
|
+
|
51
|
+
# define base class for all database tables
|
52
|
+
class ModelBase(DeclarativeBase):
|
53
|
+
registry = sa.orm.registry(
|
54
|
+
type_annotation_map={
|
55
|
+
sint: sa.SmallInteger(),
|
56
|
+
UTCDateTime: UTCDateTime(),
|
57
|
+
float: sa.Float(),
|
58
|
+
str3: sa.VARCHAR(3),
|
59
|
+
str4: sa.VARCHAR(4),
|
60
|
+
str7: sa.VARCHAR(7),
|
61
|
+
str30: sa.VARCHAR(30),
|
62
|
+
str50: sa.VARCHAR(50),
|
63
|
+
}
|
64
|
+
)
|
65
|
+
|
66
|
+
def __init_subclass__(cls, **kwargs):
|
67
|
+
super().__init_subclass__(**kwargs)
|
68
|
+
if hasattr(cls, "load_fixtures"):
|
69
|
+
sa.event.listen(cls.metadata, 'after_create', cls.load_fixtures)
|
70
|
+
|
71
|
+
# define database models
|
72
|
+
# ----------------------
|
73
|
+
class MetaBase(ModelBase):
|
74
|
+
__abstract__ = True
|
75
|
+
|
76
|
+
station_id: Mapped[int] = mapped_column(
|
77
|
+
primary_key=True,
|
78
|
+
comment="official DWD-ID of the station",
|
79
|
+
sort_order=-105)
|
80
|
+
is_real: Mapped[bool] = mapped_column(
|
81
|
+
default=True,
|
82
|
+
server_default=sa.sql.expression.true(),
|
83
|
+
comment=" 'Is this station a real station with own measurements or only a virtual station, to have complete timeseries for every precipitation station.",
|
84
|
+
sort_order=-104)
|
85
|
+
stationsname: Mapped[str50] = mapped_column(
|
86
|
+
comment="The stations official name as text",
|
87
|
+
sort_order=-103)
|
88
|
+
bundesland: Mapped[str30] = mapped_column(
|
89
|
+
comment="The state the station is located in",
|
90
|
+
sort_order=-102)
|
91
|
+
raw_from: Mapped[Optional[UTCDateTime]] = mapped_column(
|
92
|
+
comment="The timestamp from when on own \"raw\" data is available",
|
93
|
+
sort_order=-95)
|
94
|
+
raw_until: Mapped[Optional[UTCDateTime]] = mapped_column(
|
95
|
+
comment="The timestamp until when own \"raw\" data is available",
|
96
|
+
sort_order=-94)
|
97
|
+
hist_until: Mapped[Optional[UTCDateTime]] = mapped_column(
|
98
|
+
comment="The timestamp until when own \"raw\" data is available",
|
99
|
+
sort_order=-93)
|
100
|
+
last_imp_from: Mapped[Optional[UTCDateTime]] = mapped_column(
|
101
|
+
comment="The minimal timestamp of the last import, that might not yet have been treated",
|
102
|
+
sort_order=-81)
|
103
|
+
last_imp_until: Mapped[Optional[UTCDateTime]] = mapped_column(
|
104
|
+
comment="The maximal timestamp of the last import, that might not yet have been treated",
|
105
|
+
sort_order=-80)
|
106
|
+
last_imp_filled: Mapped[bool] = mapped_column(
|
107
|
+
default=False,
|
108
|
+
server_default=sa.sql.expression.false(),
|
109
|
+
comment="Got the last import already filled?",
|
110
|
+
sort_order=-75)
|
111
|
+
filled_from: Mapped[Optional[UTCDateTime]] = mapped_column(
|
112
|
+
comment="The timestamp from when on filled data is available",
|
113
|
+
sort_order=-74)
|
114
|
+
filled_until: Mapped[Optional[UTCDateTime]] = mapped_column(
|
115
|
+
comment="The timestamp until when filled data is available",
|
116
|
+
sort_order=-73)
|
117
|
+
stationshoehe: Mapped[int] = mapped_column(
|
118
|
+
comment="The stations height above the ground in meters",
|
119
|
+
sort_order=10)
|
120
|
+
geometry: Mapped[str] = mapped_column(
|
121
|
+
Geometry('POINT', 4326),
|
122
|
+
comment="The stations location in the WGS84 coordinate reference system (EPSG:4326)",
|
123
|
+
sort_order=11)
|
124
|
+
geometry_utm: Mapped[str] = mapped_column(
|
125
|
+
Geometry('POINT', 25832),
|
126
|
+
comment="The stations location in the UTM32 coordinate reference system (EPSG:25832)",
|
127
|
+
sort_order=12)
|
128
|
+
|
129
|
+
class MetaBaseQC(ModelBase):
|
130
|
+
__abstract__ = True
|
131
|
+
|
132
|
+
last_imp_qc: Mapped[bool] = mapped_column(
|
133
|
+
default=False,
|
134
|
+
server_default=sa.sql.expression.false(),
|
135
|
+
comment="Got the last import already quality checked?",
|
136
|
+
sort_order=-65)
|
137
|
+
qc_from: Mapped[Optional[UTCDateTime]] = mapped_column(
|
138
|
+
comment="The timestamp from when on quality checked(\"qc\") data is available",
|
139
|
+
sort_order=-64)
|
140
|
+
qc_until: Mapped[Optional[UTCDateTime]] = mapped_column(
|
141
|
+
comment="The timestamp until when quality checked(\"qc\") data is available",
|
142
|
+
sort_order=-63)
|
143
|
+
qc_dropped: Mapped[Optional[float]] = mapped_column(
|
144
|
+
comment="The percentage of dropped values during the quality check",
|
145
|
+
sort_order=-62)
|
146
|
+
|
147
|
+
# declare all database tables
|
148
|
+
# ---------------------------
|
149
|
+
class MetaP(MetaBase, MetaBaseQC):
|
150
|
+
__tablename__ = 'meta_p'
|
151
|
+
__table_args__ = dict(
|
152
|
+
schema='public',
|
153
|
+
comment="The Meta informations of the precipitation stations.")
|
154
|
+
|
155
|
+
last_imp_corr: Mapped[bool] = mapped_column(
|
156
|
+
default=False,
|
157
|
+
server_default=sa.sql.expression.false(),
|
158
|
+
comment="Got the last import already Richter corrected?",
|
159
|
+
sort_order=-55)
|
160
|
+
corr_from: Mapped[Optional[UTCDateTime]] = mapped_column(
|
161
|
+
comment="The timestamp from when on corrected data is available",
|
162
|
+
sort_order=-54)
|
163
|
+
corr_until: Mapped[Optional[UTCDateTime]] = mapped_column(
|
164
|
+
comment="The timestamp until when corrected data is available",
|
165
|
+
sort_order=-53)
|
166
|
+
horizon: Mapped[Optional[float]] = mapped_column(
|
167
|
+
comment="The horizon angle in degrees, how it got defined by Richter(1995).",
|
168
|
+
sort_order=-52)
|
169
|
+
richter_class: Mapped[Optional[str]] = mapped_column(
|
170
|
+
sa.String(),
|
171
|
+
comment="The Richter exposition class, that got derived from the horizon angle.",
|
172
|
+
sort_order=-51)
|
173
|
+
|
174
|
+
|
175
|
+
class MetaPD(MetaBase):
|
176
|
+
__tablename__ = 'meta_p_d'
|
177
|
+
__table_args__ = dict(
|
178
|
+
schema='public',
|
179
|
+
comment="The Meta informations of the daily precipitation stations.")
|
180
|
+
|
181
|
+
|
182
|
+
class MetaET(MetaBase, MetaBaseQC):
|
183
|
+
__tablename__ = 'meta_et'
|
184
|
+
__table_args__ = dict(
|
185
|
+
schema='public',
|
186
|
+
comment="The Meta informations of the evapotranspiration stations.")
|
187
|
+
|
188
|
+
|
189
|
+
class MetaT(MetaBase, MetaBaseQC):
|
190
|
+
__tablename__ = 'meta_t'
|
191
|
+
__table_args__ = dict(
|
192
|
+
schema='public',
|
193
|
+
comment="The Meta informations of the temperature stations.")
|
194
|
+
|
195
|
+
|
196
|
+
class RawFiles(ModelBase):
|
197
|
+
__tablename__ = 'raw_files'
|
198
|
+
__table_args__ = dict(
|
199
|
+
schema='public',
|
200
|
+
comment="The files that got imported from the CDC Server.")
|
201
|
+
|
202
|
+
parameter: Mapped[str3] = mapped_column(
|
203
|
+
primary_key=True,
|
204
|
+
comment="The parameter that got downloaded for this file. e.g. t, et, p_d, p",
|
205
|
+
sort_order=-10)
|
206
|
+
filepath: Mapped[str] = mapped_column(
|
207
|
+
primary_key=True,
|
208
|
+
comment="The filepath on the CDC Server",
|
209
|
+
sort_order=-8)
|
210
|
+
modtime: Mapped[UTCDateTime] = mapped_column(
|
211
|
+
comment="The modification time on the CDC Server of the coresponding file",
|
212
|
+
sort_order=-5)
|
213
|
+
|
214
|
+
|
215
|
+
class DroppedStations(ModelBase):
|
216
|
+
__tablename__ = 'dropped_stations'
|
217
|
+
__table_args__ = dict(
|
218
|
+
schema='public',
|
219
|
+
comment="This table is there to save the station ids that got dropped, so they wont GET recreated")
|
220
|
+
|
221
|
+
station_id: Mapped[int] = mapped_column(
|
222
|
+
primary_key=True,
|
223
|
+
comment="The station id that got dropped",
|
224
|
+
sort_order=-10)
|
225
|
+
parameter: Mapped[str3] = mapped_column(
|
226
|
+
primary_key=True,
|
227
|
+
comment="The parameter (p,t,et,p_d) of the station that got dropped",
|
228
|
+
sort_order=-9)
|
229
|
+
why: Mapped[str] = mapped_column(
|
230
|
+
sa.Text(),
|
231
|
+
comment="The reason why the station got dropped",
|
232
|
+
sort_order=-8)
|
233
|
+
timestamp: Mapped[UTCDateTime] = mapped_column(
|
234
|
+
server_default=func.now(),
|
235
|
+
comment="The timestamp when the station got dropped",
|
236
|
+
sort_order=-7)
|
237
|
+
|
238
|
+
|
239
|
+
class ParameterVariables(ModelBase):
|
240
|
+
__tablename__ = 'parameter_variables'
|
241
|
+
__table_args__ = dict(
|
242
|
+
schema='public',
|
243
|
+
comment="This table is there to save specific variables that are nescesary for the functioning of the scripts")
|
244
|
+
|
245
|
+
parameter: Mapped[str3] = mapped_column(
|
246
|
+
primary_key=True,
|
247
|
+
comment="The parameter for which the variables are valid. e.g. p/p_d/t/et.",
|
248
|
+
sort_order=-10)
|
249
|
+
start_tstp_last_imp: Mapped[Optional[UTCDateTime]] = mapped_column(
|
250
|
+
comment="At what timestamp did the last complete import start. This is then the maximum timestamp for which to expand the timeseries to.",
|
251
|
+
sort_order=-9)
|
252
|
+
max_tstp_last_imp: Mapped[Optional[UTCDateTime]] = mapped_column(
|
253
|
+
comment="The maximal timestamp of the last imports raw data of all the timeseries",
|
254
|
+
sort_order=-8)
|
255
|
+
|
256
|
+
|
257
|
+
class RichterParameters(ModelBase):
|
258
|
+
__tablename__ = 'richter_parameters'
|
259
|
+
__table_args__ = dict(
|
260
|
+
schema='public',
|
261
|
+
comment="The Richter parameter values for the equation.")
|
262
|
+
|
263
|
+
precipitation_typ: Mapped[str] = mapped_column(
|
264
|
+
sa.Text(),
|
265
|
+
primary_key=True,
|
266
|
+
comment="The type of precipitation. e.g. 'Schnee', 'Regen', ...",
|
267
|
+
sort_order=-10)
|
268
|
+
e: Mapped[float] = mapped_column(
|
269
|
+
comment="The e-value of the equation.",
|
270
|
+
sort_order=-8)
|
271
|
+
b_no_protection: Mapped[float] = mapped_column(
|
272
|
+
name="b_no-protection",
|
273
|
+
comment="The b-value of the equation for exposition class 'no protection'.",
|
274
|
+
sort_order=-5)
|
275
|
+
b_little_protection: Mapped[float] = mapped_column(
|
276
|
+
name="b_little-protection",
|
277
|
+
comment="The b-value of the equation for exposition class 'little protection'.",
|
278
|
+
sort_order=-4)
|
279
|
+
b_protected: Mapped[float] = mapped_column(
|
280
|
+
comment="The b-value of the equation for exposition class 'protected'.",
|
281
|
+
sort_order=-3)
|
282
|
+
b_heavy_protection: Mapped[float] = mapped_column(
|
283
|
+
name="b_heavy-protection",
|
284
|
+
comment="The b-value of the equation for exposition class 'heavy protection'.",
|
285
|
+
sort_order=-2)
|
286
|
+
|
287
|
+
@classmethod
|
288
|
+
def load_fixtures(cls, target, connection, *args, **kwargs):
|
289
|
+
from pathlib import Path
|
290
|
+
from json import load
|
291
|
+
|
292
|
+
fix_dir = Path(__file__).parent.joinpath("fixtures")
|
293
|
+
with open(fix_dir/"RichterParameters.json") as f:
|
294
|
+
data = load(f)
|
295
|
+
|
296
|
+
if connection.execute(sa.sql.select(sa.func.count("*")).select_from(cls.__table__)).scalar() == 0:
|
297
|
+
connection.execute(cls.__table__.insert(), data)
|
298
|
+
connection.commit()
|
299
|
+
|
300
|
+
# sa.event.listen(RichterParameters.__table__, 'after_create', RichterParameters.load_fixtures)
|
301
|
+
|
302
|
+
class StationMATimeserie(ModelBase):
|
303
|
+
__tablename__ = 'station_ma_timeserie'
|
304
|
+
__table_args__ = dict(
|
305
|
+
schema='public',
|
306
|
+
comment="The multi annual mean values of the stations timeseries for the maximum available timespan.")
|
307
|
+
station_id: Mapped[int] = mapped_column(
|
308
|
+
primary_key=True,
|
309
|
+
comment="The DWD-ID of the station.",
|
310
|
+
sort_order=-10)
|
311
|
+
parameter: Mapped[str3] = mapped_column(
|
312
|
+
primary_key=True,
|
313
|
+
comment="The parameter of the station. e.g. 'P', 'T', 'ET'",
|
314
|
+
sort_order=-9)
|
315
|
+
kind: Mapped[str] = mapped_column(
|
316
|
+
primary_key=True,
|
317
|
+
comment="The kind of the timeserie. e.g. 'raw', 'filled', 'corr'",
|
318
|
+
sort_order=-8)
|
319
|
+
value: Mapped[int] = mapped_column(
|
320
|
+
comment="The multi annual value of the yearly mean value of the station to the multi annual mean value of the raster.",
|
321
|
+
sort_order=1)
|
322
|
+
|
323
|
+
|
324
|
+
class StationMARaster(ModelBase):
|
325
|
+
__tablename__ = 'station_ma_raster'
|
326
|
+
__table_args__ = dict(
|
327
|
+
schema='public',
|
328
|
+
comment="The multi annual climate raster values for each station.")
|
329
|
+
|
330
|
+
station_id: Mapped[int] = mapped_column(
|
331
|
+
primary_key=True,
|
332
|
+
comment="The DWD-ID of the station.",
|
333
|
+
sort_order=-10)
|
334
|
+
raster_key: Mapped[str7] = mapped_column(
|
335
|
+
primary_key=True,
|
336
|
+
comment="The name of the raster. e.g. 'dwd' or 'hyras'",
|
337
|
+
sort_order=-9)
|
338
|
+
parameter: Mapped[str3] = mapped_column(
|
339
|
+
primary_key=True,
|
340
|
+
comment="The parameter of the raster. e.g. 'p', 't', 'et'",
|
341
|
+
sort_order=-8)
|
342
|
+
term: Mapped[str4] = mapped_column(
|
343
|
+
primary_key=True,
|
344
|
+
comment="The term of the raster. e.g. 'year', 'wihy', 'suhy'",
|
345
|
+
sort_order=-7)
|
346
|
+
value: Mapped[int] = mapped_column(
|
347
|
+
comment="The value of the raster for the station in the database unit.",
|
348
|
+
sort_order=1)
|
349
|
+
distance: Mapped[int] = mapped_column(
|
350
|
+
comment="The distance of the station to the raster value in meters.",
|
351
|
+
sort_order=2)
|
352
|
+
|
353
|
+
|
354
|
+
class NeededDownloadTime(ModelBase):
|
355
|
+
__tablename__ = 'needed_download_time'
|
356
|
+
__table_args__ = dict(
|
357
|
+
schema='public',
|
358
|
+
comment="Saves the time needed to save the timeseries. This helps predicting download time")
|
359
|
+
timestamp: Mapped[UTCDateTime] = mapped_column(
|
360
|
+
server_default=func.now(),
|
361
|
+
primary_key=True,
|
362
|
+
comment="The timestamp when the download hapend.",
|
363
|
+
sort_order=-5)
|
364
|
+
quantity: Mapped[int] = mapped_column(
|
365
|
+
comment="The number of stations that got downloaded",
|
366
|
+
sort_order=1)
|
367
|
+
aggregate: Mapped[str] = mapped_column(
|
368
|
+
comment="The chosen aggregation. e.g. hourly, 10min, daily, ...",
|
369
|
+
sort_order=2)
|
370
|
+
timespan: Mapped[timedelta] = mapped_column(
|
371
|
+
sa.Interval(),
|
372
|
+
comment="The timespan of the downloaded timeseries. e.g. 2 years",
|
373
|
+
sort_order=3)
|
374
|
+
zip: Mapped[bool] = mapped_column(
|
375
|
+
comment="Was the download zipped?",
|
376
|
+
sort_order=4)
|
377
|
+
pc: Mapped[str] = mapped_column(
|
378
|
+
comment="The name of the pc that downloaded the timeseries.",
|
379
|
+
sort_order=5)
|
380
|
+
duration: Mapped[timedelta] = mapped_column(
|
381
|
+
sa.Interval(),
|
382
|
+
comment="The needed time to download and create the timeserie",
|
383
|
+
sort_order=6)
|
384
|
+
output_size: Mapped[int] = mapped_column(
|
385
|
+
comment="The size of the created output file in bytes",
|
386
|
+
sort_order=7)
|
387
|
+
|
388
|
+
|
389
|
+
class Settings(ModelBase):
|
390
|
+
__tablename__ = 'settings'
|
391
|
+
__table_args__ = dict(
|
392
|
+
schema='public',
|
393
|
+
comment="This table saves settings values for the script-databse connection. E.G. the latest package version that updated the database.")
|
394
|
+
key: Mapped[str] = mapped_column(
|
395
|
+
sa.String(20),
|
396
|
+
primary_key=True,
|
397
|
+
comment="The key of the setting",
|
398
|
+
sort_order=0)
|
399
|
+
value: Mapped[str] = mapped_column(
|
400
|
+
sa.String(60),
|
401
|
+
comment="The value of the setting",
|
402
|
+
sort_order=1)
|
@@ -0,0 +1,155 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import sqlalchemy as sa
|
3
|
+
from ..views import StationKindQuotientView, StationMATimeserieRasterQuotientView
|
4
|
+
|
5
|
+
|
6
|
+
def _get_quotient(con, stids, paras, kinds_num, kinds_denom, return_as):
|
7
|
+
"""Get the quotient of multi-annual means of two different kinds or the timeserie and the multi annual raster value.
|
8
|
+
|
9
|
+
$quotient = \\overline{ts}_{kind_num} / \\overline{ts}_{denom}$
|
10
|
+
|
11
|
+
Parameters
|
12
|
+
----------
|
13
|
+
con : sqlalchemy.engine.base.Connection
|
14
|
+
The connection to the database.
|
15
|
+
stids : list of int or int or None
|
16
|
+
The station ids to get the quotient from.
|
17
|
+
If None, then all stations in the database are used.
|
18
|
+
paras : list of str or str
|
19
|
+
The parameters to get the quotient from.
|
20
|
+
kinds_num : list of str or str
|
21
|
+
The timeseries kinds of the numerators.
|
22
|
+
Should be one of ['raw', 'qc', 'filled'].
|
23
|
+
For precipitation also "corr" is possible.
|
24
|
+
kinds_denom : list of str or str
|
25
|
+
The timeseries kinds of the denominator or the multi annual raster key.
|
26
|
+
If the denominator is a multi annual raster key, then the result is the quotient of the timeserie and the raster value.
|
27
|
+
Possible values are:
|
28
|
+
- for timeserie kinds: 'raw', 'qc', 'filled' or for precipitation also "corr".
|
29
|
+
- for raster keys: 'hyras', 'dwd' or 'regnie', depending on your defined raster files.
|
30
|
+
return_as : str
|
31
|
+
The format of the return value.
|
32
|
+
If "df" then a pandas DataFrame is returned.
|
33
|
+
If "json" then a list with dictionaries is returned.
|
34
|
+
|
35
|
+
Returns
|
36
|
+
-------
|
37
|
+
pandas.DataFrame or list of dict
|
38
|
+
The quotient of the two timeseries as DataFrame or list of dictionaries (JSON) depending on the return_as parameter.
|
39
|
+
The default is pd.DataFrame.
|
40
|
+
|
41
|
+
Raises
|
42
|
+
------
|
43
|
+
ValueError
|
44
|
+
If the input parameters were not correct.
|
45
|
+
"""
|
46
|
+
# split ts and raster kinds
|
47
|
+
rast_keys = {"hyras", "regnie", "dwd"}
|
48
|
+
kinds_denom_ts = set(kinds_denom) - rast_keys
|
49
|
+
kinds_denom_rast = set(kinds_denom) & rast_keys
|
50
|
+
|
51
|
+
# create tests
|
52
|
+
tests_ts = []
|
53
|
+
tests_rast = []
|
54
|
+
|
55
|
+
# create stids tests
|
56
|
+
if (isinstance(stids, list) or isinstance(stids, set)) and len(stids) == 1:
|
57
|
+
stids = stids[0]
|
58
|
+
if isinstance(stids, int):
|
59
|
+
tests_ts.append(StationKindQuotientView.station_id == stids)
|
60
|
+
tests_rast.append(StationMATimeserieRasterQuotientView.station_id == stids)
|
61
|
+
elif isinstance(stids, list) or isinstance(stids, set):
|
62
|
+
tests_ts.append(StationKindQuotientView.station_id.in_(stids))
|
63
|
+
tests_rast.append(StationMATimeserieRasterQuotientView.station_id.in_(stids))
|
64
|
+
elif stids is not None:
|
65
|
+
raise ValueError("The stids parameter should be a list of integers or an integer or None.")
|
66
|
+
|
67
|
+
# create paras tests
|
68
|
+
if (isinstance(paras, list) or isinstance(paras, set)) and len(paras) == 1:
|
69
|
+
paras = paras[0]
|
70
|
+
if isinstance(paras, str):
|
71
|
+
tests_ts.append(StationKindQuotientView.parameter == paras)
|
72
|
+
tests_rast.append(StationMATimeserieRasterQuotientView.parameter.like(f"{paras}%"))
|
73
|
+
elif isinstance(paras, list) or isinstance(paras, set):
|
74
|
+
tests_ts.append(StationKindQuotientView.parameter.in_(paras))
|
75
|
+
ors = []
|
76
|
+
for para in paras:
|
77
|
+
ors.append(StationMATimeserieRasterQuotientView.parameter.like(f"{para}%"))
|
78
|
+
tests_rast.append(sa.or_(*ors))
|
79
|
+
else:
|
80
|
+
raise ValueError("The paras parameter should be a list of strings or a string.")
|
81
|
+
|
82
|
+
# create kinds_num tests
|
83
|
+
if (isinstance(kinds_num, list) or isinstance(kinds_num, set)) and len(kinds_num) == 1:
|
84
|
+
kinds_num = kinds_num[0]
|
85
|
+
if isinstance(kinds_num, str):
|
86
|
+
tests_ts.append(StationKindQuotientView.kind_numerator == kinds_num)
|
87
|
+
tests_rast.append(StationMATimeserieRasterQuotientView.kind == kinds_num)
|
88
|
+
elif isinstance(kinds_num, list) or isinstance(kinds_num, set):
|
89
|
+
tests_ts.append(StationKindQuotientView.kind_numerator.in_(kinds_num))
|
90
|
+
tests_rast.append(StationMATimeserieRasterQuotientView.kind.in_(kinds_num))
|
91
|
+
else:
|
92
|
+
raise ValueError("The kinds_num parameter should be a list of strings or a string.")
|
93
|
+
|
94
|
+
# create kinds_denom tests
|
95
|
+
if (isinstance(kinds_denom, list) or isinstance(kinds_denom, set)) and len(kinds_denom) == 1:
|
96
|
+
kinds_denom = kinds_denom[0]
|
97
|
+
if isinstance(kinds_denom, str):
|
98
|
+
tests_ts.append(StationKindQuotientView.kind_denominator == kinds_denom)
|
99
|
+
tests_rast.append(StationMATimeserieRasterQuotientView.raster_key == kinds_denom)
|
100
|
+
elif isinstance(kinds_denom, list) or isinstance(kinds_denom, set):
|
101
|
+
tests_ts.append(StationKindQuotientView.kind_denominator.in_(kinds_denom))
|
102
|
+
tests_rast.append(StationMATimeserieRasterQuotientView.raster_key.in_(kinds_denom))
|
103
|
+
else:
|
104
|
+
raise ValueError("The kinds_denom parameter should be a list of strings or a string.")
|
105
|
+
|
106
|
+
# get the quotient from the database views
|
107
|
+
data = []
|
108
|
+
# get timeseries quotient
|
109
|
+
if len(kinds_denom_ts) > 0:
|
110
|
+
stmnt = sa\
|
111
|
+
.select(
|
112
|
+
StationKindQuotientView.station_id,
|
113
|
+
StationKindQuotientView.parameter,
|
114
|
+
StationKindQuotientView.kind_numerator,
|
115
|
+
StationKindQuotientView.kind_denominator,
|
116
|
+
StationKindQuotientView.value)\
|
117
|
+
.select_from(StationKindQuotientView.__table__)\
|
118
|
+
.where(sa.and_(*tests_ts))
|
119
|
+
|
120
|
+
data = con.execute(stmnt).all()
|
121
|
+
|
122
|
+
# get raster quotient
|
123
|
+
if len(kinds_denom_rast) > 0:
|
124
|
+
stmnt = sa.\
|
125
|
+
select(
|
126
|
+
StationMATimeserieRasterQuotientView.station_id,
|
127
|
+
StationMATimeserieRasterQuotientView.parameter,
|
128
|
+
StationMATimeserieRasterQuotientView.kind.label("kind_numerator"),
|
129
|
+
StationMATimeserieRasterQuotientView.raster_key.label("kind_denominator"),
|
130
|
+
StationMATimeserieRasterQuotientView.value)\
|
131
|
+
.select_from(
|
132
|
+
StationMATimeserieRasterQuotientView.__table__)\
|
133
|
+
.where(sa.and_(*tests_rast))
|
134
|
+
|
135
|
+
data = data + con.execute(stmnt).all()
|
136
|
+
|
137
|
+
# create return value
|
138
|
+
if not any(data):
|
139
|
+
data = []
|
140
|
+
if return_as == "json":
|
141
|
+
return [
|
142
|
+
dict(
|
143
|
+
station_id=row[0],
|
144
|
+
parameter=row[1],
|
145
|
+
kind_num=row[2],
|
146
|
+
kind_denom=row[3],
|
147
|
+
value=row[4])
|
148
|
+
for row in data]
|
149
|
+
elif return_as == "df":
|
150
|
+
return pd.DataFrame(
|
151
|
+
data,
|
152
|
+
columns=["station_id", "parameter", "kind_num", "kind_denom", "value"]
|
153
|
+
).set_index(["station_id", "parameter", "kind_num", "kind_denom"])
|
154
|
+
else:
|
155
|
+
raise ValueError("The return_as parameter was not correct. Use one of ['df', 'json'].")
|