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
@@ -0,0 +1,98 @@
1
+ # libraries
2
+ import logging
3
+ import sqlalchemy as sa
4
+ from sqlalchemy import text as sqltxt
5
+ from functools import cached_property
6
+
7
+ from ..db.connections import db_engine
8
+ from ..utils.dwd import dwd_id_to_str
9
+ from ..db.models import MetaPD
10
+ from .StationBases import StationPBase, StationCanVirtualBase
11
+
12
+ # set settings
13
+ # ############
14
+ __all__ = ["StationPD"]
15
+ log = logging.getLogger(__name__)
16
+
17
+ # class definition
18
+ ##################
19
+ class StationPD(StationPBase, StationCanVirtualBase):
20
+ """A class to work with and download daily precipitation data for one station.
21
+
22
+ Those station data are only downloaded to do some quality checks on the 10 minute data.
23
+ Therefor there is no special quality check and richter correction done on this data.
24
+ If you want daily precipitation data, better use the 10 minutes station(StationP) and aggregate to daily values."""
25
+
26
+ # common settings
27
+ _MetaModel = MetaPD
28
+ _para = "p_d"
29
+ _para_base = "p"
30
+ _para_long = "daily Precipitation"
31
+ _unit = "mm/day"
32
+ _valid_kinds = {"raw", "filled", "filled_by"}
33
+ _best_kind = "filled"
34
+
35
+ # cdc dwd parameters
36
+ _ftp_folder_base = [
37
+ "climate_environment/CDC/observations_germany/climate/daily/kl/",
38
+ "climate_environment/CDC/observations_germany/climate/daily/more_precip/"]
39
+ _cdc_col_names_imp = ["RSK"]
40
+ _db_col_names_imp = ["raw"]
41
+
42
+ # timestamp configurations
43
+ _tstp_format_db = "%Y%m%d"
44
+ _tstp_format_human = "%Y-%m-%d"
45
+ _tstp_dtype = "date"
46
+ _interval = "1 day"
47
+
48
+ # aggregation
49
+ _min_agg_to = "day"
50
+
51
+ # methods from the base class that should not be active for this class
52
+ quality_check = property(doc='(!) Disallowed inherited')
53
+ last_imp_quality_check = property(doc='(!) Disallowed inherited')
54
+ get_corr = property(doc='(!) Disallowed inherited')
55
+ get_adj = property(doc='(!) Disallowed inherited')
56
+ get_qc = property(doc='(!) Disallowed inherited')
57
+
58
+ def __init__(self, id, **kwargs):
59
+ super().__init__(id, **kwargs)
60
+ self.id_str = dwd_id_to_str(id)
61
+
62
+ def _download_raw(self, zipfiles):
63
+ df_all, max_hist_tstp = super()._download_raw(zipfiles)
64
+
65
+ # fill RSK with values from RS if not given
66
+ if "RS" in df_all.columns and "RSK" in df_all.columns:
67
+ mask = df_all["RSK"].isna()
68
+ df_all.loc[mask, "RSK"] = df_all.loc[mask, "RS"]
69
+ elif "RS" in df_all.columns:
70
+ df_all["RSK"] = df_all["RS"]
71
+
72
+ return df_all, max_hist_tstp
73
+
74
+ @cached_property
75
+ def _table(self):
76
+ return sa.table(
77
+ f"{self.id}_{self._para}",
78
+ sa.column("timestamp", sa.Date),
79
+ sa.column("raw", sa.Integer),
80
+ sa.column("filled", sa.Integer),
81
+ sa.column("filled_by", sa.SmallInteger),
82
+ schema="timeseries")
83
+
84
+ @db_engine.deco_create_privilege
85
+ def _create_timeseries_table(self):
86
+ """Create the timeseries table in the DB if it is not yet existing."""
87
+ sql_add_table = '''
88
+ CREATE TABLE IF NOT EXISTS timeseries."{stid}_{para}" (
89
+ timestamp date PRIMARY KEY,
90
+ raw int4,
91
+ filled int4,
92
+ filled_by int2
93
+ );
94
+ '''.format(stid=self.id, para=self._para)
95
+ with db_engine.connect() as con:
96
+ con.execute(sqltxt(sql_add_table))
97
+ con.commit()
98
+
@@ -0,0 +1,164 @@
1
+ # libraries
2
+ import logging
3
+ import sqlalchemy as sa
4
+ from sqlalchemy import text as sqltxt
5
+ from functools import cached_property
6
+
7
+ from ..db.connections import db_engine
8
+ from ..utils.dwd import dwd_id_to_str
9
+ from ..db.models import MetaT
10
+ from .StationBases import StationTETBase
11
+
12
+ # set settings
13
+ # ############
14
+ __all__ = ["StationT"]
15
+ log = logging.getLogger(__name__)
16
+
17
+ # class definition
18
+ ##################
19
+ class StationT(StationTETBase):
20
+ """A class to work with and download temperaure data for one station."""
21
+
22
+ # common settings
23
+ _MetaModel = MetaT
24
+ _para = "t"
25
+ _para_base = _para
26
+ _para_long = "Temperature"
27
+ _unit = "°C"
28
+ _decimals = 10
29
+ _valid_kinds = {"raw", "raw_min", "raw_max", "qc",
30
+ "filled", "filled_min", "filled_max", "filled_by"}
31
+
32
+ # cdc dwd parameters
33
+ _ftp_folder_base = [
34
+ "climate_environment/CDC/observations_germany/climate/daily/kl/"]
35
+ _cdc_date_col = "MESS_DATUM"
36
+ _cdc_col_names_imp = ["TMK", "TNK", "TXK"]
37
+ _db_col_names_imp = ["raw", "raw_min", "raw_max"]
38
+
39
+ # aggregation
40
+ _agg_fun = "avg"
41
+
42
+ # for regionalistaion
43
+ _ma_terms = ["year"]
44
+ _coef_sign = ["-", "+"]
45
+
46
+ # # for the fillup
47
+ _filled_by_n = 5
48
+ _fillup_max_dist = 100e3
49
+
50
+
51
+ def __init__(self, id, **kwargs):
52
+ super().__init__(id, **kwargs)
53
+ self.id_str = dwd_id_to_str(id)
54
+
55
+ @cached_property
56
+ def _table(self):
57
+ return sa.table(
58
+ f"{self.id}_{self._para}",
59
+ sa.column("timestamp", sa.Date),
60
+ sa.column("raw", sa.Integer),
61
+ sa.column("raw_min", sa.Integer),
62
+ sa.column("raw_max", sa.Integer),
63
+ sa.column("qc", sa.Integer),
64
+ sa.column("filled", sa.Integer),
65
+ sa.column("filled_min", sa.Integer),
66
+ sa.column("filled_max", sa.Integer),
67
+ sa.column("filled_by", sa.SmallInteger),
68
+ schema="timeseries")
69
+
70
+ def _create_timeseries_table(self):
71
+ """Create the timeseries table in the DB if it is not yet existing."""
72
+ sql_add_table = f'''
73
+ CREATE TABLE IF NOT EXISTS timeseries."{self.id}_{self._para}" (
74
+ timestamp date PRIMARY KEY,
75
+ raw integer NULL DEFAULT NULL,
76
+ raw_min integer NULL DEFAULT NULL,
77
+ raw_max integer NULL DEFAULT NULL,
78
+ qc integer NULL DEFAULT NULL,
79
+ filled integer NULL DEFAULT NULL,
80
+ filled_min integer NULL DEFAULT NULL,
81
+ filled_max integer NULL DEFAULT NULL,
82
+ filled_by smallint[{self._filled_by_n}] NULL DEFAULT NULL
83
+ );
84
+ '''
85
+ with db_engine.connect() as con:
86
+ con.execute(sqltxt(sql_add_table))
87
+ con.commit()
88
+
89
+ def _get_sql_new_qc(self, period):
90
+ # inversion possible?
91
+ do_invers = self.get_meta(infos=["stationshoehe"])>800
92
+
93
+ sql_nears = self._get_sql_near_median(
94
+ period=period, only_real=False, add_is_winter=do_invers,
95
+ extra_cols="raw-nbs_median AS diff")
96
+
97
+ if do_invers:
98
+ # without inversion
99
+ sql_null_case = "CASE WHEN (winter) THEN "+\
100
+ f"diff < {-5 * self._decimals} ELSE "+\
101
+ f"ABS(diff) > {5 * self._decimals} END "+\
102
+ f"OR raw < {-50 * self._decimals} OR raw > {50 * self._decimals}"
103
+ else:
104
+ # with inversion
105
+ sql_null_case = f"ABS(diff) > {5 * self._decimals}"
106
+
107
+ # create sql for new qc
108
+ sql_new_qc = f"""
109
+ WITH nears AS ({sql_nears})
110
+ SELECT
111
+ timestamp,
112
+ (CASE WHEN ({sql_null_case})
113
+ THEN NULL
114
+ ELSE nears."raw"
115
+ END) as qc
116
+ FROM nears
117
+ """
118
+
119
+ return sql_new_qc
120
+
121
+ @db_engine.deco_update_privilege
122
+ def _sql_fillup_extra_dict(self, **kwargs):
123
+ # additional parts to calculate the filling of min and max
124
+ fillup_extra_dict = super()._sql_fillup_extra_dict(**kwargs)
125
+ sql_array_init = "ARRAY[{0}]".format(
126
+ ", ".join(["NULL::smallint"] * self._filled_by_n))
127
+ fillup_extra_dict.update({
128
+ "extra_new_temp_cols": "raw_min AS filled_min, raw_max AS filled_max," +
129
+ f"{sql_array_init} AS nb_min, {sql_array_init} AS nb_max,",
130
+ "extra_cols_fillup_calc": "filled_min=round(nb.raw_min + %3$s, 0)::int, " +
131
+ "filled_max=round(nb.raw_max + %3$s, 0)::int, ",
132
+ "extra_cols_fillup": "filled_min = new.filled_min, " +
133
+ "filled_max = new.filled_max, ",
134
+ "extra_fillup_where": ' OR ts."filled_min" IS DISTINCT FROM new."filled_min"' +
135
+ ' OR ts."filled_max" IS DISTINCT FROM new."filled_max"',
136
+ "extra_exec_cols": "nb_max[{i}]=round(nb.raw_max + %3$s, 0)::int,"+
137
+ "nb_min[{i}]=round(nb.raw_min + %3$s, 0)::int,",
138
+ "extra_after_loop_extra_col": """,
139
+ filled_min=(SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY v)
140
+ FROM unnest(nb_min) as T(v)),
141
+ filled_max=(SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY v)
142
+ FROM unnest(nb_max) as T(v))"""})
143
+ return fillup_extra_dict
144
+
145
+ def get_multi_annual_raster(self):
146
+ mas = super().get_multi_annual_raster()
147
+ if mas is not None:
148
+ return [ma / 10 for ma in mas]
149
+ else:
150
+ return None
151
+
152
+ def get_adj(self, **kwargs):
153
+ main_df, adj_df, ma, main_df_tr = super().get_adj(**kwargs)
154
+
155
+ # calculate the yearly
156
+ main_df_y = main_df.groupby(main_df_tr.index.year)\
157
+ .mean().mean()
158
+
159
+ adj_df["adj"] = (main_df + (ma[0] - main_df_y)).round(1)
160
+
161
+ return adj_df
162
+
163
+ def get_quotient(self, **kwargs):
164
+ raise NotImplementedError("The quotient is not yet implemented for temperature.")
@@ -0,0 +1,13 @@
1
+ """
2
+ This module has a class for every type of station. E.g. StationP (or StationP).
3
+ One object represents one Station with one parameter.
4
+ This object can get used to get the corresponding timeserie.
5
+ There is also a StationGroup class that groups the three parameters precipitation, temperature and evapotranspiration together for one station.
6
+ """
7
+ from .StationET import StationET
8
+ from .StationP import StationP
9
+ from .StationPD import StationPD
10
+ from .StationT import StationT
11
+ from .GroupStation import GroupStation
12
+
13
+ __all__ = ["StationET", "StationP", "StationPD", "StationT", "GroupStation"]
@@ -0,0 +1,21 @@
1
+ # Variables
2
+ # ---------
3
+ # possible aggregation periods from small to big
4
+ AGG_TO = {
5
+ None: {
6
+ "split":{"p": 5, "t":3, "et": 3}},
7
+ "10 min": {
8
+ "split":{"p": 5, "t":3, "et": 3}},
9
+ "hour": {
10
+ "split":{"p": 4, "t":3, "et": 3}},
11
+ "day": {
12
+ "split":{"p": 3, "t":3, "et": 3}},
13
+ "month": {
14
+ "split":{"p": 2, "t":2, "et": 2}},
15
+ "year": {
16
+ "split":{"p": 1, "t":1, "et": 1}},
17
+ "decade": {
18
+ "split":{"p": 1, "t":1, "et": 1}}
19
+ }
20
+
21
+ __all__ = ["AGG_TO"]