diff --git a/setup.cfg b/setup.cfg index 29b29d6b814840c67fe94fa674f527b8a632d88d..95d249b4b76bc9eb2bf866d97017bd97f8675389 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = u4py -version = 0.0.10 +version = 0.0.11b [options] packages=u4py, u4py.addons, u4py.analysis, u4py.plotting, u4py.utils, u4py.io @@ -8,6 +8,7 @@ install_requires= numpy<2.0.0 # required for GDAL bmi_arcgis_restapi contextily + python_docx ffmpeg_python fiona ;platform_system=="Linux" fiona @ https://github.com/cgohlke/geospatial-wheels/releases/download/v2024.2.18/fiona-1.9.5-cp39-cp39-win_amd64.whl ;python_version=='3.9.*' and platform_system=="Windows" @@ -19,22 +20,23 @@ install_requires= GDAL @ https://github.com/cgohlke/geospatial-wheels/releases/download/v2024.2.18/GDAL-3.8.4-cp310-cp310-win_amd64.whl ;python_version=='3.10.*' and platform_system=="Windows" GDAL @ https://github.com/cgohlke/geospatial-wheels/releases/download/v2024.2.18/GDAL-3.8.4-cp311-cp311-win_amd64.whl ;python_version=='3.11.*' and platform_system=="Windows" GDAL @ https://github.com/cgohlke/geospatial-wheels/releases/download/v2024.2.18/GDAL-3.8.4-cp312-cp312-win_amd64.whl ;python_version=='3.12.*' and platform_system=="Windows" + geographiclib geopandas geopy humanize - ipython - mahotas + mapclassify matplotlib openpyxl osmnx - OWSlib + OWSLib packaging - pandas pathvalidate Pillow pycwt pyproj + python_docx rasterio + Requests scipy setuptools Shapely diff --git a/u4py/addons/__init__.py b/u4py/addons/__init__.py index e2bd906a723d427359f27e6419c8f20c803cf8f6..e919bb62f000992e5d89838c91d07b703bfa9555 100644 --- a/u4py/addons/__init__.py +++ b/u4py/addons/__init__.py @@ -17,6 +17,7 @@ from . import ( gnss, groundwater, rivers, + seismo, web_services, ) from .adatools import * @@ -27,4 +28,5 @@ from .gma import * from .gnss import * from .groundwater import * from .rivers import * +from .seismo import * from .web_services import * diff --git a/u4py/io/__init__.py b/u4py/io/__init__.py index 2bd4cb9129243dd5daaad8fdbbf62e3bfaea11de..23554dd5839d161dfda3ca4b7c65daddaedfccb3 100644 --- a/u4py/io/__init__.py +++ b/u4py/io/__init__.py @@ -3,10 +3,23 @@ Module containing file operations for specific file types and general file dialogs. """ -from . import csvs, files, gpkg, psi, shp, sql, tex_report, tiff +from . import ( + csvs, + docx_report, + files, + gpkg, + human_text, + psi, + shp, + sql, + tex_report, + tiff, +) from .csvs import * +from .docx_report import * from .files import * from .gpkg import * +from .human_text import * from .psi import * from .shp import * from .sql import * diff --git a/u4py/plotting/axes.py b/u4py/plotting/axes.py index c62fc8c53b99c817d2a758b8997c90909ecf070e..87315430ab5100f5a8fe234236ef652044fb6266 100644 --- a/u4py/plotting/axes.py +++ b/u4py/plotting/axes.py @@ -23,6 +23,10 @@ from typing import Callable, Iterable, Tuple import contextily import geopandas as gp +import mapclassify # Keep for user defined chloropleths +import matplotlib.lines as mlines +import matplotlib.patches as mpatches +import matplotlib.path as mpath import matplotlib.pyplot as plt import matplotlib.ticker as mticker import numpy as np @@ -31,9 +35,8 @@ import rasterio.plot as rioplot import scipy.stats as spstats import shapely as shp import skimage.transform as sktransf -import urllib3.exceptions as urlexept from matplotlib.axes import Axes -from matplotlib.colors import hsv_to_rgb +from matplotlib.colors import ListedColormap, hsv_to_rgb from matplotlib.figure import Figure from pyproj import CRS from shapely import plotting as shplt @@ -1040,3 +1043,427 @@ def add_hlnug_shapes( linewidth=leg_dict["linewidth"][ii], add_points=False, ) + + +@_add_or_create +def add_fault_data( + fault_data: gp.GeoDataFrame, + leg_handles: list, + leg_labels: list, + ax: Axes, +) -> Tuple[list, list]: + """Adds faults from the HLNUG server to the axis + + :param fault_data: The fault data loaded from HLNUG + :type fault_data: gp.GeoDataFrame + :param leg_handles: The legend handles for symbology + :type leg_handles: list + :param leg_labels: The legend labels + :type leg_labels: list + :param ax: The axis to add the data to + :type ax: Axes + :return: A tuple with the updated legend handles and labels + :rtype: Tuple[list, list] + """ + # Add known faults + faults = ~fault_data.BEZEICHNUNG.str.contains( + "vermutet", case=False, na=False + ) + if faults.any(): + fault_data[faults].plot(ax=ax, color="k") + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "Störung", + color="k", + linewidth=2, + linestyle="-", + ) + + # Add suspected faults + sus_faults = fault_data.BEZEICHNUNG.str.contains( + "vermutet", case=False, na=False + ) + if sus_faults.any(): + fault_data[sus_faults].plot(ax=ax, color="k", linestyle="--") + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "Störung, vermutet", + color="k", + linewidth=2, + linestyle="--", + ) + return leg_handles, leg_labels + + +@_add_or_create +def add_hydrological_points( + hydro_pts: gp.GeoDataFrame, + leg_handles: list, + leg_labels: list, + ax: Axes, +) -> Tuple[list, list]: + """Adds hydrogeological points of interest from the HLNUG server to the axis + + :param hydro_pts: The hydrogeological points of interest data loaded from HLNUG + :type hydro_pts: gp.GeoDataFrame + :param leg_handles: The legend handles for symbology + :type leg_handles: list + :param leg_labels: The legend labels + :type leg_labels: list + :param ax: The axis to add the data to + :type ax: Axes + :return: A tuple with the updated legend handles and labels + :rtype: Tuple[list, list] + """ + springs = hydro_pts.BEZEICHNUNG.str.contains("Quell", case=False, na=False) + if springs.any(): + hydro_pts[springs].plot( + ax=ax, + marker=mpath.Path.arc(0, 180), + color="#ff00cc", + markersize=50, + zorder=5, + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "Quellen und Quellgruppen", + marker=mpath.Path.arc(0, 180), + color="#ff00cc", + linestyle="None", + ) + ponors = hydro_pts.BEZEICHNUNG.str.contains( + "Schwind", case=False, na=False + ) + if ponors.any(): + hydro_pts[ponors].plot( + ax=ax, + marker="$\downarrow$", + color="#ff00cc", + markersize=50, + zorder=5, + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "Schwinde, Bachschwinde", + marker="$\downarrow$", + color="#ff00cc", + linestyle="None", + ) + + drains = hydro_pts.BEZEICHNUNG.str.contains("Drän", case=False, na=False) + if drains.any(): + hydro_pts[drains].plot( + ax=ax, + marker="s", + color="#ff00cc", + markersize=50, + zorder=5, + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "Drän", + marker="s", + color="#ff00cc", + linestyle="None", + ) + + drain_adits = hydro_pts.BEZEICHNUNG.str.contains( + "Wasserstollen", case=False, na=False + ) + if drain_adits.any(): + hydro_pts[drain_adits].plot( + ax=ax, + marker="s", + color="r", + markersize=50, + zorder=5, + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "Wasserstollen", + marker="s", + color="r", + linestyle="None", + ) + + wells = hydro_pts.BEZEICHNUNG.str.contains("Brunnen", case=False, na=False) + if wells.any(): + hydro_pts[wells].plot( + ax=ax, + marker="o", + edgecolor="r", + color="None", + markersize=50, + zorder=5, + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "Brunnen", + marker="o", + markeredgecolor="r", + markerfacecolor="None", + linestyle="None", + ) + + if not (springs | drains | ponors | drain_adits | wells).all(): + print("There are hydrogeological points without symbology.") + return leg_handles, leg_labels + + +@_add_or_create +def add_soggy_areas( + soggy_areas: gp.GeoDataFrame, + leg_handles: list, + leg_labels: list, + ax: Axes, +) -> Tuple[list, list]: + """Adds soggy areas from the HLNUG server to the axis + + :param soggy_areas: The soggy areas data loaded from HLNUG + :type soggy_areas: gp.GeoDataFrame + :param leg_handles: The legend handles for symbology + :type leg_handles: list + :param leg_labels: The legend labels + :type leg_labels: list + :param ax: The axis to add the data to + :type ax: Axes + :return: A tuple with the updated legend handles and labels + :rtype: Tuple[list, list] + """ + soggy_areas.plot(ax=ax, facecolor="b", edgecolor="None", alpha=0.5) + add_patch_legend_entry( + leg_handles, + leg_labels, + "Nassstellen", + facecolor="b", + edgecolor="None", + alpha=0.5, + ) + return leg_handles, leg_labels + + +@_add_or_create +def add_water_surface( + water_surface: gp.GeoDataFrame, + leg_handles: list, + leg_labels: list, + ax: Axes, +) -> Tuple[list, list]: + """Adds water surfaces from the HLNUG server to the axis + + :param water_surface: The water surfaces data loaded from HLNUG + :type water_surface: gp.GeoDataFrame + :param leg_handles: The legend handles for symbology + :type leg_handles: list + :param leg_labels: The legend labels + :type leg_labels: list + :param ax: The axis to add the data to + :type ax: Axes + :return: A tuple with the updated legend handles and labels + :rtype: Tuple[list, list] + """ + water_surface.plot(ax=ax, facecolor="c", edgecolor="None") + add_patch_legend_entry( + leg_handles, + leg_labels, + "Wasserflächen", + facecolor="c", + edgecolor="None", + ) + return leg_handles, leg_labels + + +@_add_or_create +def add_geo_pts( + geo_pts: gp.GeoDataFrame, + leg_handles: list, + leg_labels: list, + ax: Axes, +) -> Tuple[list, list]: + """Adds geological points of interest from the HLNUG server to the axis + + :param geo_pts: The geological points of interest data loaded from HLNUG + :type geo_pts: gp.GeoDataFrame + :param leg_handles: The legend handles for symbology + :type leg_handles: list + :param leg_labels: The legend labels + :type leg_labels: list + :param ax: The axis to add the data to + :type ax: Axes + :return: A tuple with the updated legend handles and labels + :rtype: Tuple[list, list] + """ + landslides = geo_pts.BEZEICHNUNG.str.contains( + "Rutschung", case=False, na=False + ) + if landslides.any(): + geo_pts[landslides].plot( + ax=ax, + marker="$\downarrow$", + edgecolor="r", + facecolor="None", + markersize=50, + zorder=5, + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "Rutschung", + marker="$\downarrow$", + markeredgecolor="r", + markerfacecolor="None", + linestyle="None", + ) + + mining = geo_pts.BEZEICHNUNG.str.contains("Bergbau", case=False, na=False) + if mining.any(): + geo_pts[mining].plot( + ax=ax, + marker="x", + edgecolor="k", + facecolor="None", + markersize=50, + zorder=5, + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "Bergbau", + marker="x", + markeredgecolor="k", + markerfacecolor="None", + linestyle="None", + ) + return leg_handles, leg_labels + + +@_add_or_create +def add_drill_sites( + drill_sites: gp.GeoDataFrame, + leg_handles: list, + leg_labels: list, + ax: Axes, +) -> Tuple[list, list]: + """Adds drill sites from the HLNUG server to the axis + + :param drill_sites: The drill sites data loaded from HLNUG + :type drill_sites: gp.GeoDataFrame + :param leg_handles: The legend handles for symbology + :type leg_handles: list + :param leg_labels: The legend labels + :type leg_labels: list + :param ax: The axis to add the data to + :type ax: Axes + :return: A tuple with the updated legend handles and labels + :rtype: Tuple[list, list] + """ + drill_sites.plot( + ax=ax, + column="ET", + scheme="UserDefined", + classification_kwds={"bins": [10, 20, 50, 100]}, + cmap=ListedColormap( + [ + [0.56078431, 0.76078431, 1.0, 1.0], + [0.47058824, 0.61176471, 1.0, 1.0], + [0.61960784, 0.50196078, 1.0, 1.0], + [0.81176471, 0.34117647, 0.96862745, 1.0], + [1.0, 0.0, 0.76078431, 1.0], + ] + ), + edgecolor="k", + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, leg_labels, "Bohrungen", linestyle="None" + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "< 10 m", + marker="o", + markerfacecolor=[0.56078431, 0.76078431, 1.0, 1.0], + markeredgecolor="k", + linestyle="None", + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "10 - 20 m", + marker="o", + markerfacecolor=[0.47058824, 0.61176471, 1.0, 1.0], + markeredgecolor="k", + linestyle="None", + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "20 - 50 m", + marker="o", + markerfacecolor=[0.61960784, 0.50196078, 1.0, 1.0], + markeredgecolor="k", + linestyle="None", + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "50 - 100 m", + marker="o", + markerfacecolor=[0.81176471, 0.34117647, 0.96862745, 1.0], + markeredgecolor="k", + linestyle="None", + ) + leg_handles, leg_labels = add_line_legend_entry( + leg_handles, + leg_labels, + "> 100 m", + marker="o", + markerfacecolor=[1.0, 0.0, 0.76078431, 1.0], + markeredgecolor="k", + linestyle="None", + ) + return leg_handles, leg_labels + + +def add_line_legend_entry( + leg_handles: list, leg_labels: list, label: str, **kwargs +) -> Tuple[list, list]: + """Helper function to add handles and labels of a line object to the list. + + :param leg_handles: The legend handles for symbology + :type leg_handles: list + :param leg_labels: The legend labels + :type leg_labels: list + :param **kwargs: Formatting arguments for the Line2D object + :type **kwargs: dict + :return: A tuple with the updated legend handles and labels + :rtype: Tuple[list, list] + """ + leg_handles.append(mlines.Line2D([], [], **kwargs)) + leg_labels.append(label) + return leg_handles, leg_labels + + +def add_patch_legend_entry( + leg_handles: list, leg_labels: list, label: str, **kwargs +) -> Tuple[list, list]: + """Helper function to add handles and labels of a patch object to the list. + + :param leg_handles: The legend handles for symbology + :type leg_handles: list + :param leg_labels: The legend labels + :type leg_labels: list + :param **kwargs: Formatting arguments for the Line2D object + :type **kwargs: dict + :return: A tuple with the updated legend handles and labels + :rtype: Tuple[list, list] + """ + leg_handles.append(mpatches.Patch(**kwargs)) + leg_labels.append(label) + return leg_handles, leg_labels diff --git a/u4py/plotting/formatting.py b/u4py/plotting/formatting.py index 580393bdb00449d4604714f6b9144dd08fe764d5..74b8454c4a741a4e80bdeecb8c9d6740851d9cc9 100644 --- a/u4py/plotting/formatting.py +++ b/u4py/plotting/formatting.py @@ -465,16 +465,13 @@ def get_style_from_legend( un_uuid.sort() leg_handles = [] leg_labels = [] - jj = 0 + for uuid in un_uuid: try: ii = uuids.index(uuid) if isinstance(styles[ii], str): leg_handles.append( - mpatches.Rectangle( - (0, jj), - width=0.05, - height=0.75, + mpatches.Patch( facecolor=facecolors[ii], edgecolor=edgecolors[ii], alpha=alphas[ii], @@ -486,10 +483,7 @@ def get_style_from_legend( elif isinstance(styles[ii], dict): if not styles[ii]["hatch"]: leg_handles.append( - mpatches.Rectangle( - (0, jj), - width=0.05, - height=0.75, + mpatches.Patch( facecolor=facecolors[ii], edgecolor=edgecolors[ii], alpha=alphas[ii], @@ -498,11 +492,9 @@ def get_style_from_legend( ) ) else: - leg_handles.append( - mpatches.Rectangle( - (0, jj), - width=0.05, - height=0.75, + handle = [] + handle.append( + mpatches.Patch( facecolor=facecolors[ii], edgecolor=edgecolors[ii], alpha=alphas[ii], @@ -514,11 +506,8 @@ def get_style_from_legend( ht = styles[ii]["hatch"][hii] hc = styles[ii]["hatch_color"][0] hca = styles[ii]["hatch_alpha"][0] - leg_handles.append( - mpatches.Rectangle( - (0, jj), - width=0.05, - height=0.75, + handle.append( + mpatches.Patch( facecolor="None", edgecolor=hc, alpha=hca, @@ -526,20 +515,13 @@ def get_style_from_legend( linewidth=linewidths[ii], ) ) + leg_handles.append(tuple(handle)) txt_width = 80 - wrapped_text = textwrap.wrap(labels[ii], txt_width) leg_labels.append(textwrap.fill(labels[ii], txt_width)) - for ii in range(len(wrapped_text)): - if ii > 0: - leg_labels.append("") - jj += 0.75 except ValueError: leg_handles.append( - mpatches.Rectangle( - (0, jj), - width=0.05, - height=0.75, + mpatches.Patch( facecolor=(0.5, 0.5, 0.5), edgecolor=(0.5, 0.5, 0.5), alpha=0.5, @@ -549,7 +531,6 @@ def get_style_from_legend( ) ) leg_labels.append("unknown") - jj += 1 return leg_dict, leg_handles, leg_labels diff --git a/u4py/plotting/plots.py b/u4py/plotting/plots.py index 32cd51fa4fec84b8940a148e7e262c49de474277..06daebb6791027d33e25ce345af88422e4de2994 100644 --- a/u4py/plotting/plots.py +++ b/u4py/plotting/plots.py @@ -13,7 +13,6 @@ import contextily import geopandas as gp import matplotlib.artist as martist import matplotlib.cm as mcm -import matplotlib.lines as mlines import matplotlib.patches as mpatches import matplotlib.pyplot as plt import numpy as np @@ -602,43 +601,7 @@ def geology_map( suffix=f"_{row[1].group:05}", out_folder=shp_path, ) - hydro_pts = u4web.query_hlnug( - "geologie/gk25/MapServer", - "Hydrogeologie (Punktdaten)", - region=region, - suffix=f"_hygpts_{row[1].group:05}", - out_folder=shp_path, - ) - nass = u4web.query_hlnug( - "geologie/gk25/MapServer", - "Nassstellen", - region=region, - suffix=f"_nass_{row[1].group:05}", - out_folder=shp_path, - ) - water = u4web.query_hlnug( - "geologie/gk25/MapServer", - "Gewässer", - region=region, - suffix=f"_water_{row[1].group:05}", - out_folder=shp_path, - ) - geo_pts = u4web.query_hlnug( - "geologie/gk25/MapServer", - "Geologie (Punktdaten)", - region=region, - suffix=f"_gepts_{row[1].group:05}", - out_folder=shp_path, - ) - bohr = u4web.query_hlnug( - "geologie/bohrdatenportal/MapServer", - "Archivbohrungen, Endteufe [m]", - region=region, - suffix=f"_bohr_{row[1].group:05}", - out_folder=shp_path, - ) - - if len(geology_data) > 0: + if not geology_data.empty: # Get symbology and fill legend entry lists if "TKEH" in geology_data.keys(): # For data from HLNUG leg_list = geology_data["TKEH"].to_list() @@ -650,35 +613,24 @@ def geology_map( geology_data["einheit"].to_list(), ) ] - leg_dict, lgh_from_file, lglb_from_file = ( - u4plotfmt.get_style_from_legend(leg_list, legend_path) - ) + leg_handles = [] leg_labels = [] - leg_handles.append( - mpatches.Rectangle( - (0, -1), - width=0.05, - height=0.75, - facecolor="None", - edgecolor="C0", - linewidth=3, - ) + + leg_handles, leg_labels = u4ax.add_patch_legend_entry( + leg_handles, + leg_labels, + "Bereich der Anomalie", + facecolor="None", + edgecolor="C0", + linewidth=3, + ) + + leg_dict, lgh_from_file, lglb_from_file = ( + u4plotfmt.get_style_from_legend(leg_list, legend_path) ) - leg_labels.append("Bereich der Anomalie") leg_handles.extend(lgh_from_file) leg_labels.extend(lglb_from_file) - last_row = sum([1 if ll else 0.75 for ll in leg_labels]) + 1 - leg_handles.append( - mlines.Line2D( - [0.005, 0.045], - [last_row - 1.9, last_row - 1.5], - color="k", - linewidth=2, - zorder=5, - ) - ) - leg_labels.append("Störungen, inkl. vermutet") # Plot geological map u4ax.add_hlnug_shapes( @@ -730,19 +682,88 @@ def geology_map( suffix=f"_{row[1].group:05}", out_folder=shp_path, ) - if len(fault_data) > 0: - fault_data.plot(ax=ax, color="k") + if not fault_data.empty: + leg_handles, leg_labels = u4ax.add_fault_data( + fault_data=fault_data, + leg_handles=leg_handles, + leg_labels=leg_labels, + ax=ax, + ) + hydro_pts = u4web.query_hlnug( + "geologie/gk25/MapServer", + "Hydrogeologie (Punktdaten)", + region=region, + suffix=f"_hygpts_{row[1].group:05}", + out_folder=shp_path, + ) if not hydro_pts.empty: - hydro_pts.plot(ax=ax, color="b") - if not nass.empty: - nass.plot(ax=ax, color="b") - if not water.empty: - water.plot(ax=ax, color="c") + leg_handles, leg_labels = u4ax.add_hydrological_points( + hydro_pts=hydro_pts, + leg_handles=leg_handles, + leg_labels=leg_labels, + ax=ax, + ) + + soggy_areas = u4web.query_hlnug( + "geologie/gk25/MapServer", + "Nassstellen", + region=region, + suffix=f"_nass_{row[1].group:05}", + out_folder=shp_path, + ) + if not soggy_areas.empty: + leg_handles, leg_labels = u4ax.add_soggy_areas( + soggy_areas=soggy_areas, + leg_handles=leg_handles, + leg_labels=leg_labels, + ax=ax, + ) + + water_surface = u4web.query_hlnug( + "geologie/gk25/MapServer", + "Gewässer", + region=region, + suffix=f"_water_{row[1].group:05}", + out_folder=shp_path, + ) + if not water_surface.empty: + leg_handles, leg_labels = u4ax.add_water_surface( + water_surface=water_surface, + leg_handles=leg_handles, + leg_labels=leg_labels, + ax=ax, + ) + + geo_pts = u4web.query_hlnug( + "geologie/gk25/MapServer", + "Geologie (Punktdaten)", + region=region, + suffix=f"_gepts_{row[1].group:05}", + out_folder=shp_path, + ) if not geo_pts.empty: - geo_pts.plot(ax=ax, color="r") - if not bohr.empty: - bohr.plot(ax=ax) + leg_handles, leg_labels = u4ax.add_geo_pts( + geo_pts=geo_pts, + leg_handles=leg_handles, + leg_labels=leg_labels, + ax=ax, + ) + + drill_sites = u4web.query_hlnug( + "geologie/bohrdatenportal/MapServer", + "Archivbohrungen, Endteufe [m]", + region=region, + suffix=f"_bohr_{row[1].group:05}", + out_folder=shp_path, + ) + if not drill_sites.empty: + leg_handles, leg_labels = u4ax.add_drill_sites( + drill_sites=drill_sites, + leg_handles=leg_handles, + leg_labels=leg_labels, + ax=ax, + ) # Formatting and other stuff u4plotfmt.add_scalebar(ax=ax, width=plot_buffer * 4) @@ -759,6 +780,7 @@ def geology_map( # Create legend plot_legend(leg_handles, leg_labels, output_path, row[1].group, "GK25") + plt.close(fig) @@ -839,17 +861,14 @@ def hydrogeology_map( ) leg_handles = [] leg_labels = [] - leg_handles.append( - mpatches.Rectangle( - (0, -1), - width=0.05, - height=0.75, - facecolor="None", - edgecolor="C0", - linewidth=3, - ) + leg_handles, leg_labels = u4ax.add_patch_legend_entry( + leg_handles, + leg_labels, + "Bereich der Anomalie", + facecolor="None", + edgecolor="C0", + linewidth=3, ) - leg_labels.append("Bereich der Anomalie") leg_handles.extend(lgh_from_file) leg_labels.extend(lglb_from_file) @@ -871,11 +890,11 @@ def hydrogeology_map( # Formatting and other stuff u4plotfmt.add_scalebar(ax=ax, width=plot_buffer * 4) - # u4ax.add_basemap( - # ax=ax, - # crs=hydro_units_data.crs, - # # source=contextily.providers.CartoDB.Positron, - # ) + u4ax.add_basemap( + ax=ax, + crs=hydro_units_data.crs, + source=contextily.providers.TopPlusOpen.Grey, + ) for ftype in GLOBAL_TYPES: fig.savefig( os.path.join(output_path, f"{row[1].group:05}_HUEK200.{ftype}") @@ -968,17 +987,14 @@ def topsoil_map( leg_dict["alpha"] = [0.4 * a for a in leg_dict["alpha"]] leg_handles = [] leg_labels = [] - leg_handles.append( - mpatches.Rectangle( - (0, -1), - width=0.05, - height=0.75, - facecolor="None", - edgecolor="C0", - linewidth=3, - ) + leg_handles, leg_labels = u4ax.add_patch_legend_entry( + leg_handles, + leg_labels, + "Bereich der Anomalie", + facecolor="None", + edgecolor="C0", + linewidth=3, ) - leg_labels.append("Bereich der Anomalie") leg_handles.extend(lgh_from_file) leg_labels.extend(lglb_from_file) @@ -1000,11 +1016,11 @@ def topsoil_map( # Formatting and other stuff u4plotfmt.add_scalebar(ax=ax, width=plot_buffer * 4) - # u4ax.add_basemap( - # ax=ax, - # crs=soil_data.crs, - # # source=contextily.providers.CartoDB.Positron, - # ) + u4ax.add_basemap( + ax=ax, + crs=soil_data.crs, + source=contextily.providers.TopPlusOpen.Grey, + ) for ftype in GLOBAL_TYPES: fig.savefig( os.path.join(output_path, f"{row[1].group:05}_BFD50.{ftype}") @@ -1771,27 +1787,19 @@ def plot_legend( grp = f"{group:05}" logging.info("Creating legend") - figl, axl = plt.subplots( + figl = plt.figure( figsize=(16 / 2.54, len(leg_labels) * 0.25), dpi=GLOBAL_DPI ) - - ii = 0 - for lbl in leg_labels: - axl.annotate(lbl, (0.06, -0.9 + ii), verticalalignment="top") - if lbl: - ii += 1 - else: - ii += 0.75 - - for lgh in leg_handles: - axl.add_artist(lgh) - - axl.axis("off") - axl.set_ylim(-1.5, ii - 0.5) - axl.set_xlim(0, 1) - axl.set_position([0, 0, 1, 1]) - axl.invert_yaxis() + legend = figl.legend( + handles=leg_handles, labels=leg_labels, framealpha=1, frameon=False + ) + bbox = legend.get_window_extent() + bbox = bbox.from_extents(*(bbox.extents + np.array([-5, -5, 5, 5]))) + bbox = bbox.transformed(figl.dpi_scale_trans.inverted()) for ftype in GLOBAL_TYPES: - figl.savefig(os.path.join(output_path, f"{grp}_{suffix}_leg.{ftype}")) + figl.savefig( + os.path.join(output_path, f"{grp}_{suffix}_leg.{ftype}"), + bbox_inches=bbox, + ) plt.close(figl) diff --git a/u4py/scripts/gis_workflows/PostProcess_ClassifiedShapes.py b/u4py/scripts/gis_workflows/PostProcess_ClassifiedShapes.py index 3158847003f48d75e2dc07d4a399919f547099d2..ada0f2e80b5dd3d43bc00894aa2718a2e7925b13 100644 --- a/u4py/scripts/gis_workflows/PostProcess_ClassifiedShapes.py +++ b/u4py/scripts/gis_workflows/PostProcess_ClassifiedShapes.py @@ -18,16 +18,25 @@ import u4py.analysis.spatial as u4spatial import u4py.io.docx_report as u4docx import u4py.io.tex_report as u4tex import u4py.plotting.plots as u4plots +import u4py.utils.cmd_args as u4args import u4py.utils.projects as u4proj def main(): + args = u4args.load() + if args.input: + proj_path = args.input + else: + # proj_path = r"~\Documents\ArcGIS\U4_projects\PostProcess_ClassifiedShapesHLNUG.u4project" + # proj_path = ( + # "~/Documents/umwelt4/PostProcess_ClassifiedShapesHLNUG.u4project" + # ) + proj_path = "~/Documents/umwelt4/PostProcess_ClassifiedShapes_onlyLarge.u4project" + # proj_path = ( + # "~/Documents/umwelt4/PostProcess_ClassifiedShapes_hazard.u4project" + # ) project = u4proj.get_project( - proj_path=Path( - r"~\Documents\ArcGIS\U4_projects\PostProcess_ClassifiedShapesHLNUG.u4project" - # "~/Documents/umwelt4/PostProcess_ClassifiedShapesHLNUG.u4project" - # "~/Documents/umwelt4/PostProcess_ClassifiedShapes.u4project" - ).expanduser(), + proj_path=Path(proj_path).expanduser(), required=[ "base_path", "places_path", @@ -37,35 +46,14 @@ def main(): ], interactive=False, ) - overwrite = False - use_filtered = False - use_parallel = False - generate_plots = False - overwrite_plots = True - generate_document = True - single_report = False - is_hlnug = True - if is_hlnug: + if project.getboolean("config", "is_hlnug"): u4plots.GLOBAL_TYPES = ["png"] - # Setting up names of report - - # report_title = "Große Bewegungsanomalien in Hessen" - # report_subtitle = ( - # "Anomalien mit mindestens 20000\,m\\textsuperscript{3} Volumenänderung" - # ) - # report_suffix = "_onlyLarge" - # report_title = "Bewegungsanomalien in Hessen" - # report_subtitle = "Anomalien in der Nähe bekannter Geogefahren" - # report_suffix = "_hazard" - report_title = "Rutschungsdatenbank Hessen" - report_subtitle = "nach HLNUG" - report_suffix = "_hlnug" - # Setting up paths output_path = os.path.join( - project["paths"]["output_path"], f"Detailed_Maps{report_suffix}" + project["paths"]["output_path"], + f"Detailed_Maps{project['metadata']['report_suffix']}", ) os.makedirs(output_path, exist_ok=True) class_shp_fp = os.path.join( @@ -73,7 +61,7 @@ def main(): ) cls_shp_fp_filtered = os.path.join( project["paths"]["results_path"], - f"Filtered_Classified_Shapes{report_suffix}.gpkg", + f"Filtered_Classified_Shapes{project['metadata']['report_suffix']}.gpkg", ) hlnug_path = os.path.join( project["paths"]["places_path"], @@ -91,28 +79,35 @@ def main(): ) # Read Data - if use_filtered: - if not os.path.exists(cls_shp_fp_filtered) or overwrite: + if project.getboolean("config", "use_filtered"): + if not os.path.exists(cls_shp_fp_filtered) or project.getboolean( + "config", "overwrite_data" + ): gdf_filtered = filter_shapes( class_shp_fp, cls_shp_fp_filtered, project ) gdf_filtered = reverse_geolocate( - gdf_filtered, project["paths"]["results_path"] + gdf_filtered, + project["paths"]["results_path"], + cls_shp_fp_filtered, ) else: gdf_filtered = gp.read_file(cls_shp_fp_filtered) else: - if not os.path.exists(cls_shp_fp_filtered) or overwrite: + if not os.path.exists(cls_shp_fp_filtered) or project.getboolean( + "config", "overwrite_data" + ): gdf_filtered = gp.read_file(class_shp_fp) - gdf_filtered = gdf_filtered[:20] gdf_filtered = reverse_geolocate( - gdf_filtered, project["paths"]["results_path"] + gdf_filtered, + project["paths"]["results_path"], + cls_shp_fp_filtered, ) else: gdf_filtered = gp.read_file(cls_shp_fp_filtered) # Generating Plots - if generate_plots: + if project.getboolean("config", "generate_plots"): # Assembling arguments for processing args = [ ( @@ -123,13 +118,13 @@ def main(): project, dem_path, contour_path, - overwrite_plots, - report_suffix, + project.getboolean("config", "overwrite_plots"), + project["metadata"]["report_suffix"], ) for row in gdf_filtered.iterrows() ] # args = args[:20] - if use_parallel: + if project.getboolean("config", "use_parallel"): u4proc.batch_mapping(args, wrap_map_worker, "Generating Plots") else: for arg in tqdm( @@ -140,7 +135,7 @@ def main(): ): wrap_map_worker(arg) - if is_hlnug: + if project.getboolean("config", "is_hlnug"): hlnug_data = gp.read_file( os.path.join( project["paths"]["places_path"], @@ -153,20 +148,20 @@ def main(): hlnug_data = gp.GeoDataFrame() # Generating individual files and final report (for PDF). - if generate_document: - if not is_hlnug: + if project.getboolean("config", "generate_document"): + if not project.getboolean("config", "is_hlnug"): for row in tqdm( gdf_filtered.iterrows(), desc="Generating tex files", total=len(gdf_filtered), ): u4tex.site_report(row, output_path, "tex_includes") - if single_report: + if project.getboolean("config", "single_report"): u4tex.main_report( output_path, - report_title, - report_subtitle, - report_suffix, + project["metadata"]["report_title"], + project["metadata"]["report_subtitle"], + project["metadata"]["report_suffix"], ) else: u4tex.multi_report(output_path) @@ -179,7 +174,10 @@ def main(): ): if ii < 20: u4docx.site_report( - row, output_path, report_suffix, hlnug_data + row, + output_path, + project["metadata"]["report_suffix"], + hlnug_data, ) ii += 1 @@ -211,6 +209,7 @@ def filter_shapes( def reverse_geolocate( gdf_filtered: gp.GeoDataFrame, output_path: os.PathLike, + cls_shp_fp_filtered: os.PathLike, ) -> gp.GeoDataFrame: # Do a reverse geocoding to get the address of the locations. cached_locations = os.path.join(output_path, "cached_locations.txt") @@ -231,9 +230,9 @@ def reverse_geolocate( cache.write(loc + "\n") gdf_filtered = gdf_filtered.assign(locations=locations) - gdf_filtered.to_file(output_path) + gdf_filtered.to_file(cls_shp_fp_filtered) gdf_filtered.to_crs("EPSG:4326").to_file( - os.path.splitext(output_path)[0] + ".geojson" + os.path.splitext(cls_shp_fp_filtered)[0] + ".geojson" ) return gdf_filtered @@ -255,7 +254,7 @@ def map_worker( project: configparser.ConfigParser, dem_path: os.PathLike, contour_path: os.PathLike, - overwrite: bool, + overwrite_plots: bool, suffix: str, ): """Calls the various plotting and reporting functions. @@ -284,7 +283,7 @@ def map_worker( hlnug_path, contour_path, plot_buffer=250, - overwrite=overwrite, + overwrite=overwrite_plots, ) u4plots.satimg_map( row, @@ -293,7 +292,7 @@ def map_worker( "known_features", contour_path, plot_buffer=100, - overwrite=overwrite, + overwrite=overwrite_plots, ) u4plots.diffplan_map( row, @@ -302,7 +301,7 @@ def map_worker( "known_features", project["paths"]["diff_plan_path"], plot_buffer=100, - overwrite=overwrite, + overwrite=overwrite_plots, ) u4plots.dem_map( row, @@ -312,7 +311,7 @@ def map_worker( dem_path, contour_path, plot_buffer=100, - overwrite=overwrite, + overwrite=overwrite_plots, ) u4plots.slope_map( row, @@ -322,7 +321,7 @@ def map_worker( dem_path, contour_path, plot_buffer=100, - overwrite=overwrite, + overwrite=overwrite_plots, ) u4plots.aspect_map( row, @@ -332,7 +331,7 @@ def map_worker( dem_path, contour_path, plot_buffer=100, - overwrite=overwrite, + overwrite=overwrite_plots, ) u4plots.aspect_slope_map( row, @@ -342,7 +341,7 @@ def map_worker( dem_path, contour_path, plot_buffer=100, - overwrite=overwrite, + overwrite=overwrite_plots, ) shp_path = os.path.join( project["paths"]["places_path"], @@ -360,7 +359,7 @@ def map_worker( ), shp_path=shp_path, plot_buffer=100, - overwrite=overwrite, + overwrite=overwrite_plots, use_internal=False, ) u4plots.hydrogeology_map( @@ -374,7 +373,7 @@ def map_worker( ), shp_path=shp_path, plot_buffer=100, - overwrite=overwrite, + overwrite=overwrite_plots, ) u4plots.topsoil_map( row, @@ -385,7 +384,7 @@ def map_worker( os.path.join(project["paths"]["places_path"], "legend_BFD50.pkl"), shp_path=shp_path, plot_buffer=100, - overwrite=overwrite, + overwrite=overwrite_plots, ) try: @@ -398,7 +397,7 @@ def map_worker( contour_path, os.path.join(project["paths"]["psi_path"]), plot_buffer=100, - overwrite=overwrite, + overwrite=overwrite_plots, ) except TypeError: logging.info("No PSI features") diff --git a/u4py/utils/__init__.py b/u4py/utils/__init__.py index 4ee112e9603bc884578c9e78b9dadc14195aff4a..19be2ca7721c4df351b72359bc93beb7f50bdf6b 100644 --- a/u4py/utils/__init__.py +++ b/u4py/utils/__init__.py @@ -3,9 +3,10 @@ Module with several utility functions for data conversion, configuration or project management """ -from . import cmd_args, config, convert, projects, utils +from . import cmd_args, config, convert, projects, types, utils from .cmd_args import * from .config import * from .convert import * from .projects import * +from .types import * from .utils import * diff --git a/u4py/utils/projects.py b/u4py/utils/projects.py index c2ae7d73ab7c1a006d98376004cb879bb248235d..d2ab170a0fa537a6e98d98a4557777aaf47a42f6 100644 --- a/u4py/utils/projects.py +++ b/u4py/utils/projects.py @@ -19,6 +19,7 @@ from functools import partial from tkinter import TclError, messagebox, ttk import u4py.io.files as u4files +import u4py.utils.types as u4types global PROJECT @@ -29,7 +30,7 @@ def get_project( "base_path", ], interactive: bool = True, -) -> configparser.ConfigParser: +) -> u4types.U4Project: """Interactive project path loader. A project file stores all relevant paths for specific plots. Typically used diff --git a/u4py/utils/types.py b/u4py/utils/types.py new file mode 100644 index 0000000000000000000000000000000000000000..1df5e32a4bef0c9aa55722be9863d9575404c2bd --- /dev/null +++ b/u4py/utils/types.py @@ -0,0 +1,45 @@ +""" +Contains types for better type hints in Python +""" + +import os +from typing import TypedDict + + +class U4PathsConfig(TypedDict): + base_path: os.PathLike + ext_path: os.PathLike + places_path: os.PathLike + output_path: os.PathLike + psi_path: os.PathLike + processing_path: os.PathLike + diff_plan_path: os.PathLike + u4projects_path: os.PathLike + tektonik_path: os.PathLike + bld_path: os.PathLike + piloten_path: os.PathLike + base_map_path: os.PathLike + subsubregions_path: os.PathLike + psivert_path: os.PathLike + psiew_path: os.PathLike + results_path: os.PathLike + +class U4Config(TypedDict): + overwrite: bool + use_filtered: bool + use_parallel: bool + generate_plots: bool + overwrite_plots: bool + generate_document: bool + single_report: bool + is_hlnug: bool + +class U4Metadata(TypedDict): + report_title: str + report_subtitle: str + report_suffix: str + +class U4Project(TypedDict): + paths: U4PathsConfig + config: U4Config + metadata: U4Metadata \ No newline at end of file