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.
- 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
 
| 
         @@ -0,0 +1,285 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """
         
     | 
| 
      
 2 
     | 
    
         
            +
            Some utilities functions to download the needed data for the module to work.
         
     | 
| 
      
 3 
     | 
    
         
            +
            """
         
     | 
| 
      
 4 
     | 
    
         
            +
            import requests
         
     | 
| 
      
 5 
     | 
    
         
            +
            from pathlib import Path
         
     | 
| 
      
 6 
     | 
    
         
            +
            from distutils.util import strtobool
         
     | 
| 
      
 7 
     | 
    
         
            +
            import hashlib
         
     | 
| 
      
 8 
     | 
    
         
            +
            import progressbar as pb
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            from ..config import config
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            def download_ma_rasters(which="all", overwrite=None, update_user_config=False):
         
     | 
| 
      
 13 
     | 
    
         
            +
                """Get the multi annual rasters on which bases the regionalisation is done.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                The refined multi annual datasets, that are downloaded are published on Zenodo [1]_
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                References
         
     | 
| 
      
 18 
     | 
    
         
            +
                ----------
         
     | 
| 
      
 19 
     | 
    
         
            +
                .. [1] Schmit, M.; Weiler, M. (2023). German weather services (DWD) multi annual meteorological rasters for the climate period 1991-2020 refined to 25m grid (1.0.0) [Data set]. Zenodo. https://doi.org/10.5281/zenodo.10066045
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                Parameters
         
     | 
| 
      
 22 
     | 
    
         
            +
                ----------
         
     | 
| 
      
 23 
     | 
    
         
            +
                which : str or [str], optional
         
     | 
| 
      
 24 
     | 
    
         
            +
                    Which raster to download.
         
     | 
| 
      
 25 
     | 
    
         
            +
                    Options are "dwd", "hyras", "regnie" and "all".
         
     | 
| 
      
 26 
     | 
    
         
            +
                    The default is "all".
         
     | 
| 
      
 27 
     | 
    
         
            +
                overwrite : bool, optional
         
     | 
| 
      
 28 
     | 
    
         
            +
                    Should the multi annual rasters be downloaded even if they already exist?
         
     | 
| 
      
 29 
     | 
    
         
            +
                    If None the user will be asked.
         
     | 
| 
      
 30 
     | 
    
         
            +
                    The default is None.
         
     | 
| 
      
 31 
     | 
    
         
            +
                update_user_config : bool, optional
         
     | 
| 
      
 32 
     | 
    
         
            +
                    Should the downloaded rasters be set as the regionalisation rasters in the user configuration file?
         
     | 
| 
      
 33 
     | 
    
         
            +
                    The default is False.
         
     | 
| 
      
 34 
     | 
    
         
            +
                """
         
     | 
| 
      
 35 
     | 
    
         
            +
                # DOI of the multi annual dataset
         
     | 
| 
      
 36 
     | 
    
         
            +
                DOI = "10.5281/zenodo.10066045"
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                # check which
         
     | 
| 
      
 39 
     | 
    
         
            +
                if isinstance(which, str):
         
     | 
| 
      
 40 
     | 
    
         
            +
                    which = [which]
         
     | 
| 
      
 41 
     | 
    
         
            +
                for w in which:
         
     | 
| 
      
 42 
     | 
    
         
            +
                    if w not in ["all", "dwd", "hyras", "regnie"]:
         
     | 
| 
      
 43 
     | 
    
         
            +
                        raise ValueError(
         
     | 
| 
      
 44 
     | 
    
         
            +
                            "which must be one of 'all', 'dwd', 'hyras' or 'regnie'.")
         
     | 
| 
      
 45 
     | 
    
         
            +
                    if w == "all":
         
     | 
| 
      
 46 
     | 
    
         
            +
                        which = ["dwd", "hyras", "regnie"]
         
     | 
| 
      
 47 
     | 
    
         
            +
                        break
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                # get zenodo record
         
     | 
| 
      
 50 
     | 
    
         
            +
                zenodo_id = requests.get(
         
     | 
| 
      
 51 
     | 
    
         
            +
                    f"https://doi.org/{DOI}"
         
     | 
| 
      
 52 
     | 
    
         
            +
                ).url.split("/")[-1]
         
     | 
| 
      
 53 
     | 
    
         
            +
                zenodo_rec = requests.get(
         
     | 
| 
      
 54 
     | 
    
         
            +
                    f"https://zenodo.org/api/records/{zenodo_id}"
         
     | 
| 
      
 55 
     | 
    
         
            +
                ).json()
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                # download files
         
     | 
| 
      
 58 
     | 
    
         
            +
                for file in zenodo_rec["files"]:
         
     | 
| 
      
 59 
     | 
    
         
            +
                    file_key = file["key"].lower().split("_")[0].split("-")[0]
         
     | 
| 
      
 60 
     | 
    
         
            +
                    if file_key in which:
         
     | 
| 
      
 61 
     | 
    
         
            +
                        # check if file is in config
         
     | 
| 
      
 62 
     | 
    
         
            +
                        if f"data:rasters:{file_key}" not in config:
         
     | 
| 
      
 63 
     | 
    
         
            +
                            print(f"Skipping {file_key} as it is not in your configuration.\nPlease add a section 'data:rasters:{file_key}' to your configuration file.")
         
     | 
| 
      
 64 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                        # check if file already exists
         
     | 
| 
      
 67 
     | 
    
         
            +
                        file_path = Path(config.get(f"data:rasters:{file_key}", "file"))
         
     | 
| 
      
 68 
     | 
    
         
            +
                        if file_path.exists():
         
     | 
| 
      
 69 
     | 
    
         
            +
                            skip = False
         
     | 
| 
      
 70 
     | 
    
         
            +
                            if overwrite is False:
         
     | 
| 
      
 71 
     | 
    
         
            +
                                skip = True
         
     | 
| 
      
 72 
     | 
    
         
            +
                            elif overwrite is None:
         
     | 
| 
      
 73 
     | 
    
         
            +
                                skip = not strtobool(input(
         
     | 
| 
      
 74 
     | 
    
         
            +
                                    f"{file_key} already exists at {file_path}.\n"+
         
     | 
| 
      
 75 
     | 
    
         
            +
                                    "Do you want to overwrite it? [y/n] "))
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                            if skip:
         
     | 
| 
      
 78 
     | 
    
         
            +
                                print(f"Skipping {file_key} as overwriting is not allowed.")
         
     | 
| 
      
 79 
     | 
    
         
            +
                                continue
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                        # check if the directory exists
         
     | 
| 
      
 82 
     | 
    
         
            +
                        if not file_path.parent.exists():
         
     | 
| 
      
 83 
     | 
    
         
            +
                            if strtobool(input(
         
     | 
| 
      
 84 
     | 
    
         
            +
                                    f"The directory \"{file_path.parent}\" does not exist.\n"+
         
     | 
| 
      
 85 
     | 
    
         
            +
                                    "Do you want to create it? [y/n] ")):
         
     | 
| 
      
 86 
     | 
    
         
            +
                                file_path.parent.mkdir(parents=True)
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                        # download file
         
     | 
| 
      
 89 
     | 
    
         
            +
                        r = requests.get(file["links"]["self"], stream=True)
         
     | 
| 
      
 90 
     | 
    
         
            +
                        if r.status_code != 200:
         
     | 
| 
      
 91 
     | 
    
         
            +
                            r.raise_for_status()  # Will only raise for 4xx codes, so...
         
     | 
| 
      
 92 
     | 
    
         
            +
                            raise RuntimeError(
         
     | 
| 
      
 93 
     | 
    
         
            +
                                f'Request to {file["links"]["self"]} returned status code {r.status_code}')
         
     | 
| 
      
 94 
     | 
    
         
            +
                        block_size = 1024
         
     | 
| 
      
 95 
     | 
    
         
            +
                        file_size = int(r.headers.get('Content-Length', 0))
         
     | 
| 
      
 96 
     | 
    
         
            +
                        pbar = pb.ProgressBar(
         
     | 
| 
      
 97 
     | 
    
         
            +
                            max_value=file_size,
         
     | 
| 
      
 98 
     | 
    
         
            +
                            prefix=f"downloading {file_key}: ",
         
     | 
| 
      
 99 
     | 
    
         
            +
                            widgets=[ " ",
         
     | 
| 
      
 100 
     | 
    
         
            +
                                pb.widgets.DataSize(),
         
     | 
| 
      
 101 
     | 
    
         
            +
                                "/",
         
     | 
| 
      
 102 
     | 
    
         
            +
                                pb.widgets.DataSize("max_value"),
         
     | 
| 
      
 103 
     | 
    
         
            +
                                pb.widgets.AdaptiveTransferSpeed(
         
     | 
| 
      
 104 
     | 
    
         
            +
                                    format='(%(scaled)5.1f %(prefix)s%(unit)-s/s) '),
         
     | 
| 
      
 105 
     | 
    
         
            +
                                pb.widgets.Bar(), " ",
         
     | 
| 
      
 106 
     | 
    
         
            +
                                pb.widgets.Percentage(),
         
     | 
| 
      
 107 
     | 
    
         
            +
                                pb.widgets.ETA()],
         
     | 
| 
      
 108 
     | 
    
         
            +
                            line_breaks=False,
         
     | 
| 
      
 109 
     | 
    
         
            +
                            redirect_stdout=True
         
     | 
| 
      
 110 
     | 
    
         
            +
                            ).start()
         
     | 
| 
      
 111 
     | 
    
         
            +
                        md5 = hashlib.md5()
         
     | 
| 
      
 112 
     | 
    
         
            +
                        with open(file_path, "wb+") as f:
         
     | 
| 
      
 113 
     | 
    
         
            +
                            for i, chunk in enumerate(r.iter_content(block_size)):
         
     | 
| 
      
 114 
     | 
    
         
            +
                                f.write(chunk)
         
     | 
| 
      
 115 
     | 
    
         
            +
                                md5.update(chunk)
         
     | 
| 
      
 116 
     | 
    
         
            +
                                pbar.update(i*block_size)
         
     | 
| 
      
 117 
     | 
    
         
            +
                        pbar.finish()
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                        # check checksum
         
     | 
| 
      
 120 
     | 
    
         
            +
                        if md5.hexdigest() != file["checksum"].replace("md5:", ""):
         
     | 
| 
      
 121 
     | 
    
         
            +
                            raise ValueError(
         
     | 
| 
      
 122 
     | 
    
         
            +
                                f"Checksum of {file_key} doesn't match. File might be corrupted.")
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                        # update user config
         
     | 
| 
      
 125 
     | 
    
         
            +
                        if update_user_config:
         
     | 
| 
      
 126 
     | 
    
         
            +
                            if config.has_user_config:
         
     | 
| 
      
 127 
     | 
    
         
            +
                                config.update_user_config(f"data:rasters:{file_key}", "file", str(file_path))
         
     | 
| 
      
 128 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 129 
     | 
    
         
            +
                                print(f"No user configuration file found, therefor the raster '{file_key}' is not set in the user configuration file.")
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
            def download_dem(overwrite=None, extent=(5.3, 46.1, 15.6, 55.4), update_user_config=False):
         
     | 
| 
      
 133 
     | 
    
         
            +
                """Download the newest DEM data from the Copernicus Sentinel dataset.
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                Only the GLO-30 DEM, which has a 30m resolution, is downloaded as it is freely available.
         
     | 
| 
      
 136 
     | 
    
         
            +
                If you register as a scientific researcher also the EEA-10, with 10 m resolution, is available.
         
     | 
| 
      
 137 
     | 
    
         
            +
                You will have to download the data yourself and define it in the configuration file.
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                After downloading the data, the files are merged and saved as a single tif file in the data directory in a subfolder called 'DEM'.
         
     | 
| 
      
 140 
     | 
    
         
            +
                To use the DEM data in the WeatherDB, you will have to define the path to the tif file in the configuration file.
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                Source:
         
     | 
| 
      
 143 
     | 
    
         
            +
                Copernicus DEM - Global and European Digital Elevation Model. Digital Surface Model (DSM) provided in 3 different resolutions (90m, 30m, 10m) with varying geographical extent (EEA: European and GLO: global) and varying format (INSPIRE, DGED, DTED). DOI:10.5270/ESA-c5d3d65.
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                Parameters
         
     | 
| 
      
 146 
     | 
    
         
            +
                ----------
         
     | 
| 
      
 147 
     | 
    
         
            +
                overwrite : bool, optional
         
     | 
| 
      
 148 
     | 
    
         
            +
                    Should the DEM data be downloaded even if it already exists?
         
     | 
| 
      
 149 
     | 
    
         
            +
                    If None the user will be asked.
         
     | 
| 
      
 150 
     | 
    
         
            +
                    The default is None.
         
     | 
| 
      
 151 
     | 
    
         
            +
                extent : tuple, optional
         
     | 
| 
      
 152 
     | 
    
         
            +
                    The extent in WGS84 of the DEM data to download.
         
     | 
| 
      
 153 
     | 
    
         
            +
                    The default is the boundary of germany + ~40km = (5.3, 46.1, 15.6, 55.4).
         
     | 
| 
      
 154 
     | 
    
         
            +
                update_user_config : bool, optional
         
     | 
| 
      
 155 
     | 
    
         
            +
                    Should the downloaded DEM be set as the used DEM in the user configuration file?
         
     | 
| 
      
 156 
     | 
    
         
            +
                    The default is False.
         
     | 
| 
      
 157 
     | 
    
         
            +
                """
         
     | 
| 
      
 158 
     | 
    
         
            +
                # import necessary modules
         
     | 
| 
      
 159 
     | 
    
         
            +
                import rasterio as rio
         
     | 
| 
      
 160 
     | 
    
         
            +
                from rasterio.merge import merge
         
     | 
| 
      
 161 
     | 
    
         
            +
                import tarfile
         
     | 
| 
      
 162 
     | 
    
         
            +
                import shutil
         
     | 
| 
      
 163 
     | 
    
         
            +
                from tempfile import TemporaryDirectory
         
     | 
| 
      
 164 
     | 
    
         
            +
                import re
         
     | 
| 
      
 165 
     | 
    
         
            +
                import json
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                # get dem_dir
         
     | 
| 
      
 168 
     | 
    
         
            +
                base_dir = Path(config.get("data", "base_dir"))
         
     | 
| 
      
 169 
     | 
    
         
            +
                dem_dir = base_dir / "DEM"
         
     | 
| 
      
 170 
     | 
    
         
            +
                dem_dir.mkdir(parents=True, exist_ok=True)
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                # get available datasets
         
     | 
| 
      
 173 
     | 
    
         
            +
                prism_url = "https://prism-dem-open.copernicus.eu/pd-desk-open-access/publicDemURLs"
         
     | 
| 
      
 174 
     | 
    
         
            +
                avl_ds_req = json.loads(
         
     | 
| 
      
 175 
     | 
    
         
            +
                    requests.get(
         
     | 
| 
      
 176 
     | 
    
         
            +
                        prism_url,
         
     | 
| 
      
 177 
     | 
    
         
            +
                        headers={"Accept": "json"}
         
     | 
| 
      
 178 
     | 
    
         
            +
                        ).text
         
     | 
| 
      
 179 
     | 
    
         
            +
                )
         
     | 
| 
      
 180 
     | 
    
         
            +
                avl_ds = [{
         
     | 
| 
      
 181 
     | 
    
         
            +
                    "id": e["datasetId"],
         
     | 
| 
      
 182 
     | 
    
         
            +
                    "year": int(e["datasetId"].split("/")[1].split("_")[0]),
         
     | 
| 
      
 183 
     | 
    
         
            +
                    "year_part": int(e["datasetId"].split("/")[1].split("_")[1]),
         
     | 
| 
      
 184 
     | 
    
         
            +
                    "resolution": int(e["datasetId"].split("-")[2]),
         
     | 
| 
      
 185 
     | 
    
         
            +
                    } for e in avl_ds_req]
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                # select newest and highest resolution dataset
         
     | 
| 
      
 188 
     | 
    
         
            +
                ds_id = sorted(
         
     | 
| 
      
 189 
     | 
    
         
            +
                    avl_ds,
         
     | 
| 
      
 190 
     | 
    
         
            +
                    key=lambda x: (-x["resolution"], x["year"], x["year_part"])
         
     | 
| 
      
 191 
     | 
    
         
            +
                    )[-1]["id"]
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                # check if dataset already exists
         
     | 
| 
      
 194 
     | 
    
         
            +
                dem_file = dem_dir / f'{ds_id.replace("/", "__")}.tif'
         
     | 
| 
      
 195 
     | 
    
         
            +
                if dem_file.exists():
         
     | 
| 
      
 196 
     | 
    
         
            +
                    print(f"The DEM data already exists at {dem_file}.")
         
     | 
| 
      
 197 
     | 
    
         
            +
                    if overwrite is None:
         
     | 
| 
      
 198 
     | 
    
         
            +
                        overwrite = strtobool(input("Do you want to overwrite it? [y/n] "))
         
     | 
| 
      
 199 
     | 
    
         
            +
                    if not overwrite:
         
     | 
| 
      
 200 
     | 
    
         
            +
                        print("Skipping, because overwritting was turned of.")
         
     | 
| 
      
 201 
     | 
    
         
            +
                        return
         
     | 
| 
      
 202 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 203 
     | 
    
         
            +
                        print("Overwriting the dataset.")
         
     | 
| 
      
 204 
     | 
    
         
            +
                dem_dir.mkdir(exist_ok=True)
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
                # selecting DEM tiles
         
     | 
| 
      
 207 
     | 
    
         
            +
                print(f"getting available tiles for Copernicus dataset '{ds_id}'")
         
     | 
| 
      
 208 
     | 
    
         
            +
                ds_files_req = json.loads(
         
     | 
| 
      
 209 
     | 
    
         
            +
                    requests.get(
         
     | 
| 
      
 210 
     | 
    
         
            +
                        f"{prism_url}/{ds_id.replace('/', '__')}",
         
     | 
| 
      
 211 
     | 
    
         
            +
                        headers={"Accept": "json"}
         
     | 
| 
      
 212 
     | 
    
         
            +
                        ).text
         
     | 
| 
      
 213 
     | 
    
         
            +
                )
         
     | 
| 
      
 214 
     | 
    
         
            +
                re_comp = re.compile(r".*/Copernicus_DSM_\d{2}_N\d*_\d{2}_E\d*.*")
         
     | 
| 
      
 215 
     | 
    
         
            +
                ds_files_all = [
         
     | 
| 
      
 216 
     | 
    
         
            +
                    {"lat": int(Path(f["nativeDemUrl"]).stem.split("_")[3][1:]),
         
     | 
| 
      
 217 
     | 
    
         
            +
                     "long": int(Path(f["nativeDemUrl"]).stem.split("_")[5][1:]),
         
     | 
| 
      
 218 
     | 
    
         
            +
                     **f} for f in ds_files_req if re_comp.match(f["nativeDemUrl"])]
         
     | 
| 
      
 219 
     | 
    
         
            +
                res_deg = 1
         
     | 
| 
      
 220 
     | 
    
         
            +
                ds_files = list(filter(
         
     | 
| 
      
 221 
     | 
    
         
            +
                    lambda x: (
         
     | 
| 
      
 222 
     | 
    
         
            +
                        (extent[0] - res_deg) < x["long"] < extent[2] and
         
     | 
| 
      
 223 
     | 
    
         
            +
                        (extent[1] - res_deg) < x["lat"] < extent[3]
         
     | 
| 
      
 224 
     | 
    
         
            +
                        ),
         
     | 
| 
      
 225 
     | 
    
         
            +
                    ds_files_all))
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
                # download DEM tiles
         
     | 
| 
      
 228 
     | 
    
         
            +
                print("downloading tiles")
         
     | 
| 
      
 229 
     | 
    
         
            +
                with TemporaryDirectory() as tmp_dir:
         
     | 
| 
      
 230 
     | 
    
         
            +
                    tmp_dir_fp = Path(tmp_dir)
         
     | 
| 
      
 231 
     | 
    
         
            +
                    for f in pb.progressbar(ds_files):
         
     | 
| 
      
 232 
     | 
    
         
            +
                        with open(tmp_dir_fp / Path(f["nativeDemUrl"]).name, "wb") as d:
         
     | 
| 
      
 233 
     | 
    
         
            +
                            d.write(requests.get(f["nativeDemUrl"]).content)
         
     | 
| 
      
 234 
     | 
    
         
            +
                    print("downloaded all files")
         
     | 
| 
      
 235 
     | 
    
         
            +
             
     | 
| 
      
 236 
     | 
    
         
            +
                    # extracting tifs from tars
         
     | 
| 
      
 237 
     | 
    
         
            +
                    for i, f in pb.progressbar(list(enumerate(tmp_dir_fp.glob("*.tar")))):
         
     | 
| 
      
 238 
     | 
    
         
            +
                        with tarfile.open(f) as t:
         
     | 
| 
      
 239 
     | 
    
         
            +
                            # extract dem tif
         
     | 
| 
      
 240 
     | 
    
         
            +
                            re_comp = re.compile(r"^.*\/DEM\/.*\.tif$")
         
     | 
| 
      
 241 
     | 
    
         
            +
                            name = list(filter(re_comp.match, t.getnames()))[0]
         
     | 
| 
      
 242 
     | 
    
         
            +
                            with open(tmp_dir_fp/f"{name.split('/')[-1]}", "wb") as d:
         
     | 
| 
      
 243 
     | 
    
         
            +
                                d.write(t.extractfile(name).read())
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
      
 245 
     | 
    
         
            +
                            # extract info contract
         
     | 
| 
      
 246 
     | 
    
         
            +
                            if i==0:
         
     | 
| 
      
 247 
     | 
    
         
            +
                                re_comp = re.compile(r"^.*\/INFO\/.*\.pdf$")
         
     | 
| 
      
 248 
     | 
    
         
            +
                                name = list(filter(re_comp.match, t.getnames()))[0]
         
     | 
| 
      
 249 
     | 
    
         
            +
                                with open(tmp_dir_fp/f"{name.split('/')[-1]}", "wb") as d:
         
     | 
| 
      
 250 
     | 
    
         
            +
                                    d.write(t.extractfile(name).read())
         
     | 
| 
      
 251 
     | 
    
         
            +
             
     | 
| 
      
 252 
     | 
    
         
            +
                        # remove tar
         
     | 
| 
      
 253 
     | 
    
         
            +
                        f.unlink()
         
     | 
| 
      
 254 
     | 
    
         
            +
             
     | 
| 
      
 255 
     | 
    
         
            +
                    # merge files
         
     | 
| 
      
 256 
     | 
    
         
            +
                    srcs = [rio.open(f) for f in tmp_dir_fp.glob("*.tif")]
         
     | 
| 
      
 257 
     | 
    
         
            +
                    dem_np, dem_tr = merge(srcs)
         
     | 
| 
      
 258 
     | 
    
         
            +
                    dem_meta = srcs[0].meta.copy()
         
     | 
| 
      
 259 
     | 
    
         
            +
                    dem_meta.update({
         
     | 
| 
      
 260 
     | 
    
         
            +
                        "driver": "GTiff",
         
     | 
| 
      
 261 
     | 
    
         
            +
                        "height": dem_np.shape[1],
         
     | 
| 
      
 262 
     | 
    
         
            +
                        "width": dem_np.shape[2],
         
     | 
| 
      
 263 
     | 
    
         
            +
                        "transform": dem_tr
         
     | 
| 
      
 264 
     | 
    
         
            +
                    })
         
     | 
| 
      
 265 
     | 
    
         
            +
                    with rio.open(dem_file, "w", **dem_meta) as d:
         
     | 
| 
      
 266 
     | 
    
         
            +
                        d.write(dem_np)
         
     | 
| 
      
 267 
     | 
    
         
            +
             
     | 
| 
      
 268 
     | 
    
         
            +
                    # copy info contract
         
     | 
| 
      
 269 
     | 
    
         
            +
                    tmp_eula_fp = next(tmp_dir_fp.glob("*.pdf"))
         
     | 
| 
      
 270 
     | 
    
         
            +
                    shutil.copyfile(
         
     | 
| 
      
 271 
     | 
    
         
            +
                        tmp_eula_fp,
         
     | 
| 
      
 272 
     | 
    
         
            +
                        dem_dir / tmp_eula_fp.name
         
     | 
| 
      
 273 
     | 
    
         
            +
                    )
         
     | 
| 
      
 274 
     | 
    
         
            +
             
     | 
| 
      
 275 
     | 
    
         
            +
                print(f"created DEM at '{dem_file}'.")
         
     | 
| 
      
 276 
     | 
    
         
            +
             
     | 
| 
      
 277 
     | 
    
         
            +
                # update user config
         
     | 
| 
      
 278 
     | 
    
         
            +
                if update_user_config:
         
     | 
| 
      
 279 
     | 
    
         
            +
                    if config.has_user_config:
         
     | 
| 
      
 280 
     | 
    
         
            +
                        config.update_user_config("data:rasters", "dems", str(dem_file))
         
     | 
| 
      
 281 
     | 
    
         
            +
                        return
         
     | 
| 
      
 282 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 283 
     | 
    
         
            +
                        print("No user configuration file found, therefor the DEM is not set in the user configuration file.")
         
     | 
| 
      
 284 
     | 
    
         
            +
             
     | 
| 
      
 285 
     | 
    
         
            +
                print("To use the DEM data in the WeatherDB, you will have to define the path to the tif file in the user configuration file.")
         
     | 
| 
         @@ -0,0 +1,126 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import logging
         
     | 
| 
      
 2 
     | 
    
         
            +
            from logging.handlers import TimedRotatingFileHandler
         
     | 
| 
      
 3 
     | 
    
         
            +
            import datetime
         
     | 
| 
      
 4 
     | 
    
         
            +
            from pathlib import Path
         
     | 
| 
      
 5 
     | 
    
         
            +
            import re
         
     | 
| 
      
 6 
     | 
    
         
            +
            import socket
         
     | 
| 
      
 7 
     | 
    
         
            +
            import os
         
     | 
| 
      
 8 
     | 
    
         
            +
            import gzip
         
     | 
| 
      
 9 
     | 
    
         
            +
            import shutil
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            from ..config import config
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            try:
         
     | 
| 
      
 14 
     | 
    
         
            +
                import coloredlogs
         
     | 
| 
      
 15 
     | 
    
         
            +
                cl_available = True
         
     | 
| 
      
 16 
     | 
    
         
            +
            except ImportError:
         
     | 
| 
      
 17 
     | 
    
         
            +
                cl_available = False
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            # set the log
         
     | 
| 
      
 20 
     | 
    
         
            +
            #############
         
     | 
| 
      
 21 
     | 
    
         
            +
            log = logging.getLogger(__name__.split(".")[0])
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            def _get_log_dir ():
         
     | 
| 
      
 24 
     | 
    
         
            +
                return Path(config.get("logging", "directory"))
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            def remove_old_logs(max_days=14):
         
     | 
| 
      
 27 
     | 
    
         
            +
                # remove old logs
         
     | 
| 
      
 28 
     | 
    
         
            +
                log_dir = _get_log_dir()
         
     | 
| 
      
 29 
     | 
    
         
            +
                log_date_min = datetime.datetime.now() - datetime.timedelta(days=max_days)
         
     | 
| 
      
 30 
     | 
    
         
            +
                for log_file in [
         
     | 
| 
      
 31 
     | 
    
         
            +
                        file for file in log_dir.glob("*.log.*")
         
     | 
| 
      
 32 
     | 
    
         
            +
                            if re.match(r".*\.log\.\d{4}-\d{2}-\d{2}$", file.name)]:
         
     | 
| 
      
 33 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 34 
     | 
    
         
            +
                        file_date = datetime.datetime.strptime(log_file.name.split(".")[-1], "%Y-%m-%d")
         
     | 
| 
      
 35 
     | 
    
         
            +
                        if file_date < log_date_min:
         
     | 
| 
      
 36 
     | 
    
         
            +
                            log_file.unlink()
         
     | 
| 
      
 37 
     | 
    
         
            +
                    except:
         
     | 
| 
      
 38 
     | 
    
         
            +
                        pass
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            def setup_logging_handlers():
         
     | 
| 
      
 41 
     | 
    
         
            +
                """Setup the logging handlers depending on the configuration.
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                Raises
         
     | 
| 
      
 44 
     | 
    
         
            +
                ------
         
     | 
| 
      
 45 
     | 
    
         
            +
                ValueError
         
     | 
| 
      
 46 
     | 
    
         
            +
                    If the handler type is not known.
         
     | 
| 
      
 47 
     | 
    
         
            +
                """
         
     | 
| 
      
 48 
     | 
    
         
            +
                # get log dir
         
     | 
| 
      
 49 
     | 
    
         
            +
                log_dir = _get_log_dir()
         
     | 
| 
      
 50 
     | 
    
         
            +
                if not log_dir.is_dir(): log_dir.mkdir()
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                # add filehandler if necessary
         
     | 
| 
      
 53 
     | 
    
         
            +
                log.setLevel(config.get("logging", "level", fallback=logging.DEBUG))
         
     | 
| 
      
 54 
     | 
    
         
            +
                handler_names = [h.get_name() for h in log.handlers]
         
     | 
| 
      
 55 
     | 
    
         
            +
                format = config.get(
         
     | 
| 
      
 56 
     | 
    
         
            +
                    "logging",
         
     | 
| 
      
 57 
     | 
    
         
            +
                    "format",
         
     | 
| 
      
 58 
     | 
    
         
            +
                    raw=True,
         
     | 
| 
      
 59 
     | 
    
         
            +
                    fallback="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
         
     | 
| 
      
 60 
     | 
    
         
            +
                level = config.get("logging", "level", fallback=logging.DEBUG)
         
     | 
| 
      
 61 
     | 
    
         
            +
                for handler_type in config.get_list("logging", "handlers"):
         
     | 
| 
      
 62 
     | 
    
         
            +
                    handler_name = f"weatherDB_config:{handler_type}"
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    # check if coloredlogs is available
         
     | 
| 
      
 65 
     | 
    
         
            +
                    if cl_available and handler_type == "console":
         
     | 
| 
      
 66 
     | 
    
         
            +
                        coloredlogs.install(level=level, fmt=format, logger=log)
         
     | 
| 
      
 67 
     | 
    
         
            +
                        log.debug("Using coloredlogs")
         
     | 
| 
      
 68 
     | 
    
         
            +
                        continue
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                    # get log file name
         
     | 
| 
      
 71 
     | 
    
         
            +
                    if handler_type == "file":
         
     | 
| 
      
 72 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 73 
     | 
    
         
            +
                            user = os.getlogin()
         
     | 
| 
      
 74 
     | 
    
         
            +
                        except:
         
     | 
| 
      
 75 
     | 
    
         
            +
                            user = "anonym"
         
     | 
| 
      
 76 
     | 
    
         
            +
                        host = socket.gethostname().replace(".","_")
         
     | 
| 
      
 77 
     | 
    
         
            +
                        log_file = log_dir.joinpath(
         
     | 
| 
      
 78 
     | 
    
         
            +
                            config.get("logging", "file", fallback="weatherDB_{user}_{host}.log")\
         
     | 
| 
      
 79 
     | 
    
         
            +
                                .format(user=user, host=host))
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                    # get or create handler
         
     | 
| 
      
 82 
     | 
    
         
            +
                    if handler_name not in handler_names:
         
     | 
| 
      
 83 
     | 
    
         
            +
                        if handler_type == "console":
         
     | 
| 
      
 84 
     | 
    
         
            +
                            handler = logging.StreamHandler()
         
     | 
| 
      
 85 
     | 
    
         
            +
                        elif handler_type == "file":
         
     | 
| 
      
 86 
     | 
    
         
            +
                            handler = TimedRotatingFileHandler(
         
     | 
| 
      
 87 
     | 
    
         
            +
                                log_file,
         
     | 
| 
      
 88 
     | 
    
         
            +
                                when="midnight",
         
     | 
| 
      
 89 
     | 
    
         
            +
                                encoding="utf-8")
         
     | 
| 
      
 90 
     | 
    
         
            +
                            if config.getboolean("logging", "compression", fallback=True):
         
     | 
| 
      
 91 
     | 
    
         
            +
                                def namer(name):
         
     | 
| 
      
 92 
     | 
    
         
            +
                                    return name + ".gz"
         
     | 
| 
      
 93 
     | 
    
         
            +
                                def rotator(source, dest):
         
     | 
| 
      
 94 
     | 
    
         
            +
                                    with open(source, 'rb') as f_in:
         
     | 
| 
      
 95 
     | 
    
         
            +
                                        with gzip.open(dest, 'wb') as f_out:
         
     | 
| 
      
 96 
     | 
    
         
            +
                                            shutil.copyfileobj(f_in, f_out)
         
     | 
| 
      
 97 
     | 
    
         
            +
                                    os.remove(source)
         
     | 
| 
      
 98 
     | 
    
         
            +
                                handler.namer = namer
         
     | 
| 
      
 99 
     | 
    
         
            +
                                handler.rotator = rotator
         
     | 
| 
      
 100 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 101 
     | 
    
         
            +
                            raise ValueError(f"Handler '{handler_type}' not known.")
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                        handler.set_name(handler_name)
         
     | 
| 
      
 104 
     | 
    
         
            +
                        log.addHandler(handler)
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                    elif handler_name in handler_names:
         
     | 
| 
      
 107 
     | 
    
         
            +
                        handler = log.handlers[handler_names.index(handler_name)]
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                        # check if file path has changed
         
     | 
| 
      
 110 
     | 
    
         
            +
                        if handler_type == "file" and handler.baseFilename != str(log_file):
         
     | 
| 
      
 111 
     | 
    
         
            +
                            log.removeHandler(handler)
         
     | 
| 
      
 112 
     | 
    
         
            +
                            handler.close()
         
     | 
| 
      
 113 
     | 
    
         
            +
                            handler = TimedRotatingFileHandler(
         
     | 
| 
      
 114 
     | 
    
         
            +
                                log_file,
         
     | 
| 
      
 115 
     | 
    
         
            +
                                when="midnight",
         
     | 
| 
      
 116 
     | 
    
         
            +
                                encoding="utf-8")
         
     | 
| 
      
 117 
     | 
    
         
            +
                            handler.set_name(handler_name)
         
     | 
| 
      
 118 
     | 
    
         
            +
                            log.addHandler(handler)
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                    # set formatter and level
         
     | 
| 
      
 121 
     | 
    
         
            +
                    handler.setFormatter(
         
     | 
| 
      
 122 
     | 
    
         
            +
                        logging.Formatter(format))
         
     | 
| 
      
 123 
     | 
    
         
            +
                    handler.setLevel(level)
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
            # add config listener
         
     | 
| 
      
 126 
     | 
    
         
            +
            config.add_listener("logging", None, setup_logging_handlers)
         
     |