diff --git a/u4py/io/docx_report.py b/u4py/io/docx_report.py
new file mode 100644
index 0000000000000000000000000000000000000000..97f702c71f1acf867ea09bb60c897bed9cf88f42
--- /dev/null
+++ b/u4py/io/docx_report.py
@@ -0,0 +1,833 @@
+"""
+Functions for creating a Word-Based report of the anomalies, taylored to the
+requirements of the HLNUG.
+"""
+
+import os
+
+import docx
+import docx.parts
+import docx.shared
+import geopandas as gp
+import humanize
+import numpy as np
+import uncertainties as unc
+from docx.document import Document
+from docx.enum.text import WD_ALIGN_PARAGRAPH
+
+import u4py.io.human_text as u4human
+
+FIGURENUM = 1
+TABLENUM = 1
+
+
+def site_report(
+    row: tuple,
+    output_path: os.PathLike,
+    suffix: str,
+    hlnug_data: gp.GeoDataFrame,
+    img_fmt: str = "png",
+):
+    """Creates a report for each area of interest using python-docx.
+
+    :param row: The index and data for the area of interest.
+    :type row: tuple
+    :param output_path: The path where to store the outputs.
+    :type output_path: os.PathLike
+    :param suffix: The subfolder to use for the docx files.
+    :type suffix: str
+    """
+    global FIGURENUM
+    # Setting Paths
+    group = row[1].group
+    output_path_docx = os.path.join(output_path, suffix)
+    os.makedirs(output_path_docx, exist_ok=True)
+    img_path = os.path.join(output_path, "known_features", f"{group:05}")
+
+    # Create Document and apply style
+    document = docx.Document()
+    styles = document.styles
+    styles["Normal"].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
+    section = document.sections[0]
+    section.page_height = docx.shared.Mm(297)
+    section.page_width = docx.shared.Mm(210)
+    section.left_margin = docx.shared.Mm(25.4)
+    section.right_margin = docx.shared.Mm(25.4)
+    section.top_margin = docx.shared.Mm(25.4)
+    section.bottom_margin = docx.shared.Mm(25.4)
+    section.header_distance = docx.shared.Mm(12.7)
+    section.footer_distance = docx.shared.Mm(12.7)
+
+    # Start filling the Document
+    document.add_heading(f"Beschreibung für Nr. {group}", level=0)
+
+    # Add header
+    heading = ",".join(row[1].locations.split(",")[:-4])
+    document.add_heading(heading, level=1)
+
+    # Overview plot and satellite image
+    document = location(row[1], document)
+    if os.path.exists(img_path + f"_map.{img_fmt}") and os.path.exists(
+        img_path + f"_satimg.{img_fmt}"
+    ):
+        document = details_and_satellite(img_path, img_fmt, document)
+    # Manual Classification or HLNUG data
+    document = hlnug_description(
+        hlnug_data[hlnug_data.AMT_NR_ == group], document
+    )
+    document = shape(row[1], document)
+    document = landuse(row[1], document)
+
+    # Volumina
+    document = moved_volumes(row[1], document)
+
+    # Difference maps
+    if os.path.exists(img_path + f"_diffplan.{img_fmt}"):
+        document = difference(img_path, img_fmt, document)
+
+    # Topographie
+    if os.path.exists(img_path + f"_slope.{img_fmt}") or os.path.exists(
+        img_path + f"_aspect_slope.{img_fmt}"
+    ):
+        document = topography(row[1], img_path, img_fmt, document)
+
+    # PSI Data
+    if os.path.exists(img_path + f"_psi.{img_fmt}"):
+        document = psi_map(img_path, img_fmt, document)
+
+    # Geohazard
+    document = geohazard(row[1], document)
+
+    # Geologie etc...
+    if os.path.exists(img_path + f"_GK25.{img_fmt}"):
+        document = geology(img_path, img_fmt, document)
+    if os.path.exists(img_path + f"_HUEK200.{img_fmt}"):
+        document = hydrogeology(img_path, img_fmt, document)
+    if os.path.exists(img_path + f"_BFD50.{img_fmt}"):
+        document = soils(img_path, img_fmt, document)
+
+    # Save to docx file
+    document.save(os.path.join(output_path_docx, f"{group:05}_info.docx"))
+
+
+def location(series: gp.GeoSeries, document: Document) -> Document:
+    """Adds location information to the document
+
+    :param series: The GeoSeries object extracted from the row.
+    :type series: gp.GeoSeries
+    :return: The tex code.
+    :rtype: str
+    """
+
+    wgs_point = gp.GeoDataFrame(
+        geometry=[series.geometry.centroid], crs="EPSG:32632"
+    ).to_crs("EPSG:4326")
+    lat = np.round(float(wgs_point.geometry.y.iloc[0]), 6)
+    lng = np.round(float(wgs_point.geometry.x.iloc[0]), 6)
+    document.add_heading("Lokalität:", level=1)
+
+    prgph = document.add_paragraph()
+    prgph.add_run("Adresse: ").bold = True
+    prgph.add_run(f"{series.locations}".replace("\n", ""))
+
+    prgph = document.add_paragraph()
+    prgph.add_run("Koordinaten (UTM 32N): ").bold = True
+    prgph.add_run(f"{int(series.geometry.centroid.y)} N ")
+    prgph.add_run(f"{int(series.geometry.centroid.x)} E")
+
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.LEFT
+    prgph.add_run("Google Maps: ").bold = True
+    prgph.add_run(
+        f"https://www.google.com/maps/place/{lat},{lng}/@{lat},{lng}/data=!3m1!1e3"
+    )
+
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.LEFT
+    prgph.add_run("Bing Maps: ").bold = True
+    prgph.add_run(
+        f"https://bing.com/maps/default.aspx?cp={lat}~{lng}&style=h&lvl=15"
+    )
+
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.LEFT
+    prgph.add_run("OpenStreetMap: ").bold = True
+    prgph.add_run(
+        f"http://www.openstreetmap.org/?lat={lat}&lon={lng}&zoom=17&layers=M"
+    )
+    return document
+
+
+def details_and_satellite(
+    img_path: os.PathLike, img_fmt: str, document: Document
+) -> Document:
+    """Adds the detailed map and the satellite image map.
+
+    :param img_path: The path to the image folder including group name.
+    :type img_path: os.PathLike
+    :return: The tex code.
+    :rtype: str
+    """
+    global FIGURENUM
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    run = prgph.add_run()
+    run.add_picture(img_path + f"_map.{img_fmt}", width=docx.shared.Mm(70))
+    run.add_picture(img_path + f"_satimg.{img_fmt}", width=docx.shared.Mm(70))
+
+    prgph = document.add_paragraph()
+    prgph.add_run(f"Abbildung {FIGURENUM}: ").bold = True
+    FIGURENUM += 1
+    prgph.add_run("Lokalität der Anomalie. ")
+    prgph.add_run("Links: ").italic = True
+    prgph.add_run(
+        "Übersicht über das Gebiet der Gruppe inklusive verschiedener "
+        + "Geogefahren und der detektierten Anomalien "
+        + "(Kartengrundlage OpenStreetMap). "
+    )
+    prgph.add_run("Rechts: ").italic = True
+    prgph.add_run("Luftbild basierend auf ESRI Imagery.")
+
+    return document
+
+
+def hlnug_description(hld: gp.GeoDataFrame, document: Document) -> Document:
+    """Adds a description based on HLNUG data
+
+    :param hld: The dataset
+    :type hld: gp.GeoDataFrame
+    :return: The description
+    :rtype: str
+    """
+
+    def kart_str(in_str):
+        if "ja" in in_str:
+            spl = in_str.split(" ")
+            if len(spl) > 2:
+                return f"von {spl[1]} am {spl[2]}"
+            else:
+                return f"von {spl[1]}"
+        else:
+            return "aus dem DGM"
+
+    document.add_heading("Beschreibung", level=1)
+    prgph = document.add_paragraph()
+    prgph.add_run(f"Es handelt sich hierbei um eine {hld.OBJEKT.values[0]} ")
+    if hld.HERKUNFT.values[0]:
+        prgph.add_run(f"welche durch {hld.HERKUNFT.values[0]} ")
+        if hld.KARTIERT.values[0]:
+            prgph.add_run(f"{kart_str(hld.KARTIERT.values[0])} ")
+        prgph.add_run("kartiert wurde")
+    prgph.add_run(". ")
+    if hld.KLASSI_DGM.values[0]:
+        prgph.add_run(f"Der Befund im DGM ist {hld.KLASSI_DGM.values[0]}. ")
+    if hld.RU_SCHICHT.values[0]:
+        prgph.add_run(
+            f"Die betroffenen Einheiten sind {hld.RU_SCHICHT.values[0]} "
+        )
+        if hld.RU_SCHIC_2.values[0]:
+            prgph.add_run(f"und {hld.RU_SCHIC_2.values[0]} ")
+        if hld.GEOLOGIE.values[0]:
+            prgph.add_run(f"auf {hld.GEOLOGIE.values[0]} ")
+            if hld.STR_SYSTEM.values[0]:
+                prgph.add_run(f"({hld.STR_SYSTEM.values[0]})")
+        prgph.add_run(". ")
+    else:
+        if hld.GEOLOGIE.values[0]:
+            prgph.add_run(
+                f"Die Geologie besteht aus {hld.GEOLOGIE.values[0]} "
+            )
+            if hld.STR_SYSTEM.values[0]:
+                prgph.add_run(f"({hld.STR_SYSTEM.values[0]})")
+        prgph.add_run(". ")
+
+    if hld.FLAECHE_M2.values[0]:
+        prgph.add_run(
+            f"Die betroffene Fläche beträgt {np.round(hld.FLAECHE_M2.values[0], -2)} m². "
+        )
+
+    if hld.LAENGE_M.values[0] and hld.BREITE_M.values[0]:
+        prgph.add_run(
+            f"Sie ist {hld.LAENGE_M.values[0]}\u00a0m lang und {hld.BREITE_M.values[0]}\u00a0m breit"
+        )
+        if (
+            hld.H_MAX_MNN.values[0]
+            and hld.H_MIN_MNN.values[0]
+            and hld.H_DIFF_M.values[0]
+        ):
+            prgph.add_run(
+                f" und erstreckt sich von {hld.H_MAX_MNN.values[0]}\u00a0m NN bis {hld.H_MIN_MNN.values[0]}\u00a0m NN über {hld.H_DIFF_M.values[0]}\u00a0m Höhendifferenz"
+            )
+        prgph.add_run(". ")
+
+    if hld.EXPOSITION.values[0]:
+        if hld.EXPOSITION.values[0] != "n.b.":
+            prgph.add_run(
+                f"Das Gelände fällt nach {u4human.direction_to_text(hld.EXPOSITION.values[0], in_lang='de')} ein. "
+            )
+
+    if hld.LANDNUTZUN.values[0]:
+        prgph.add_run(
+            f"Im wesentlichen ist das Gebiet von {hld.LANDNUTZUN.values[0]} bedeckt. "
+        )
+
+    if hld.URSACHE.values[0]:
+        prgph.add_run(f"Eine mögliche Ursache ist {hld.URSACHE.values[0]}. ")
+
+    if hld.SCHUTZ_OBJ.values[0]:
+        if hld.SCHUTZ_OBJ.values[0] == "nicht bekannt":
+            prgph.add_run("Eine potentielle Gefährdung ist nicht bekannt. ")
+        else:
+            prgph.add_run(
+                f"Eine potentielle Gefährdung für {hld.SCHUTZ_OBJ.values[0]} könnte vorliegen. "
+            )
+
+    if hld.AKTIVITAET.values[0]:
+        if hld.AKTIVITAET.values[0] == "nicht bekannt":
+            prgph.add_run("Eine mögliche Aktivität ist nicht bekannt. ")
+        if hld.AKTIVITAET.values[0] == "aktiv":
+            prgph.add_run(f"Die {hld.OBJEKT.values[0]} ist aktiv. ")
+
+    if hld.MASSNAHME.values[0]:
+        if hld.MASSNAHME.values[0] == "nicht bekannt":
+            prgph.add_run("Über unternommene Maßnahmen ist nichts bekannt. ")
+        else:
+            prgph.add_run("")
+
+    if hld.BEMERKUNG.values[0]:
+        prgph = document.add_paragraph()
+        prgph.add_run(
+            "Kommentar: " + hld.BEMERKUNG.values[0] + "."
+        ).italic = True
+
+    return document
+
+
+def shape(series: gp.GeoSeries, document: Document) -> Document:
+    """Adds shape information to the document
+
+    :param series: The GeoSeries object extracted from the row.
+    :type series: gp.GeoSeries
+    :return: The tex code.
+    :rtype: str
+    """
+    humanize.activate("de")
+    prgph = document.add_paragraph()
+    prgph.add_run("Größe und Form: ").bold = True
+
+    long_ax = eval(series["shape_ellipse_a"])
+    short_ax = eval(series["shape_ellipse_b"])
+    if isinstance(long_ax, list):
+        areas = [np.pi * a * b for a, b in zip(long_ax, short_ax)]
+        if len(areas) > 0:
+            imax = np.argmax(areas)
+            imin = np.argmin(areas)
+            if len(long_ax) > 2:
+                lax = (
+                    f"zwischen {round(np.min(long_ax))} und "
+                    + f"{round(np.max(long_ax))}"
+                )
+                sax = (
+                    f"zwischen {round(np.min(short_ax))} und "
+                    + f"{round(np.max(short_ax))}"
+                )
+            else:
+                lax = f"{round(long_ax[0])} und {round(long_ax[1])}"
+                sax = f"{round(short_ax[0])} und {round(short_ax[1])}"
+
+            prgph.add_run(
+                f"Es handelt sich um {humanize.apnumber(len(long_ax))} "
+                + f"Anomalien. Die Anomalien sind {lax}\u00a0m lang und {sax}"
+                + "\u00a0m breit. "
+            )
+            if len(long_ax) > 2:
+                prgph.add_run(
+                    "Die flächenmäßig kleinste Anomalie ist hierbei "
+                    + f"{round(long_ax[imin])}\u00a0m lang und "
+                    + f"{round(short_ax[imin])}\u00a0m breit, die größte "
+                    + f"{round(long_ax[imax])}\u00a0m lang und "
+                    + f"{round(short_ax[imax])}\u00a0m breit. "
+                )
+    if isinstance(long_ax, float):
+        lax = f"{round(long_ax)}"
+        sax = f"{round(short_ax)}"
+        prgph.add_run(f"Die Anomalie ist {lax} m lang und {sax} m breit. ")
+
+    return document
+
+
+def landuse(series: gp.GeoSeries, document: Document) -> Document:
+    """Converts the landuse into a descriptive text.
+
+    :param series: The GeoSeries object extracted from the row.
+    :type series: gp.GeoSeries
+    :return: The tex code.
+    :rtype: str
+    """
+    prgph = document.add_paragraph()
+    prgph.add_run("Landnutzung: ").bold = True
+    landuse = ""
+    try:
+        landuse = eval(series.landuse_names)
+    except NameError:
+        landuse = series.landuse_names
+    landuse_perc = eval(series.landuse_percent)
+    if landuse:
+        prgph.add_run(
+            "Der überwiegende Teil wird durch "
+            + f"{u4human.landuse_str(series.landuse_major)} bedeckt. "
+            + "Die Anteile der Landnutzung sind: "
+        )
+        if isinstance(landuse, list):
+            lnd_str = ""
+            for ii in range(1, len(landuse) + 1):
+                lnd_str += f" {landuse_perc[-ii]:.1f}% {u4human.landuse_str(landuse[-ii])}, "
+
+            lnd_str = lnd_str[:-2] + "."
+            prgph.add_run(lnd_str)
+        else:
+            prgph.add_run(
+                f"{landuse_perc:.1f}% {u4human.landuse_str(landuse)}"
+            )
+    return document
+
+
+def moved_volumes(series: gp.GeoSeries, document: Document) -> Document:
+    """Adds description of moved volumes to the document.
+
+    :param series: The GeoSeries object extracted from the row.
+    :type series: gp.GeoSeries
+    :return: The tex code.
+    :rtype: str
+    """
+    document.add_heading("Höhenveränderungen", level=1)
+    document.add_paragraph(
+        "Im Gebiet um die detektierte Anomalie wurde insgesamt "
+        + f"{series.volumes_moved}\u00a0m³ Material bewegt, "
+        + f"wovon {series.volumes_added}\u00a0m³ hinzugefügt und "
+        + f"{abs(series.volumes_removed)}\u00a0m³ abgetragen wurde. "
+        + f"Dies ergibt eine Gesamtbilanz von {series.volumes_total}\u00a0m³,"
+        + f" in Summe wurde also {u4human.vol_str(series.volumes_total)}."
+    )
+    return document
+
+
+def difference(
+    img_path: os.PathLike, img_fmt: str, document: Document
+) -> Document:
+    """Adds the difference and slope maps.
+
+    :param img_path: The path to the image folder including group name.
+    :type img_path: os.PathLike
+    :return: The tex code.
+    :rtype: str
+    """
+    global FIGURENUM
+    if os.path.exists(img_path + f"_diffplan.{img_fmt}") and os.path.exists(
+        img_path + f"_dem.{img_fmt}"
+    ):
+        prgph = document.add_paragraph()
+        prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+        run = prgph.add_run()
+        run.add_picture(
+            img_path + f"_diffplan.{img_fmt}", width=docx.shared.Mm(70)
+        )
+        run.add_picture(img_path + f"_dem.{img_fmt}", width=docx.shared.Mm(70))
+    prgph = document.add_paragraph()
+    prgph.add_run(f"Abbildung {FIGURENUM}: ").bold = True
+    FIGURENUM += 1
+    prgph.add_run("Höhendaten im Bereich der Anomalie. ")
+    prgph.add_run("Links: ").italic = True
+    prgph.add_run("Differenzenplan im Gebiet.")
+    prgph.add_run("Rechts: ").italic = True
+    prgph.add_run("Digitales Höhenmodell (Schummerung).")
+    return document
+
+
+def topography(
+    series: gp.GeoSeries,
+    img_path: os.PathLike,
+    img_fmt: str,
+    document: Document,
+) -> Document:
+    """Converts the slope into a descriptive text.
+
+    :param series: The GeoSeries object extracted from the row.
+    :type series: gp.GeoSeries
+    :return: The tex code.
+    :rtype: str
+    """
+    global FIGURENUM
+    document.add_heading("Topographie", level=1)
+
+    document.add_paragraph(
+        "In den folgenden Jahren war die Topographie im Bereich der "
+        + "Anomalien wie folgt:"
+    )
+    for yy in ["14", "19", "21"]:
+        year = f"20{yy}"
+        prgph = document.add_paragraph()
+        prgph.add_run(f"{year}: ").bold = True
+
+        if not series[f"slope_polygons_mean_{yy}"] == "[]":
+            # Values for the individual polygons (can be empty)
+            sl_m = eval(series[f"slope_polygons_mean_{yy}"])
+            sl_s = eval(series[f"slope_polygons_std_{yy}"])
+            as_m = eval(series[f"aspect_polygons_mean_{yy}"])
+            as_s = eval(series[f"aspect_polygons_std_{yy}"])
+            if isinstance(sl_m, list) or isinstance(sl_m, float):
+                usl, usl_str = u4human.topo_text(sl_m, sl_s, "%", is_tex=False)
+                prgph.add_run(
+                    f"Im Bereich der Anomalie {u4human.slope_std_str(usl.s)} "
+                    + f"und {u4human.slope_str(usl.n)} ({usl_str}). "
+                )
+                if as_m:
+                    uas, uas_str = u4human.topo_text(
+                        as_m, as_s, "°", is_tex=False
+                    )
+                    prgph.add_run(
+                        "Die Anomalien fallen nach "
+                        + f"{u4human.direction_to_text(uas.n)} ({uas_str}) ein. "
+                    )
+            else:
+                prgph.add_run(
+                    "Es liegen für den inneren Bereich der Anomalie keine Daten vor (außerhalb DEM). "
+                )
+
+            # Values for the hull around all anomalies
+            usl, usl_str = u4human.topo_text(
+                eval(series[f"slope_hull_mean_{yy}"]),
+                eval(series[f"slope_hull_std_{yy}"]),
+                "%",
+                is_tex=False,
+            )
+            if isinstance(usl, unc.UFloat):
+                prgph.add_run(
+                    f"Im näheren Umfeld ist das Gelände {u4human.slope_std_str(usl.s)}"
+                    + f" und {u4human.slope_str(usl.n)} ({usl_str}). "
+                )
+                uas, uas_str = u4human.topo_text(
+                    eval(series[f"aspect_hull_mean_{yy}"]),
+                    eval(series[f"aspect_hull_std_{yy}"]),
+                    "°",
+                    is_tex=False,
+                )
+                if isinstance(uas, unc.UFloat):
+                    prgph.add_run(
+                        "Der Bereich fällt im Mittel nach "
+                        + f"{u4human.direction_to_text(uas.n)} ({uas_str}) ein. "
+                    )
+            else:
+                prgph.add_run(
+                    "Es liegen für das nähere Umfeld der Anomalien keine Werte für die Steigung vor. "
+                )
+
+    if os.path.exists(img_path + f"_slope.{img_fmt}") and os.path.exists(
+        img_path + f"_aspect.{img_fmt}"
+    ):
+        prgph = document.add_paragraph()
+        prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+        run = prgph.add_run()
+        run.add_picture(
+            img_path + f"_slope.{img_fmt}", width=docx.shared.Mm(70)
+        )
+        run.add_picture(
+            img_path + f"_aspect.{img_fmt}", width=docx.shared.Mm(70)
+        )
+
+        prgph = document.add_paragraph()
+        prgph.add_run(f"Abbildung {FIGURENUM}: ").bold = True
+        FIGURENUM += 1
+        prgph.add_run("Topographie im Gebiet. ")
+        prgph.add_run("Links: ").italic = True
+        prgph.add_run("Steigung. ")
+        prgph.add_run("Rechts: ").italic = True
+        prgph.add_run("Exposition.")
+
+    if os.path.exists(img_path + f"_aspect_slope.{img_fmt}"):
+        prgph = document.add_paragraph()
+        prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+        run = prgph.add_run()
+        run.add_picture(
+            img_path + f"_aspect_slope.{img_fmt}",
+            width=docx.shared.Mm(150),
+        )
+        prgph = document.add_paragraph()
+        prgph.add_run(f"Abbildung {FIGURENUM}: ").bold = True
+        FIGURENUM += 1
+        prgph.add_run(
+            "Gemischte Darstellung von Steigung (Sättigung) und Exposition (Farbe)."
+        )
+
+    return document
+
+
+def psi_map(
+    img_path: os.PathLike, img_fmt: str, document: Document
+) -> Document:
+    """Adds the psi map with timeseries.
+
+    :param img_path: The path to the image folder including group name.
+    :type img_path: os.PathLike
+    :return: The tex code.
+    :rtype: str
+    """
+    global FIGURENUM
+    document.add_heading("InSAR Daten", level=1)
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    run = prgph.add_run()
+    run.add_picture(img_path + f"_psi.{img_fmt}", width=docx.shared.Mm(150))
+    prgph.add_run(f"Abbildung {FIGURENUM}: ").bold = True
+    FIGURENUM += 1
+    prgph.add_run(
+        "Persistent scatterer und Zeitreihe der Deformation "
+        + "im Gebiet der Gruppe."
+    )
+    print(img_path)
+    return document
+
+
+def geohazard(series: gp.GeoSeries, document: Document) -> Document:
+    """Converts the known geohazards into a descriptive text.
+
+    :param series: The GeoSeries object extracted from the row.
+    :type series: gp.GeoSeries
+    :return: The tex code.
+    :rtype: str
+    """
+    global TABLENUM
+    document.add_heading("Geogefahren", level=1)
+    prgph = document.add_paragraph()
+    prgph.add_run(f"Tabelle {TABLENUM}: ").bold = True
+    prgph.add_run("Bekannte Geogefahren im Bereich der Rutschung.")
+    table = document.add_table(rows=4, cols=3, style="Light Shading Accent 1")
+    table.cell(0, 0).text = "Typ"
+    table.cell(0, 1).text = "Innerhalb des Areals"
+    table.cell(0, 2).text = "Im Umkreis von 1 km"
+    table.cell(1, 0).text = "Hangrutschungen"
+    table.cell(1, 1).text = f"{series.landslides_num_inside}"
+    table.cell(1, 2).text = f"{series.landslides_num_1km}"
+    table.cell(2, 0).text = "Karsterscheinungen"
+    table.cell(2, 1).text = f"{series.karst_num_inside}"
+    table.cell(2, 2).text = f"{series.karst_num_1km}"
+    table.cell(3, 0).text = "Steinschläge"
+    table.cell(3, 1).text = f"{series.rockfall_num_inside}"
+    table.cell(3, 2).text = f"{series.rockfall_num_1km}"
+
+    document = landslide_risk(series, document)
+    document = karst_risk(series, document)
+    document = subsidence_risk(series, document)
+    return document
+
+
+def landslide_risk(series: gp.GeoSeries, document: Document) -> Document:
+    """Converts the known landslides into a descriptive text.
+
+    :param series: The GeoSeries object extracted from the row.
+    :type series: gp.GeoSeries
+    :return: The tex code.
+    :rtype: str
+    """
+    lsar = series.landslide_total
+    document.add_heading("Rutschungsgefährdung", level=2)
+    prgph = document.add_paragraph()
+    if lsar > 0:
+        prgph.add_run(
+            f"Das Gebiet liegt {u4human.part_str(lsar)} ({lsar:.0f}%) in "
+            + "einem gefährdeten Bereich mit rutschungsanfälligen Schichten. "
+        )
+        try:
+            landslide_units = eval(series.landslide_units)
+        except (NameError, SyntaxError):
+            landslide_units = series.landslide_units
+        if isinstance(landslide_units, list):
+            text = "Die Einheiten sind: "
+            for unit in landslide_units:
+                text += f"{unit}, "
+            prgph.add_run(text[:-2] + ".")
+        else:
+            prgph.add_run(f"Wichtigste Einheiten sind {landslide_units}.")
+    else:
+        prgph.add_run(
+            "Es liegen keine Informationen zur Rutschungsgefährdung vor."
+        )
+    return document
+
+
+def karst_risk(series: gp.GeoSeries, document: Document) -> Document:
+    """Converts the known karst phenomena into a descriptive text.
+
+    :param series: The GeoSeries object extracted from the row.
+    :type series: gp.GeoSeries
+    :return: The tex code.
+    :rtype: str
+    """
+    ksar = series.karst_total
+    document.add_heading("Karstgefährdung", level=2)
+    prgph = document.add_paragraph()
+    if ksar > 0:
+        prgph.add_run(
+            f"Das Gebiet liegt {u4human.part_str(ksar)} ({ksar:.0f}\%) in "
+            + "einem Bereich bekannter verkarsteter Schichten. "
+        )
+        try:
+            karst_units = eval(series.karst_units)
+        except (NameError, SyntaxError):
+            karst_units = series.karst_units
+        if isinstance(karst_units, list):
+            text = "Die Einheiten sind: "
+            for unit in karst_units:
+                text += f"{unit}, "
+            prgph.add_run(text[:-2] + ".")
+        else:
+            prgph.add_run(f"Wichtigste Einheiten sind {karst_units}.")
+    else:
+        prgph.add_run("Es liegen keine Informationen zur Karstgefährdung vor.")
+    return document
+
+
+def subsidence_risk(series: gp.GeoSeries, document: Document) -> Document:
+    """Converts the area of known subsidence into a descriptive text.
+
+    :param series: The GeoSeries object extracted from the row.
+    :type series: gp.GeoSeries
+    :return: The tex code.
+    :rtype: str
+    """
+    subsar = series.subsidence_total
+    document.add_heading("Setzungsgefährdung", level=2)
+    prgph = document.add_paragraph()
+    if subsar > 0:
+        prgph.add_run(
+            f"Das Gebiet liegt {u4human.part_str(subsar)} ({subsar:.0f}\%) in "
+            + "einem Bereich bekannter setzungsgefährdeter Schichten. "
+        )
+        try:
+            subsidence_units = eval(series.subsidence_units)
+        except (NameError, SyntaxError):
+            subsidence_units = series.subsidence_units
+        if isinstance(subsidence_units, list):
+            text = "Die Einheiten sind: "
+            for unit in subsidence_units:
+                text += f"{unit}, "
+            prgph.add_run(text[:-2] + ".")
+        else:
+            prgph.add_run(f"Wichtigste Einheiten sind {subsidence_units}.")
+    else:
+        prgph.add_run(
+            "Es liegen keine Informationen zur Setzungsgefährdung vor."
+        )
+    return document
+
+
+def geology(
+    img_path: os.PathLike, img_fmt: str, document: Document
+) -> Document:
+    """Adds information for geology to the docum
+
+    :param img_path: The path to the geology image file.
+    :type img_path: os.PathLike
+    :param img_fmt: The image file format.
+    :type img_fmt: str
+    :return: The tex code
+    :rtype: str
+    """
+    global FIGURENUM
+
+    document.add_heading("Geologie", level=1)
+
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    run = prgph.add_run()
+    run.add_picture(img_path + f"_GK25.{img_fmt}", width=docx.shared.Mm(150))
+
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    run = prgph.add_run()
+    run.add_picture(
+        img_path + f"_GK25_leg.{img_fmt}", width=docx.shared.Mm(150)
+    )
+
+    prgph = document.add_paragraph()
+    prgph.add_run(f"Abbildung {FIGURENUM}: ").bold = True
+    FIGURENUM += 1
+    prgph.add_run("Geologie im Gebiet basierend auf GK25 (Quelle: HLNUG).")
+
+    return document
+
+
+def hydrogeology(
+    img_path: os.PathLike, img_fmt: str, document: Document
+) -> Document:
+    """Adds information for hydrogeology to the document
+
+    :param img_path: The path to the hydrogeology image file.
+    :type img_path: os.PathLike
+    :param img_fmt: The image file format.
+    :type img_fmt: str
+    :return: The tex code
+    :rtype: str
+    """
+    global FIGURENUM
+
+    document.add_heading("Hydrogeologie", level=1)
+
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    run = prgph.add_run()
+    run.add_picture(
+        img_path + f"_HUEK200.{img_fmt}", width=docx.shared.Mm(150)
+    )
+
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    run = prgph.add_run()
+    run.add_picture(
+        img_path + f"_HUEK200_leg.{img_fmt}", width=docx.shared.Mm(150)
+    )
+
+    prgph = document.add_paragraph()
+    prgph.add_run(f"Abbildung {FIGURENUM}: ").bold = True
+    FIGURENUM += 1
+    prgph.add_run(
+        "Hydrogeologische Einheiten im Gebiet basierend auf HÜK200 (Quelle: HLNUG)."
+    )
+
+    return document
+
+
+def soils(img_path: os.PathLike, img_fmt: str, document: Document) -> Document:
+    """Adds information for soils to the documen
+
+    :param img_path: The path to the soils image file.
+    :type img_path: os.PathLike
+    :param img_fmt: The image file format.
+    :type img_fmt: str
+    :return: The tex code
+    :rtype: str
+    """
+    global FIGURENUM
+
+    document.add_heading("Bodengruppen", level=1)
+
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    run = prgph.add_run()
+    run.add_picture(img_path + f"_BFD50.{img_fmt}", width=docx.shared.Mm(150))
+
+    prgph = document.add_paragraph()
+    prgph.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    run = prgph.add_run()
+    run.add_picture(
+        img_path + f"_BFD50_leg.{img_fmt}", width=docx.shared.Mm(150)
+    )
+
+    prgph = document.add_paragraph()
+    prgph.add_run(f"Abbildung {FIGURENUM}: ").bold = True
+    FIGURENUM += 1
+    prgph.add_run(
+        "Bodenhauptgruppen im Gebiet basierend auf der BFD50 (Quelle: HLNUG)."
+    )
+
+    return document
diff --git a/u4py/io/human_text.py b/u4py/io/human_text.py
new file mode 100644
index 0000000000000000000000000000000000000000..44a163170a3f1a117c40c6b1ef6a21901937a710
--- /dev/null
+++ b/u4py/io/human_text.py
@@ -0,0 +1,284 @@
+"""
+Functions for converting numbers or other information to human readable text.
+"""
+
+from typing import Tuple
+
+import numpy as np
+import uncertainties as unc
+from uncertainties import unumpy as unp
+
+
+def slope_str(val: float) -> str:
+    """Converts a slope estimate into a descriptive text.
+
+    :param val: The value.
+    :type val: float
+    :return: The descriptive text.
+    :rtype: str
+    """
+    if val < 1.1:
+        return "nahezu eben"
+    if val < 3.0:
+        return "sehr leicht fallend"
+    if val < 5.0:
+        return "sanft geneigt"
+    if val < 8.5:
+        return "mäßig geneigt"
+    if val < 16.5:
+        return "stark ansteigend"
+    if val < 24.0:
+        return "sehr stark ansteigend"
+    if val < 35.0:
+        return "extrem ansteigend"
+    if val < 45.0:
+        return "steil"
+    else:
+        return "sehr steil"
+
+
+def slope_std_str(val: float) -> str:
+    """Converts a standard deviation of data into a descriptive text.
+
+    :param val: The value.
+    :type val: float
+    :return: The descriptive text.
+    :rtype: str
+    """
+    if val < 5:
+        return "gleichmäßig"
+    elif val < 10:
+        return "etwas unregelmäßig"
+    elif val < 15:
+        return "unregelmäßig"
+    else:
+        return "sehr variabel"
+
+
+def landuse_str(in_str: str) -> str:
+    """Translates the landuse strings from OSM into German.
+
+    :param in_str: The landuse text from `fclass`.
+    :type in_str: str
+    :return: The German translation.
+    :rtype: str
+    """
+    convert = {
+        "allotments": "Kleingärten",
+        "buildings": "Gebäude",
+        "cemetery": "Friedhof",
+        "commercial": "Gewerbe",
+        "farmland": "Ackerland",
+        "farmyard": "Hof",
+        "forest": "Wald",
+        "grass": "Gras",
+        "heath": "Heide",
+        "industrial": "Industrie",
+        "meadow": "Wiese",
+        "military": "Sperrgebiet",
+        "nature_reserve": "Naturschutzgebiet",
+        "orchard": "Obstgarten",
+        "park": "Park",
+        "quarry": "Steinbruch",
+        "recreation_ground": "Erholungsgebiet",
+        "residential": "Wohngebiet",
+        "retail": "Einzelhandel",
+        "roads": "Straßen",
+        "scrub": "Gestrüpp",
+        "unclassified": "nicht klassifiziert",
+        "water": "Gewässer",
+        "vineyard": "Weinberg",
+    }
+    return convert[in_str]
+
+
+def part_str(val: float) -> str:
+    """Gets a qualitative descriptor for the area.
+
+    :param val: The area coverage
+    :type val: float
+    :return: A string describing the proportion of an area.
+    :rtype: str
+    """
+    if val < 10:
+        return "zu einem geringen Teil"
+    elif val < 33:
+        return "teilweise"
+    elif val < 66:
+        return "zu einem großen Teil"
+    elif val < 90:
+        return "zu einem überwiegenden Teil"
+    else:
+        return "quasi vollständig"
+
+
+def direction_to_text(
+    direction: float | str, out_lang: str = "de", in_lang: str = "en"
+) -> str:
+    """Converts an azimut between 0 and 360 to ordinal directions.
+
+    :param direction: The direction with 0 = North and 180 = South
+    :type direction: float
+    :param lang: The language of the text (supported values: "en", "de", "abbrev"), defaults to "de"
+    :type lang: str, optional
+    :return: The ordinal direction as a string
+    :rtype: str
+    """
+
+    limits = np.arange(11.25, 360 + 22.25, 22.5)
+    abbrevs = [
+        "N",
+        "NNE",
+        "NE",
+        "ENE",
+        "E",
+        "ESE",
+        "SE",
+        "SSE",
+        "S",
+        "SSW",
+        "SW",
+        "WSW",
+        "W",
+        "WNW",
+        "NW",
+        "NNW",
+        "N",
+    ]
+    translate_abbrev_en = {  # Translate from English abbreviations
+        "de": {
+            "N": "Norden",
+            "NNE": "Nordnordosten",
+            "NE": "Nordosten",
+            "ENE": "Ostnordosten",
+            "E": "Osten",
+            "ESE": "Ostsüdosten",
+            "SE": "Südosten",
+            "SSE": "Südsüdosten",
+            "S": "Süden",
+            "SSW": "Südsüdwesten",
+            "SW": "Südwesten",
+            "WSW": "Westsüdwesten",
+            "W": "Westen",
+            "WNW": "Westnordwesten",
+            "NW": "Nordwesten",
+            "NNW": "Nordnordwesten",
+        },
+        "en": {
+            "N": "North",
+            "NNE": "North-northeast",
+            "NE": "Northeast",
+            "ENE": "East-northeast",
+            "E": "East",
+            "ESE": "East-southeast",
+            "SE": "Southeast",
+            "SSE": "South-southeast",
+            "S": "South",
+            "SSW": "South-southwest",
+            "SW": "Southwest",
+            "WSW": "West-southwest",
+            "W": "West",
+            "WNW": "West-northwest",
+            "NW": "Northwest",
+            "NNW": "North-northwest",
+        },
+    }
+    translate_abbrev_de = {  # Translate from German abbreviations
+        "de": {
+            "N": "Norden",
+            "NNO": "Nordnordosten",
+            "NNW": "Nordnordwesten",
+            "NO": "Nordosten",
+            "NW": "Nordwesten",
+            "O": "Osten",
+            "ONO": "Ostnordosten",
+            "OSO": "Ostsüdosten",
+            "S": "Süden",
+            "SO": "Südosten",
+            "SSO": "Südsüdosten",
+            "SSW": "Südsüdwesten",
+            "SW": "Südwesten",
+            "W": "Westen",
+            "WNW": "Westnordwesten",
+            "WSW": "Westsüdwesten",
+        },
+        "en": {
+            "N": "North",
+            "NNO": "North-northeast",
+            "NNW": "North-northwest",
+            "NO": "Northeast",
+            "NW": "Northwest",
+            "O": "East",
+            "ONO": "East-northeast",
+            "OSO": "East-southeast",
+            "S": "South",
+            "SO": "Southwest",
+            "SSO": "South-southeast",
+            "SSW": "South-southwest",
+            "SW": "Southwest",
+            "W": "West",
+            "WNW": "West-northwest",
+            "WSW": "West-southwest",
+        },
+    }
+
+    if isinstance(direction, float):
+        for ii in range(len(limits)):
+            desc = abbrevs[ii]
+            if direction <= limits[ii]:
+                break
+    elif isinstance(direction, str):
+        desc = direction
+    if out_lang != "abbrev":
+        if in_lang == "en":
+            desc = translate_abbrev_en[out_lang][desc]
+        if in_lang == "de":
+            desc = translate_abbrev_de[out_lang][desc]
+    return desc
+
+
+def vol_str(val: float) -> str:
+    """Converts a volume estimate into a descriptive text.
+
+    :param val: The value.
+    :type val: float
+    :return: The descriptive text.
+    :rtype: str
+    """
+    if val > 1000:
+        return "Material hinzugefügt"
+    elif val < 1000:
+        return "Material abgetragen"
+    else:
+        return "das Gesamtvolumen nur wenig verändert"
+
+
+def topo_text(
+    means: float | list, std: float | list, unit: str, is_tex: bool
+) -> Tuple[unc.ufloat, str]:
+    """Converts the measured topography index, such as slope or aspect to a descriptive text.
+
+    :param means: The mean of the value
+    :type means: float | list
+    :param std: The standard deviation of the value
+    :type std: float | list
+    """
+    if isinstance(means, list):
+        slope_unp = unp.uarray(
+            means,
+            std,
+        )
+        usl = np.mean(slope_unp)
+    elif isinstance(means, float):
+        usl = unc.ufloat(means, std)
+
+    ustr = str(usl) + f"\\,{unit}"
+    if "e" in ustr:
+        ustr = f"{usl:.0f}\\,{unit}"
+
+    if is_tex:
+        ustr = ustr.replace("+/-", "$\\pm$")
+    else:
+        ustr = ustr.replace("+/-", "±")
+        ustr = ustr.replace("\\,", "\u00a0")
+    return usl, ustr
diff --git a/u4py/io/tex_report.py b/u4py/io/tex_report.py
index a4d1e3f860ab8754049e5ec15e7320877e2ffbdb..607033bd09d1888611a1e65e71226c4df97b17eb 100644
--- a/u4py/io/tex_report.py
+++ b/u4py/io/tex_report.py
@@ -3,14 +3,16 @@ Functions for creating a TeX-based report of the classified anomalies.
 """
 
 import os
+import shutil
 import subprocess
-from typing import Tuple
 
 import geopandas as gp
 import humanize
 import numpy as np
 import uncertainties as unc
-from uncertainties import unumpy as unp
+from tqdm import tqdm
+
+import u4py.io.human_text as u4human
 
 
 def multi_report(output_path: os.PathLike):
@@ -35,29 +37,24 @@ def multi_report(output_path: os.PathLike):
         + "\\usepackage[ngerman]{babel}\n"
         + "\\usepackage{tocloft}"
         + "\\addtolength{\\cftsecnumwidth}{10pt}"
-        # + "\\usepackage[margin=1in]{geometry}\n"
         + "\\begin{document}\n"
-        # + "\\title{Detektierte Anomalien}\n"
-        # + "\\subtitle{Atlas anomaler Bodenbewegungen in Hessen}\n"
-        # + "\\author{automatisch generierter Report aus U4Py}\n"
-        # + "\\date{\\today}\n"
-        # + "\\addTitleBox{Institut für Angewandte Geowissenschaften}\n\n"
-        # + "\\maketitle\n\n"
-        # + "\\tableofcontents\n\n"
-        # + "\\clearpage\n"
     )
     include_list.sort()
-    for incl in include_list:
+    for incl in tqdm(
+        include_list, desc="Generating individual reports", leave=False
+    ):
         num = os.path.splitext(os.path.split(incl)[-1])[0].replace("_info", "")
         tex = f"{base_tex}\\input{{{incl}}}\n\\clearpage\n" + "\\end{document}"
-
-        report_path = os.path.join(output_path, f"Site_Report_{num}.tex")
+        report_folder = os.path.join(output_path, "single_reports_tex")
+        report_path = os.path.join(report_folder, f"Site_Report_{num}.tex")
+        os.makedirs(report_folder, exist_ok=True)
         with open(
             report_path, "wt", encoding="utf-8", newline="\n"
         ) as tex_file:
             tex_file.write(tex)
 
-        report_out_path = os.path.split(output_path)[0]
+        report_out_path = os.path.join(output_path, "single_reports_pdf")
+        os.makedirs(report_out_path, exist_ok=True)
         run_tool_chain(report_path, report_out_path)
 
 
@@ -124,6 +121,8 @@ def run_tool_chain(
 ):
     """Runs a 3x Latex toolchain, first two in draft mode and then full compilation.
 
+    Requires Latex.
+
     :param report_path: The path to the main file
     :type report_path: os.PathLike
     :param report_out_path: The output path of the final file
@@ -131,23 +130,40 @@ def run_tool_chain(
     :param suffix: A suffix that was added to the report_path, defaults to ""
     :type suffix: str, optional
     """
-    clean_aux_files(report_out_path, suffix)
-    subprocess.run(
-        ["pdflatex", "-draftmode", f"{report_path}"],
-        cwd=report_out_path,
-    )
-    subprocess.run(
-        ["pdflatex", "-draftmode", f"{report_path}"],
-        cwd=report_out_path,
-    )
-    subprocess.run(
-        ["pdflatex", f"{report_path}"],
-        cwd=report_out_path,
-    )
-    clean_aux_files(report_out_path, suffix)
+    if shutil.which("pdflatex") is not None:
+        if not suffix:
+            suffix = os.path.splitext(report_path)[0][-6:]
+            clean_aux_files(report_out_path, suffix)
+        else:
+            clean_aux_files(report_out_path, suffix)
+        subprocess.run(
+            ["pdflatex", "-draftmode", f"{report_path}"],
+            cwd=report_out_path,
+        )
+        subprocess.run(
+            ["pdflatex", "-draftmode", f"{report_path}"],
+            cwd=report_out_path,
+        )
+        subprocess.run(
+            ["pdflatex", f"{report_path}"],
+            cwd=report_out_path,
+        )
+        clean_aux_files(report_out_path, suffix)
+    else:
+        raise FileNotFoundError(
+            "pdflatex is not installed or unavailable to Python.\n"
+            + "Consider installing or check the path variable."
+        )
 
 
 def clean_aux_files(report_out_path: os.PathLike, suffix: str):
+    """Cleans Latex auxiliary files
+
+    :param report_out_path: The path to the latex file
+    :type report_out_path: os.PathLike
+    :param suffix: The suffix of the file
+    :type suffix: str
+    """
     tex_temps = [
         "pdfa.xmpi",
         f"Site_Report{suffix}.aux",
@@ -166,7 +182,7 @@ def site_report(
     row: tuple,
     output_path: os.PathLike,
     suffix: str,
-    hlnug_data: gp.GeoDataFrame,
+    img_fmt: str = "pdf",
 ):
     """Creates a report for each area of interest using LaTeX. This is later merged together into a larger main document by the `main` function.
 
@@ -176,8 +192,8 @@ def site_report(
     :type output_path: os.PathLike
     :param suffix: The subfolder to use for the LaTeX files.
     :type suffix: str
-    :param hlnug_data: More info loaded from HLNUG Dataset
-    :type hlnug_data: gp.GeoDataFrame
+    :param out_format: The output format (pdf or docx), determines which kind of images are included (pdf or png).
+    :type out_format: str
     """
 
     # Setting Paths
@@ -192,15 +208,12 @@ def site_report(
 
     # Overview plot and satellite image
     tex += location(row[1])
-    if os.path.exists(img_path + "_map.pdf") and os.path.exists(
-        img_path + "_satimg.pdf"
+    if os.path.exists(img_path + f"_map.{img_fmt}") and os.path.exists(
+        img_path + f"_satimg.{img_fmt}"
     ):
-        tex += details_and_satellite(img_path)
+        tex += details_and_satellite(img_path, img_fmt)
     # Manual Classification or HLNUG data
-    if len(hlnug_data) > 0:
-        tex += hlnug_description(hlnug_data[hlnug_data.AMT_NR_ == group])
-    else:
-        tex += manual_description(row[1])
+    tex += manual_description(row[1])
     tex += shape(row[1])
     tex += landuse(row[1])
 
@@ -208,29 +221,29 @@ def site_report(
     tex += moved_volumes(row[1])
 
     # Difference maps
-    if os.path.exists(img_path + "_diffplan.pdf"):
-        tex += difference(img_path)
+    if os.path.exists(img_path + f"_diffplan.{img_fmt}"):
+        tex += difference(img_path, img_fmt)
 
     # Topographie
-    if os.path.exists(img_path + "_slope.pdf") or os.path.exists(
-        img_path + "_aspect_slope.pdf"
+    if os.path.exists(img_path + f"_slope.{img_fmt}") or os.path.exists(
+        img_path + f"_aspect_slope.{img_fmt}"
     ):
-        tex += topography(row[1], img_path)
+        tex += topography(row[1], img_path, img_fmt)
 
     # PSI Data
-    if os.path.exists(img_path + "_psi.pdf"):
-        tex += psi_map(img_path)
+    if os.path.exists(img_path + f"_psi.{img_fmt}"):
+        tex += psi_map(img_path, img_fmt)
 
     # Geohazard
     tex += geohazard(row[1])
 
     # Geologie etc...
-    if os.path.exists(img_path + "_GK25.pdf"):
-        tex += geology(img_path)
-    if os.path.exists(img_path + "_HUEK200.pdf"):
-        tex += hydrogeology(img_path)
-    if os.path.exists(img_path + "_BFD50.pdf"):
-        tex += soils(img_path)
+    if os.path.exists(img_path + f"_GK25.{img_fmt}"):
+        tex += geology(img_path, img_fmt)
+    if os.path.exists(img_path + f"_HUEK200.{img_fmt}"):
+        tex += hydrogeology(img_path, img_fmt)
+    if os.path.exists(img_path + f"_BFD50.{img_fmt}"):
+        tex += soils(img_path, img_fmt)
 
     # Save to tex file
     with open(
@@ -261,16 +274,16 @@ def location(series: gp.GeoSeries) -> str:
         "\\subsection*{{Lokalität:}}\n"
         + "\\textbf{Adresse:} "
         + f"{address}\n\n"
-        + f"\\textbf{{Koordinaten (UTM 32N):}} "
+        + "\\textbf{{Koordinaten (UTM 32N):}} "
         + f"{int(series.geometry.centroid.y)}\\,N "
         + f"{int(series.geometry.centroid.x)}\\,E\n\n"
-        + f"\\textbf{{Google Maps:}} "
+        + "\\textbf{{Google Maps:}} "
         + f"\\href{{https://www.google.com/maps/place/{lat},{lng}/@{lat},{lng}/data=!3m1!1e3}}"
         + f"{{\\faExternalLink {np.round(lat, 3)}\\,N, {np.round(lng, 3)}\\,E}}\n\n"
-        + f"\\textbf{{Bing Maps:}} "
+        + "\\textbf{{Bing Maps:}} "
         + f"\\href{{https://bing.com/maps/default.aspx?cp={lat}~{lng}&style=h&lvl=15}}"
         + f"{{\\faExternalLink {np.round(lat, 3)}\\,N, {np.round(lng, 3)}\\,E}}\n\n"
-        + f"\\textbf{{OpenStreetMap:}} "
+        + "\\textbf{{OpenStreetMap:}} "
         + f"\\href{{http://www.openstreetmap.org/?lat={lat}&lon={lng}&zoom=17&layers=M}}"
         + f"{{\\faExternalLink {np.round(lat, 3)}\\,N, {np.round(lng, 3)}\\,E}}\n\n"
     )
@@ -296,32 +309,34 @@ def shape(series: gp.GeoSeries) -> str:
             imax = np.argmax(areas)
             imin = np.argmin(areas)
             if len(long_ax) > 2:
-                lax = str(
-                    unc.ufloat(np.mean(long_ax), 2 * np.std(long_ax))
-                ).replace("+/-", "$\\pm$")
-                sax = str(
-                    unc.ufloat(np.mean(short_ax), 2 * np.std(short_ax))
-                ).replace("+/-", "$\\pm$")
+                lax = (
+                    f"zwischen {round(np.min(long_ax))} und "
+                    + f"{round(np.max(long_ax))}"
+                )
+                sax = (
+                    f"zwischen {round(np.min(short_ax))} und "
+                    + f"{round(np.max(short_ax))}"
+                )
             else:
                 lax = f"{round(long_ax[0])} und {round(long_ax[1])}"
                 sax = f"{round(short_ax[0])} und {round(short_ax[1])}"
 
             tex += (
                 f"Es handelt sich um {humanize.apnumber(len(long_ax))} Anomalien. "
-                + f"Die Anomalien sind ca. {lax}\\,m lang und ca. {sax}\\,m breit. "
+                + f"Die Anomalien sind {lax}\\,m lang und {sax}\\,m breit. "
             )
             if len(long_ax) > 2:
                 tex += (
-                    "Die flächenmäßig kleinste Anomalie ist hierbei ca. "
+                    "Die flächenmäßig kleinste Anomalie ist hierbei "
                     + f"{round(long_ax[imin])}\\,m lang und "
-                    + f"{round(short_ax[imin])}\\,m breit, die größte ca. "
+                    + f"{round(short_ax[imin])}\\,m breit, die größte "
                     + f"{round(long_ax[imax])}\\,m lang und "
                     + f"{round(short_ax[imax])}\\,m breit. "
                 )
     if isinstance(long_ax, float):
         lax = f"{round(long_ax)}"
         sax = f"{round(short_ax)}"
-        tex += f"Die Anomalie ist ca. {lax}\\,m lang und ca. {sax}\\,m breit. "
+        tex += f"Die Anomalie ist {lax}\\,m lang und {sax}\\,m breit. "
 
     return tex
 
@@ -387,7 +402,7 @@ def manual_description(series: gp.GeoSeries) -> str:
     return tex
 
 
-def details_and_satellite(img_path: os.PathLike) -> str:
+def details_and_satellite(img_path: os.PathLike, img_fmt: str) -> str:
     """Adds the detailed map and the satellite image map.
 
     :param img_path: The path to the image folder including group name.
@@ -399,11 +414,11 @@ def details_and_satellite(img_path: os.PathLike) -> str:
         "\\begin{figure}[h!]\n"
         + "  \\centering\n"
         + "  \\begin{subfigure}[][][t]{.49\\textwidth}\n"
-        + f"    \\includegraphics[width=\\textwidth]{{{img_path + '_map.pdf'}}}\n"
+        + f"    \\includegraphics[width=\\textwidth]{{{img_path + f'_map.{img_fmt}'}}}\n"
         + "    \\caption{Übersicht über das Gebiet der Gruppe inklusive verschiedener Geogefahren und der detektierten Anomalien (Kartengrundlage OpenStreetMap).}\n"
         + "  \\end{subfigure}\n\hfill\n"
         + "  \\begin{subfigure}[][][t]{.49\\textwidth}\n"
-        + f"    \\includegraphics[width=\\textwidth]{{{img_path + '_satimg.pdf'}}}\n"
+        + f"    \\includegraphics[width=\\textwidth]{{{img_path + f'_satimg.{img_fmt}'}}}\n"
         + "    \\caption{Luftbild basierend auf ESRI Imagery.}\n"
         + "  \\end{subfigure}\n"
         + "  \\caption{Lokalität der Anomalie.}"
@@ -427,28 +442,12 @@ def moved_volumes(series: gp.GeoSeries) -> str:
         + f"wovon {series.volumes_added}\\,m$^3$ hinzugefügt und "
         + f"{abs(series.volumes_removed)}\\,m$^3$ abgetragen wurde. "
         + f"Dies ergibt eine Gesamtbilanz von {series.volumes_total}\\,m$^3$,"
-        + f" in Summe wurde also {vol_str(series.volumes_total)}.\n\n"
+        + f" in Summe wurde also {u4human.vol_str(series.volumes_total)}.\n\n"
     )
     return tex
 
 
-def vol_str(val: float) -> str:
-    """Converts a volume estimate into a descriptive text.
-
-    :param val: The value.
-    :type val: float
-    :return: The descriptive text.
-    :rtype: str
-    """
-    if val > 100:
-        return "Material hinzugefügt"
-    elif val < 100:
-        return "Material abgetragen"
-    else:
-        return "das Gesamtvolumen nur wenig verändert"
-
-
-def difference(img_path: os.PathLike) -> str:
+def difference(img_path: os.PathLike, img_fmt: str) -> str:
     """Adds the difference and slope maps.
 
     :param img_path: The path to the image folder including group name.
@@ -457,27 +456,29 @@ def difference(img_path: os.PathLike) -> str:
     :rtype: str
     """
     tex = ""
-    if os.path.exists(img_path + "_diffplan.pdf"):
+    if os.path.exists(img_path + f"_diffplan.{img_fmt}"):
         tex += (
             "\\begin{figure}[!ht]\n"
             + "  \\centering"
-            + f"  \\includegraphics[width=.9\\textwidth]{{{img_path + '_diffplan.pdf'}}}\n"
+            + f"  \\includegraphics[width=.9\\textwidth]{{{img_path + f'_diffplan.{img_fmt}'}}}\n"
             + "  \\caption{Differenzenplan im Gebiet.}\n"
             + "\\end{figure}\n"
         )
 
-    if os.path.exists(img_path + "_dem.pdf"):
+    if os.path.exists(img_path + f"_dem.{img_fmt}"):
         tex += (
             "\\begin{figure}[!ht]\n"
             + "  \\centering"
-            + f"  \\includegraphics[width=.9\\textwidth]{{{img_path + '_dem.pdf'}}}\n"
+            + f"  \\includegraphics[width=.9\\textwidth]{{{img_path + f'_dem.{img_fmt}'}}}\n"
             + "  \\caption{Digitales Höhenmodell (Schummerung).}\n"
             + "\\end{figure}\n\n"
         )
     return tex
 
 
-def topography(series: gp.GeoSeries, img_path: os.PathLike) -> str:
+def topography(
+    series: gp.GeoSeries, img_path: os.PathLike, img_fmt: str
+) -> str:
     """Converts the slope into a descriptive text.
 
     :param series: The GeoSeries object extracted from the row.
@@ -498,67 +499,73 @@ def topography(series: gp.GeoSeries, img_path: os.PathLike) -> str:
             as_m = eval(series[f"aspect_polygons_mean_{yy}"])
             as_s = eval(series[f"aspect_polygons_std_{yy}"])
             if isinstance(sl_m, list) or isinstance(sl_m, float):
-                usl, usl_str = topo_text(sl_m, sl_s, "\\%")
+                usl, usl_str = u4human.topo_text(
+                    sl_m, sl_s, "\\%", is_tex=True
+                )
                 tex += (
-                    f"Im Bereich der Anomalie {slope_std_str(usl.s)} und "
-                    + f"{slope_str(usl.n)} ({usl_str}). "
+                    f"Im Bereich der Anomalie {u4human.slope_std_str(usl.s)} und "
+                    + f"{u4human.slope_str(usl.n)} ({usl_str}). "
                 )
                 if as_m:
-                    uas, uas_str = topo_text(as_m, as_s, "°")
+                    uas, uas_str = u4human.topo_text(
+                        as_m, as_s, "°", is_tex=True
+                    )
                     tex += (
                         "Die Anomalien fallen nach "
-                        + f"{direction_to_text(uas.n)} ({uas_str}) ein. "
+                        + f"{u4human.direction_to_text(uas.n)} ({uas_str}) ein. "
                     )
             else:
                 tex += "Es liegen für den inneren Bereich der Anomalie keine Daten vor (außerhalb DEM). "
 
             # Values for the hull around all anomalies
-            usl, usl_str = topo_text(
+            usl, usl_str = u4human.topo_text(
                 eval(series[f"slope_hull_mean_{yy}"]),
                 eval(series[f"slope_hull_std_{yy}"]),
                 "\\%",
+                is_tex=True,
             )
             if isinstance(usl, unc.UFloat):
                 tex += (
-                    f"Im näheren Umfeld ist das Gelände {slope_std_str(usl.s)}"
-                    + f" und {slope_str(usl.n)} ({usl_str}). "
+                    f"Im näheren Umfeld ist das Gelände {u4human.slope_std_str(usl.s)}"
+                    + f" und {u4human.slope_str(usl.n)} ({usl_str}). "
                 )
-                uas, uas_str = topo_text(
+                uas, uas_str = u4human.topo_text(
                     eval(series[f"aspect_hull_mean_{yy}"]),
                     eval(series[f"aspect_hull_std_{yy}"]),
                     "°",
+                    is_tex=True,
                 )
                 if isinstance(uas, unc.UFloat):
                     tex += (
                         "Der Bereich fällt im Mittel nach "
-                        + f"{direction_to_text(uas.n)} ({uas_str}) ein. "
+                        + f"{u4human.direction_to_text(uas.n)} ({uas_str}) ein. "
                     )
             else:
                 tex += "Es liegen für das nähere Umfeld der Anomalien keine Werte für die Steigung vor. "
             tex += "\n\n"
-    if os.path.exists(img_path + "_slope.pdf") and os.path.exists(
-        img_path + "_aspect.pdf"
+    if os.path.exists(img_path + f"_slope.{img_fmt}") and os.path.exists(
+        img_path + f"_aspect.{img_fmt}"
     ):
         tex += (
             "\n\\begin{figure}[!ht]\n"
             + "  \\begin{subfigure}[][][t]{.49\\textwidth}\n"
-            + f"  \\includegraphics[width=\\textwidth]{{{img_path + '_slope.pdf'}}}\n"
+            + f"  \\includegraphics[width=\\textwidth]{{{img_path + f'_slope.{img_fmt}'}}}\n"
             + "  \\caption{Steigung}\n"
             + "  \\end{subfigure}\n\hfill\n"
             + "  \\begin{subfigure}[][][t]{.49\\textwidth}\n"
             + "\\centering\n"
-            + f"  \\includegraphics[width=\\textwidth]{{{img_path + '_aspect.pdf'}}}\n"
+            + f"  \\includegraphics[width=\\textwidth]{{{img_path + f'_aspect.{img_fmt}'}}}\n"
             + "  \\caption{Exposition.}\n"
             + "  \\end{subfigure}\n\hfill\n"
             + "  \\caption{Topographie im Gebiet.}"
             + "\\end{figure}\n\n"
         )
 
-    if os.path.exists(img_path + "_aspect_slope.pdf"):
+    if os.path.exists(img_path + f"_aspect_slope.{img_fmt}"):
         tex += (
             "\\begin{figure}[!ht]\n"
             + "  \\centering\n"
-            + f"  \\includegraphics[width=.95\\textwidth]{{{img_path + '_aspect_slope.pdf'}}}\n"
+            + f"  \\includegraphics[width=.95\\textwidth]{{{img_path + f'_aspect_slope.{img_fmt}'}}}\n"
             + "  \\caption{Steigung und Exposition}\n"
             + "\\end{figure}\n"
         )
@@ -566,52 +573,6 @@ def topography(series: gp.GeoSeries, img_path: os.PathLike) -> str:
     return tex
 
 
-def slope_str(val: float) -> str:
-    """Converts a slope estimate into a descriptive text.
-
-    :param val: The value.
-    :type val: float
-    :return: The descriptive text.
-    :rtype: str
-    """
-    if val < 1.1:
-        return "nahezu eben"
-    if val < 3.0:
-        return "sehr leicht fallend"
-    if val < 5.0:
-        return "sanft geneigt"
-    if val < 8.5:
-        return "mäßig geneigt"
-    if val < 16.5:
-        return "stark ansteigend"
-    if val < 24.0:
-        return "sehr stark ansteigend"
-    if val < 35.0:
-        return "extrem ansteigend"
-    if val < 45.0:
-        return "steil"
-    else:
-        return "sehr steil"
-
-
-def slope_std_str(val: float) -> str:
-    """Converts a standard deviation of data into a descriptive text.
-
-    :param val: The value.
-    :type val: float
-    :return: The descriptive text.
-    :rtype: str
-    """
-    if val < 5:
-        return "gleichmäßig"
-    elif val < 10:
-        return "etwas unregelmäßig"
-    elif val < 15:
-        return "unregelmäßig"
-    else:
-        return "sehr variabel"
-
-
 def landuse(series: gp.GeoSeries) -> str:
     """Converts the landuse into a descriptive text.
 
@@ -630,76 +591,19 @@ def landuse(series: gp.GeoSeries) -> str:
     if landuse:
         tex += (
             "Der überwiegende Teil wird durch "
-            + f"{landuse_str(series.landuse_major)} bedeckt. "
-            + f"Die Anteile der Landnutzung sind: \n\n"
+            + f"{u4human.landuse_str(series.landuse_major)} bedeckt. "
+            + "Die Anteile der Landnutzung sind: \n\n"
         )
         if isinstance(landuse, list):
             for ii in range(1, len(landuse) + 1):
-                tex += f" {landuse_perc[-ii]:.1f}\\% {landuse_str(landuse[-ii])}, "
+                tex += f" {landuse_perc[-ii]:.1f}\\% {u4human.landuse_str(landuse[-ii])}, "
             tex = tex[:-2] + ".\n"
         else:
-            tex += f"{landuse_perc:.1f}\\% {landuse_str(landuse)}"
+            tex += f"{landuse_perc:.1f}\\% {u4human.landuse_str(landuse)}"
     return tex
 
 
-def landuse_str(in_str: str) -> str:
-    """Translates the landuse strings from OSM into German.
-
-    :param in_str: The landuse text from `fclass`.
-    :type in_str: str
-    :return: The German translation.
-    :rtype: str
-    """
-    convert = {
-        "allotments": "Kleingärten",
-        "buildings": "Gebäude",
-        "cemetery": "Friedhof",
-        "commercial": "Gewerbe",
-        "farmland": "Ackerland",
-        "farmyard": "Hof",
-        "forest": "Wald",
-        "grass": "Gras",
-        "heath": "Heide",
-        "industrial": "Industrie",
-        "meadow": "Wiese",
-        "military": "Sperrgebiet",
-        "nature_reserve": "Naturschutzgebiet",
-        "orchard": "Obstgarten",
-        "park": "Park",
-        "quarry": "Steinbruch",
-        "recreation_ground": "Erholungsgebiet",
-        "residential": "Wohngebiet",
-        "retail": "Einzelhandel",
-        "roads": "Straßen",
-        "scrub": "Gestrüpp",
-        "unclassified": "nicht klassifiziert",
-        "water": "Gewässer",
-        "vineyard": "Weinberg",
-    }
-    return convert[in_str]
-
-
-def part_str(val: float) -> str:
-    """Gets a qualitative descriptor for the area.
-
-    :param val: The area coverage
-    :type val: float
-    :return: A string describing the proportion of an area.
-    :rtype: str
-    """
-    if val < 10:
-        return "zu einem geringen Teil"
-    elif val < 33:
-        return "teilweise"
-    elif val < 66:
-        return "zu einem großen Teil"
-    elif val < 90:
-        return "zu einem überwiegenden Teil"
-    else:
-        return "quasi vollständig"
-
-
-def psi_map(img_path: os.PathLike) -> str:
+def psi_map(img_path: os.PathLike, img_fmt: str) -> str:
     """Adds the psi map with timeseries.
 
     :param img_path: The path to the image folder including group name.
@@ -711,7 +615,7 @@ def psi_map(img_path: os.PathLike) -> str:
         "\\subsection*{InSAR Daten}\n\n"
         + "\\begin{figure}[h!]\n"
         + "  \\centering\n"
-        + f"  \\includegraphics[width=\\textwidth]{{{img_path + '_psi.pdf'}}}\n"
+        + f"  \\includegraphics[width=\\textwidth]{{{img_path + f'_psi.{img_fmt}'}}}\n"
         + "  \\caption{Persistent scatterer und Zeitreihe der Deformation "
         + "im Gebiet der Gruppe.}\n"
         + "\\end{figure}\n\n"
@@ -760,13 +664,13 @@ def landslide_risk(series: gp.GeoSeries) -> str:
     lsar = series.landslide_total
     tex = "\\paragraph*{Rutschungsgefährdung}\n\n"
     if lsar > 0:
-        tex += f"Das Gebiet liegt {part_str(lsar)} ({lsar:.0f}\%) in einem gefährdeten Bereich mit rutschungsanfälligen Schichten. "
+        tex += f"Das Gebiet liegt {u4human.part_str(lsar)} ({lsar:.0f}\%) in einem gefährdeten Bereich mit rutschungsanfälligen Schichten. "
         try:
             landslide_units = eval(series.landslide_units)
         except (NameError, SyntaxError):
             landslide_units = series.landslide_units
         if isinstance(landslide_units, list):
-            tex += f"Die Einheiten sind: "
+            tex += "Die Einheiten sind: "
             for unit in landslide_units:
                 tex += f"{unit}, "
             tex = tex[:-2] + ".\n\n"
@@ -790,13 +694,13 @@ def karst_risk(series: gp.GeoSeries) -> str:
     ksar = series.karst_total
     tex = "\\paragraph*{Karstgefährdung}\n\n"
     if ksar > 0:
-        tex += f"Das Gebiet liegt {part_str(ksar)} ({ksar:.0f}\%) in einem Bereich bekannter verkarsteter Schichten. "
+        tex += f"Das Gebiet liegt {u4human.part_str(ksar)} ({ksar:.0f}\%) in einem Bereich bekannter verkarsteter Schichten. "
         try:
             karst_units = eval(series.karst_units)
         except (NameError, SyntaxError):
             karst_units = series.karst_units
         if isinstance(karst_units, list):
-            tex += f"Die Einheiten sind: "
+            tex += "Die Einheiten sind: "
             for unit in karst_units:
                 tex += f"{unit}, "
             tex = tex[:-2] + ".\n\n"
@@ -818,13 +722,13 @@ def subsidence_risk(series: gp.GeoSeries) -> str:
     subsar = series.subsidence_total
     tex = "\\paragraph*{Setzungsgefährdung}\n\n"
     if subsar > 0:
-        tex += f"Das Gebiet liegt {part_str(subsar)} ({subsar:.0f}\%) in einem Bereich bekannter setzungsgefährdeter Schichten. "
+        tex += f"Das Gebiet liegt {u4human.part_str(subsar)} ({subsar:.0f}\%) in einem Bereich bekannter setzungsgefährdeter Schichten. "
         try:
             subsidence_units = eval(series.subsidence_units)
         except (NameError, SyntaxError):
             subsidence_units = series.subsidence_units
         if isinstance(subsidence_units, list):
-            tex += f"Die Einheiten sind: "
+            tex += "Die Einheiten sind: "
             for unit in subsidence_units:
                 tex += f"{unit}, "
             tex = tex[:-2] + ".\n\n"
@@ -835,272 +739,84 @@ def subsidence_risk(series: gp.GeoSeries) -> str:
     return tex
 
 
-def geology(img_path) -> str:
+def geology(img_path: os.PathLike, img_fmt: str) -> str:
+    """Adds information for geology to the docum
+
+    :param img_path: The path to the geology image file.
+    :type img_path: os.PathLike
+    :param img_fmt: The image file format.
+    :type img_fmt: str
+    :return: The tex code
+    :rtype: str
+    """
     tex = (
         "\n\\subsection*{Geologie}\n\n"
         + "\\begin{figure}[H]\n"
         + "\\centering\n"
-        + f"  \\includegraphics[width=\\textwidth]{{{img_path + '_GK25.pdf'}}}\n"
+        + f"  \\includegraphics[width=\\textwidth]{{{img_path + f'_GK25.{img_fmt}'}}}\n"
         + "\\end{figure}\n"
         + "\\vspace{-2ex}\n"
         + "\\begin{figure}[H]\n"
         + "\\centering\n"
-        + f"  \\includegraphics[width=.75\\textwidth]{{{img_path + '_GK25_leg.pdf'}}}\n"
+        + f"  \\includegraphics[width=.75\\textwidth]{{{img_path + f'_GK25_leg.{img_fmt}'}}}\n"
         + "  \\caption{Geologie im Gebiet basierend auf GK25 (Quelle: HLNUG).}\n"
         + "\\end{figure}\n\n"
     )
     return tex
 
 
-def hydrogeology(img_path: os.PathLike) -> str:
+def hydrogeology(img_path: os.PathLike, img_fmt: str) -> str:
+    """Adds information for hydrogeology to the document
+
+    :param img_path: The path to the hydrogeology image file.
+    :type img_path: os.PathLike
+    :param img_fmt: The image file format.
+    :type img_fmt: str
+    :return: The tex code
+    :rtype: str
+    """
     tex = (
         "\n\\subsection*{Hydrogeologie}\n\n"
         + "\\begin{figure}[H]\n"
         + "\\centering\n"
-        + f"  \\includegraphics[width=\\textwidth]{{{img_path + '_HUEK200.pdf'}}}\n"
+        + f"  \\includegraphics[width=\\textwidth]{{{img_path + f'_HUEK200.{img_fmt}'}}}\n"
         + "\\end{figure}\n"
         + "\\vspace{-2ex}\n"
         + "\\begin{figure}[H]\n"
         + "\\centering\n"
-        + f"  \\includegraphics[width=.75\\textwidth]{{{img_path + '_HUEK200_leg.pdf'}}}\n"
+        + f"  \\includegraphics[width=.75\\textwidth]{{{img_path + f'_HUEK200_leg.{img_fmt}'}}}\n"
         + "  \\caption{Hydrogeologische Einheiten im Gebiet basierend auf HÜK200 (Quelle: HLNUG).}\n"
         + "\\end{figure}\n\n"
     )
     return tex
 
 
-def soils(img_path: os.PathLike) -> str:
+def soils(img_path: os.PathLike, img_fmt: str) -> str:
+    """Adds information for soils to the documen
+
+    :param img_path: The path to the soils image file.
+    :type img_path: os.PathLike
+    :param img_fmt: The image file format.
+    :type img_fmt: str
+    :return: The tex code
+    :rtype: str
+    """
     tex = (
         "\n\\subsection*{Bodengruppen}\n\n"
         + "\\begin{figure}[H]\n"
         + "\\centering\n"
-        + f"  \\includegraphics[width=\\textwidth]{{{img_path + '_BFD50.pdf'}}}\n"
+        + f"  \\includegraphics[width=\\textwidth]{{{img_path + f'_BFD50.{img_fmt}'}}}\n"
         + "\\end{figure}\n"
         + "\\vspace{-2ex}\n"
         + "\\begin{figure}[H]\n"
         + "\\centering\n"
-        + f"  \\includegraphics[width=.75\\textwidth]{{{img_path + '_BFD50_leg.pdf'}}}\n"
+        + f"  \\includegraphics[width=.75\\textwidth]{{{img_path + f'_BFD50_leg.{img_fmt}'}}}\n"
         + "  \\caption{Bodenhauptgruppen im Gebiet basierend auf der BFD50 (Quelle: HLNUG).}\n"
         + "\\end{figure}\n\n"
     )
     return tex
 
 
-def direction_to_text(direction: float, lang: str = "de") -> str:
-    """Converts an azimut between 0 and 360 to ordinal directions.
-
-    :param direction: The direction with 0 = North and 180 = South
-    :type direction: float
-    :param lang: The language of the text (supported values: "en", "de", "abbrev"), defaults to "de"
-    :type lang: str, optional
-    :return: The ordinal direction as a string
-    :rtype: str
-    """
-
-    limits = np.arange(11.25, 360 + 22.25, 22.5)
-    abbrevs = [
-        "N",
-        "NNE",
-        "NE",
-        "ENE",
-        "E",
-        "ESE",
-        "SE",
-        "SSE",
-        "S",
-        "SSW",
-        "SW",
-        "WSW",
-        "W",
-        "WNW",
-        "NW",
-        "NNW",
-        "N",
-    ]
-    translate_abbrev = {
-        "de": {
-            "N": "Norden",
-            "NNE": "Nordnordosten",
-            "NE": "Nordosten",
-            "ENE": "Ostnordosten",
-            "E": "Osten",
-            "ESE": "Ostsüdosten",
-            "SE": "Südosten",
-            "SSE": "Südsüdosten",
-            "S": "Süden",
-            "SSW": "Südsüdwesten",
-            "SW": "Südwesten",
-            "WSW": "Westsüdwesten",
-            "W": "Westen",
-            "WNW": "Westnordwesten",
-            "NW": "Nordwesten",
-            "NNW": "Nordnordwesten",
-        },
-        "en": {
-            "N": "North",
-            "NNE": "North-northeast",
-            "NE": "Northeast",
-            "ENE": "East-northeast",
-            "E": "East",
-            "ESE": "East-southeast",
-            "SE": "Southeast",
-            "SSE": "South-southeast",
-            "S": "South",
-            "SSW": "South-southwest",
-            "SW": "Southwest",
-            "WSW": "West-southwest",
-            "W": "West",
-            "WNW": "West-northwest",
-            "NW": "Northwest",
-            "NNW": "North-northwest",
-        },
-    }
-    for ii in range(len(limits)):
-        desc = abbrevs[ii]
-        if direction <= limits[ii]:
-            break
-    if lang != "abbrev":
-        desc = translate_abbrev[lang][desc]
-    return desc
-
-
-def topo_text(
-    means: float | list, std: float | list, unit: str
-) -> Tuple[unc.ufloat, str]:
-    """Converts the measured topography index, such as slope or aspect to a descriptive text.
-
-    :param means: The mean of the value
-    :type means: float | list
-    :param std: The standard deviation of the value
-    :type std: float | list
-    """
-    if isinstance(means, list):
-        slope_unp = unp.uarray(
-            means,
-            std,
-        )
-        usl = np.mean(slope_unp)
-    elif isinstance(means, float):
-        usl = unc.ufloat(means, std)
-
-    ustr = str(usl).replace("+/-", "$\\pm$") + f"\\,{unit}"
-    if "e" in ustr:
-        ustr = f"{usl:.0f}\\,{unit}".replace("+/-", "$\\pm$")
-    return usl, ustr
-
-
-def hlnug_description(hld: gp.GeoDataFrame) -> str:
-    """Adds a description based on HLNUG data
-
-    :param hld: The dataset
-    :type hld: gp.GeoDataFrame
-    :return: The description
-    :rtype: str
-    """
-
-    def kart_str(in_str):
-        if "ja" in in_str:
-            spl = in_str.split(" ")
-            if len(spl) > 2:
-                return f"von {spl[1]} am {spl[2]}"
-            else:
-                return f"von {spl[1]}"
-        else:
-            return "aus dem DGM"
-
-    tex = (
-        "\n\\subsection*{Beschreibung}\n\n"
-        + f"Es handelt sich hierbei um eine {hld.OBJEKT.values[0]} "
-    )
-    if hld.HERKUNFT.values[0]:
-        tex += f"welche durch {hld.HERKUNFT.values[0]} "
-        if hld.KARTIERT.values[0]:
-            tex += f"{kart_str(hld.KARTIERT.values[0])} "
-        tex += "kartiert wurde"
-    tex += ". "
-    if hld.KLASSI_DGM.values[0]:
-        tex += f"Der Befund im DGM ist {hld.KLASSI_DGM.values[0]}. "
-    if hld.RU_SCHICHT.values[0]:
-        tex += f"Die betroffenen Einheiten sind {hld.RU_SCHICHT.values[0]} "
-        if hld.RU_SCHIC_2.values[0]:
-            tex += f"und {hld.RU_SCHIC_2.values[0]} "
-        if hld.GEOLOGIE.values[0]:
-            tex += f"auf {hld.GEOLOGIE.values[0]} "
-            if hld.STR_SYSTEM.values[0]:
-                tex += f"({hld.STR_SYSTEM.values[0]})"
-        tex += ". "
-    else:
-        if hld.GEOLOGIE.values[0]:
-            tex += f"Die Geologie besteht aus {hld.GEOLOGIE.values[0]} "
-            if hld.STR_SYSTEM.values[0]:
-                tex += f"({hld.STR_SYSTEM.values[0]})"
-        tex += ". "
-
-    if hld.FLAECHE_M2.values[0]:
-        tex += f"Die betroffene Fläche beträgt ca. {np.round(hld.FLAECHE_M2.values[0], -2)}\\,m$^2$. "
-
-    if hld.LAENGE_M.values[0] and hld.BREITE_M.values[0]:
-        tex += f"Sie ist ca. {hld.LAENGE_M.values[0]}\\,m lang und {hld.BREITE_M.values[0]}\\,m breit"
-        if (
-            hld.H_MAX_MNN.values[0]
-            and hld.H_MIN_MNN.values[0]
-            and hld.H_DIFF_M.values[0]
-        ):
-            tex += f" und erstreckt sich von {hld.H_MAX_MNN.values[0]}\\,m\\,NN bis {hld.H_MIN_MNN.values[0]}\\,m\\,NN über ca. {hld.H_DIFF_M.values[0]}\\,m Höhendifferenz"
-        tex += ". "
-    exp2txt = {
-        "N": "Norden",
-        "NNO": "Nordnordosten",
-        "NNW": "Nordnordwesten",
-        "NO": "Nordosten",
-        "NW": "Nordwesten",
-        "O": "Osten",
-        "ONO": "Ostnordosten",
-        "OSO": "Ostsüdosten",
-        "S": "Süden",
-        "SO": "Südosten",
-        "SSO": "Südsüdosten",
-        "SSW": "Südsüdwesten",
-        "SW": "Südwesten",
-        "W": "Westen",
-        "WNW": "Westnordwesten",
-        "WSW": "Westsüdwesten",
-    }
-    if hld.EXPOSITION.values[0]:
-        if hld.EXPOSITION.values[0] != "n.b.":
-            tex += f"Das Gelände fällt nach {exp2txt[hld.EXPOSITION.values[0]]} ein. "
-
-    if hld.LANDNUTZUN.values[0]:
-        tex += f"Im wesentlichen ist das Gebiet von {hld.LANDNUTZUN.values[0]} bedeckt. "
-
-    if hld.URSACHE.values[0]:
-        tex += f"Eine mögliche Ursache ist {hld.URSACHE.values[0]}. "
-
-    if hld.SCHUTZ_OBJ.values[0]:
-        if hld.SCHUTZ_OBJ.values[0] == "nicht bekannt":
-            tex += "Eine potentielle Gefährdung ist nicht bekannt. "
-        else:
-            tex += f"Eine potentielle Gefährdung für {hld.SCHUTZ_OBJ.values[0]} könnte vorliegen. "
-
-    if hld.AKTIVITAET.values[0]:
-        if hld.AKTIVITAET.values[0] == "nicht bekannt":
-            tex += "Eine mögliche Aktivität ist nicht bekannt. "
-        if hld.AKTIVITAET.values[0] == "aktiv":
-            tex += f"Die {hld.OBJEKT.values[0]} ist aktiv. "
-
-    if hld.MASSNAHME.values[0]:
-        if hld.MASSNAHME.values[0] == "nicht bekannt":
-            tex += "Über unternommene Maßnahmen ist nichts bekannt. "
-        else:
-            tex += ""
-
-    if hld.BEMERKUNG.values[0]:
-        tex += "\n\n\\emph{Kommentar: " + hld.BEMERKUNG.values[0] + "}.\n\n"
-
-    tex = tex.replace("_", " ")
-    return tex
-
-
 def sanitize_text(in_str: str) -> str:
     """Escapes all special characters in the input string for LaTeX.
 
diff --git a/u4py/scripts/gis_workflows/PostProcess_ClassifiedShapes.py b/u4py/scripts/gis_workflows/PostProcess_ClassifiedShapes.py
index 757a23f693a81a2e3b548c74662d73010f63eb17..4f3cfea412735c28f021344763ed03e698d25aca 100644
--- a/u4py/scripts/gis_workflows/PostProcess_ClassifiedShapes.py
+++ b/u4py/scripts/gis_workflows/PostProcess_ClassifiedShapes.py
@@ -15,7 +15,8 @@ from tqdm import tqdm
 
 import u4py.analysis.processing as u4proc
 import u4py.analysis.spatial as u4spatial
-import u4py.io.tex_report as u4rep
+import u4py.io.docx_report as u4docx
+import u4py.io.tex_report as u4tex
 import u4py.plotting.plots as u4plots
 import u4py.utils.projects as u4proj
 
@@ -23,6 +24,7 @@ import u4py.utils.projects as u4proj
 def main():
     project = u4proj.get_project(
         proj_path=Path(
+            # "~/Documents/umwelt4/PostProcess_ClassifiedShapesHLNUG.u4project"
             "~/Documents/umwelt4/PostProcess_ClassifiedShapes.u4project"
         ).expanduser(),
         required=[
@@ -35,23 +37,33 @@ def main():
         interactive=False,
     )
     overwrite = False
-    use_filtered = True
+    use_filtered = False
     use_parallel = False
-    generate_plots = True
+    generate_plots = False
     overwrite_plots = False
-    generate_pdf = True
-    single_report = True
+    generate_document = True
+    single_report = False
     is_hlnug = False
 
+    if is_hlnug:
+        out_format = "docx"
+        u4plots.GLOBAL_TYPES = ["png"]
+    else:
+        out_format = "pdf"
+
     # 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 = "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(
@@ -86,13 +98,17 @@ def main():
             gdf_filtered = filter_shapes(
                 class_shp_fp, cls_shp_fp_filtered, project
             )
-            gdf_filtered = reverse_geolocate(gdf_filtered, cls_shp_fp_filtered)
+            gdf_filtered = reverse_geolocate(
+                gdf_filtered, project["paths"]["results_path"]
+            )
         else:
             gdf_filtered = gp.read_file(cls_shp_fp_filtered)
     else:
         if not os.path.exists(cls_shp_fp_filtered) or overwrite:
             gdf_filtered = gp.read_file(class_shp_fp)
-            gdf_filtered = reverse_geolocate(gdf_filtered, cls_shp_fp_filtered)
+            gdf_filtered = reverse_geolocate(
+                gdf_filtered, project["paths"]["results_path"]
+            )
         else:
             gdf_filtered = gp.read_file(cls_shp_fp_filtered)
 
@@ -112,6 +128,7 @@ def main():
             )
             for row in gdf_filtered.iterrows()
         ]
+        # args = args[:20]
         if use_parallel:
             u4proc.batch_mapping(args, wrap_map_worker, "Generating Plots")
         else:
@@ -134,22 +151,37 @@ def main():
         )
     else:
         hlnug_data = gp.GeoDataFrame()
-    # Generating TeX and final PDF
-    if generate_pdf:
-        for row in tqdm(
-            gdf_filtered.iterrows(),
-            desc="Generating tex files",
-            total=len(gdf_filtered),
-        ):
-            u4rep.site_report(
-                row, output_path, "tex_includes", hlnug_data=hlnug_data
-            )
-        if single_report:
-            u4rep.main_report(
-                output_path, report_title, report_subtitle, report_suffix
-            )
+
+    # Generating individual files and final report (for PDF).
+    if generate_document:
+        if not 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:
+                u4tex.main_report(
+                    output_path,
+                    report_title,
+                    report_subtitle,
+                    report_suffix,
+                )
+            else:
+                u4tex.multi_report(output_path)
         else:
-            u4rep.multi_report(output_path)
+            ii = 0
+            for row in tqdm(
+                gdf_filtered.iterrows(),
+                desc="Generating docx files",
+                total=len(gdf_filtered),
+            ):
+                if ii < 20:
+                    u4docx.site_report(
+                        row, output_path, report_suffix, hlnug_data
+                    )
+                    ii += 1
 
 
 def filter_shapes(