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
@@ -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"]