Zum Inhalt

BinaryFileHandler.py — UNF-Dateiverarbeitung

Liest die binären WaterGAP-UNF-Rasterdateien (Big-Endian, Formate UNF0/1/2/4) und konvertiert sie in NumPy-Arrays. Enthält außerdem die Pfadverkettungsfunktion für die Eingabedateien.


import Paths_and_params as PP

def getFileInfo(filepath: str) -> tuple[int, int, str]:
Extrahiert Dateiinformationen und Metadaten aus dem UNF-Dateinamen.
Diese Funktion bestimmt aus dem Dateinamen die Anzahl der Layer,
die Elementgröße und den Dateityp.

Parameter

Parameter Typ Beschreibung
filepath str Vollständiger Pfad zur UNF-Binärdatei

Rückgabe

nlayers : int Anzahl der Layer/Schichten Size : int Größe eines Datenelementes in Bytes Type : str Dateityp als String (z.B. UNF0, UNF1, UNF2, UNF4)

Notizen

- Der Dateiname wird analysiert, um die Layer-Anzahl zu extrahieren.
  Beispiel: ".12." bedeutet 12 Layer, ".31." bedeutet 31 Layer.
- Wenn die Layer-Anzahl nicht explizit angegeben ist (z.B. .UNF1),
  wird angenommen, dass die Datei nur eine Schicht enthält.
- Die Elementgröße hängt vom Datentyp ab: UNF0/UNF4 = 4 Bytes,
  UNF1 = 1 Byte, UNF2 = 2 Bytes.

Extrahiert den Dateinamen einschließlich der Dateiendung aus dem vollständigen Pfad

    filename = filepath.split("/")[-1]

Bestimmt die Anzahl der Schichten (Layer) aus dem Dateinamen

    after_first_dot = filename.split(".")[1]
    if after_first_dot.startswith("U"):
        nlayers = 1
    else:
        nlayers = int(after_first_dot)

Bestimmt die Größe eines einzelnen Datenelementes in Bytes basierend auf dem Dateityp

    file_extension = filename.split(".")[-1]
    if file_extension[-1] == "0":
        Size = 4
    elif file_extension[-1] == "1":
        Size = 1
    elif file_extension[-1] == "2":
        Size = 2
    elif file_extension[-1] == "4":
        Size = 4
    else:
        print("The file is not an UNF type of file.")

Speichert den Dateityp als String (z.B. UNF0, UNF1, UNF2 oder UNF4)

    Type = str(file_extension)

    return nlayers, Size, Type


import struct
def ReadBin(filepath: str, ng: int) -> list:
Liest Binärdaten aus einer UNF-Datei mit Typkonvertierung.
Liest Binärdaten aus einer UNF-Datei und konvertiert diese je nach
Dateityp in entsprechende Python-Objekte (Float oder Integer).

Parameter

Parameter Typ Beschreibung
filepath str Pfad zur UNF-Binärdatei ng : int Anzahl der pro Layer zu lesenden Zellen (nur Landflächen)

Rückgabe

list Liste der konvertierten Datenwerte

Notizen

- UNF0: 32-Bit Gleitkommazahlen im Big-Endian-Format
- UNF1: 8-Bit-Ganzzahlen
- UNF2: 16-Bit-Ganzzahlen
- UNF4: 32-Bit-Ganzzahlen
    Type = getFileInfo(filepath)[2]
    nbytes = getFileInfo(filepath)[1]
    nlayers = getFileInfo(filepath)[0]
    data = []
    with open(filepath, "rb") as file:
        for i in range(ng*nlayers):
            try:
                b = file.read(nbytes)

Konvertierung je nach Dateityp

                if Type == "UNF0":
                    data.append(struct.unpack('>f', b)[0])
                elif Type == "UNF1":
                    data.append(int.from_bytes(b , byteorder = "big"))
                elif Type == "UNF2":
                    data.append(int.from_bytes(b , byteorder = "big"))
                elif Type == "UNF4":
                    data.append(int.from_bytes(b , byteorder = "big"))
                else:
                    data.append(int.from_bytes(b , byteorder = "big"))
            except EOFError:
                break
    return data

import numpy as np

def FileToArray(file_path: str, GC_path: str, GR_path: str, continent_index: int,
                 continent_list: list = None, No_of_cells_list: list = None,
                 nrows_list: list = None, ncol_list: list = None) -> np.ndarray:
Konvertiert eine UNF-Binärdatei in ein mehrdimensionales NumPy-Array.
Die Funktion liest Binärdaten aus einer UNF-Datei sowie Gitter-Koordinaten (GC)
und Gitter-Zeilen (GR) aus separaten Dateien. Die Daten werden gemäß der
Gitterkoordinaten in ein räumliches Array umgewandelt, das für jeden Layer
eine 2D-Gitterstruktur aufweist. Kontinent-Metadaten werden aus config.yaml
geladen (über Paths_and_params), können aber durch optionale Parameter
überschrieben werden.

Parameter

Parameter Typ Beschreibung
file_path str Pfad zur UNF-Binärdatei GC_path : str Pfad zur Datei mit Gitter-Spalten-Koordinaten GR_path : str Pfad zur Datei mit Gitter-Zeilen-Koordinaten continent_index : int Index des Kontinents in den Metadaten-Listen continent_list : list, optional Liste der Kontinentnamen (wird aus Paths_and_params geladen, falls None) No_of_cells_list : list, optional Liste der Zellanzahl pro Kontinent (wird aus Paths_and_params geladen, falls None) nrows_list : list, optional Liste der Anzahl von Reihen pro Kontinent (wird aus Paths_and_params geladen, falls None) ncol_list : list, optional Liste der Anzahl von Spalten pro Kontinent (wird aus Paths_and_params geladen, falls None)

Rückgabe

np.ndarray 3D NumPy-Array mit Dimensionen (layer, row, col)

    if continent_list is None:
        continent_list = PP.name
    if No_of_cells_list is None:
        No_of_cells_list = PP.ng
    if nrows_list is None:
        nrows_list = PP.nrow
    if ncol_list is None:
        ncol_list = PP.ncol

    nlayers = getFileInfo(file_path)[0]
    cellsInContinent = No_of_cells_list[continent_index]
    file_values = ReadBin(file_path, cellsInContinent)
    GC = ReadBin(GC_path, cellsInContinent)
    GR = ReadBin(GR_path, cellsInContinent)
    ndarray = np.zeros((nlayers, nrows_list[continent_index], ncol_list[continent_index]))

Die Gitterkoordinaten werden verwendet, um Werte aus der eindimensionalen Liste in die richtige Position des mehrdimensionalen Arrays zu platzieren. Der Index wird um 1 reduziert, da die GC/GR-Koordinaten bei 1 beginnen, numpy-Indizierung aber bei 0 beginnt.

    for i in range(cellsInContinent):
        for j in range(nlayers):
            Ncol = GC[i]
            Nrow = GR[i]
            item = file_values[nlayers*(i-1) + j + nlayers]
            ndarray[j][Nrow-1][Ncol-1] = item
    return ndarray

from osgeo import gdal
from osgeo import osr

def ArrayToRaster(array: np.ndarray, outfile: str, xmin: float, ymax: float,
                  xres: float, yres: float, ncols: int, nrows: int,
                  xrot: float = 0, yrot: float = 0, epsg: int = 4326) -> None:
Schreibt ein NumPy-Array als georeferenziertes GeoTIFF-Rasterbild.
Die Funktion nutzt GDAL, um ein 2D-Array in ein GeoTIFF-Format zu exportieren
und dabei vollständige räumliche Referenzierungsinformationen zu bewahren.
Die Geotransformation verknüpft Pixel-Koordinaten mit geografischen Koordinaten.

Parameter

Parameter Typ Beschreibung
array np.ndarray 2D NumPy-Array mit Rasterdaten outfile : str Ausgabepfad für die GeoTIFF-Datei xmin : float Minimale x-Koordinate (westliche Grenze) ymax : float Maximale y-Koordinate (nördliche Grenze) xres : float Auflösung in x-Richtung (Pixelgröße) yres : float Auflösung in y-Richtung (Pixelgröße) ncols : int Anzahl der Spalten nrows : int Anzahl der Zeilen xrot : float, optional Rotationsparameter für x-Richtung (Standard: 0) yrot : float, optional Rotationsparameter für y-Richtung (Standard: 0) epsg : int, optional EPSG-Code für das Koordinatensystem (Standard: 4326 = WGS84)

Geotransformation definiert die Abbildung zwischen Pixel- und Weltkoordinaten

    geotransform=(xmin, xres, xrot, ymax, yrot, -yres)
    output_raster = gdal.GetDriverByName('GTiff').Create(outfile, ncols, nrows, 1, gdal.GDT_Float32)
    output_raster.SetGeoTransform(geotransform)

Räumliche Referenzierung aus EPSG-Code laden und auf den Raster anwenden

    srs = osr.SpatialReference()
    srs.ImportFromEPSG(epsg)
    output_raster.SetProjection(srs.ExportToWkt())

Array in das erste (und einzige) Rasterband schreiben und auf Speicher leeren

    output_raster.GetRasterBand(1).WriteArray(array)
    output_raster.FlushCache()
    return

def writeBin(filename: str, text: list, nbytes: int = 1) -> None:
Schreibt eine Liste von Ganzzahlen als Big-Endian-Binärdaten in eine Datei.

Parameter

Parameter Typ Beschreibung
filename str Pfad zur Ausgabedatei text : list Liste von Ganzzahlen, die geschrieben werden sollen nbytes : int, optional Anzahl der Bytes pro Wert (Standard: 1)
    with open(filename, 'wb') as file:
        for b in text:
            file.write(b.to_bytes(nbytes, byteorder = "big"))
    return

def Path_Concatenate(first: str, year: int, last: str) -> str:
Verbindet einen Pfadpräfix, ein Jahr und ein Pfadsuffix zu einem Pfad.

Parameter

Parameter Typ Beschreibung
first str Anfangsteil des Pfades year : int Jahr als Ganzzahl (wird zu String konvertiert) last : str Endsuffix des Pfades

Rückgabe

str Vollständiger Pfad als String

    result = first + str(year) + last
    return result