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.
Files changed (77) hide show
  1. docker/Dockerfile +30 -0
  2. docker/docker-compose.yaml +58 -0
  3. docker/docker-compose_test.yaml +24 -0
  4. docker/start-docker-test.sh +6 -0
  5. docs/requirements.txt +10 -0
  6. docs/source/Changelog.md +2 -0
  7. docs/source/License.rst +7 -0
  8. docs/source/Methode.md +161 -0
  9. docs/source/_static/custom.css +8 -0
  10. docs/source/_static/favicon.ico +0 -0
  11. docs/source/_static/logo.png +0 -0
  12. docs/source/api/api.rst +15 -0
  13. docs/source/api/cli.rst +8 -0
  14. docs/source/api/weatherDB.broker.rst +10 -0
  15. docs/source/api/weatherDB.config.rst +7 -0
  16. docs/source/api/weatherDB.db.rst +23 -0
  17. docs/source/api/weatherDB.rst +22 -0
  18. docs/source/api/weatherDB.station.rst +56 -0
  19. docs/source/api/weatherDB.stations.rst +46 -0
  20. docs/source/api/weatherDB.utils.rst +22 -0
  21. docs/source/conf.py +137 -0
  22. docs/source/index.rst +33 -0
  23. docs/source/setup/Configuration.md +127 -0
  24. docs/source/setup/Hosting.md +9 -0
  25. docs/source/setup/Install.md +49 -0
  26. docs/source/setup/Quickstart.md +183 -0
  27. docs/source/setup/setup.rst +12 -0
  28. weatherdb/__init__.py +24 -0
  29. weatherdb/_version.py +1 -0
  30. weatherdb/alembic/README.md +8 -0
  31. weatherdb/alembic/alembic.ini +80 -0
  32. weatherdb/alembic/config.py +9 -0
  33. weatherdb/alembic/env.py +100 -0
  34. weatherdb/alembic/script.py.mako +26 -0
  35. weatherdb/alembic/versions/V1.0.0_initial_database_creation.py +898 -0
  36. weatherdb/alembic/versions/V1.0.2_more_charachters_for_settings+term_station_ma_raster.py +88 -0
  37. weatherdb/alembic/versions/V1.0.5_fix-ma-raster-values.py +152 -0
  38. weatherdb/alembic/versions/V1.0.6_update-views.py +22 -0
  39. weatherdb/broker.py +667 -0
  40. weatherdb/cli.py +214 -0
  41. weatherdb/config/ConfigParser.py +663 -0
  42. weatherdb/config/__init__.py +5 -0
  43. weatherdb/config/config_default.ini +162 -0
  44. weatherdb/db/__init__.py +3 -0
  45. weatherdb/db/connections.py +374 -0
  46. weatherdb/db/fixtures/RichterParameters.json +34 -0
  47. weatherdb/db/models.py +402 -0
  48. weatherdb/db/queries/get_quotient.py +155 -0
  49. weatherdb/db/views.py +165 -0
  50. weatherdb/station/GroupStation.py +710 -0
  51. weatherdb/station/StationBases.py +3108 -0
  52. weatherdb/station/StationET.py +111 -0
  53. weatherdb/station/StationP.py +807 -0
  54. weatherdb/station/StationPD.py +98 -0
  55. weatherdb/station/StationT.py +164 -0
  56. weatherdb/station/__init__.py +13 -0
  57. weatherdb/station/constants.py +21 -0
  58. weatherdb/stations/GroupStations.py +519 -0
  59. weatherdb/stations/StationsBase.py +1021 -0
  60. weatherdb/stations/StationsBaseTET.py +30 -0
  61. weatherdb/stations/StationsET.py +17 -0
  62. weatherdb/stations/StationsP.py +128 -0
  63. weatherdb/stations/StationsPD.py +24 -0
  64. weatherdb/stations/StationsT.py +21 -0
  65. weatherdb/stations/__init__.py +11 -0
  66. weatherdb/utils/TimestampPeriod.py +369 -0
  67. weatherdb/utils/__init__.py +3 -0
  68. weatherdb/utils/dwd.py +350 -0
  69. weatherdb/utils/geometry.py +69 -0
  70. weatherdb/utils/get_data.py +285 -0
  71. weatherdb/utils/logging.py +126 -0
  72. weatherdb-1.1.0.dist-info/LICENSE +674 -0
  73. weatherdb-1.1.0.dist-info/METADATA +765 -0
  74. weatherdb-1.1.0.dist-info/RECORD +77 -0
  75. weatherdb-1.1.0.dist-info/WHEEL +5 -0
  76. weatherdb-1.1.0.dist-info/entry_points.txt +2 -0
  77. 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'].")