weatherdb 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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'].")