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