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(