from decimal import Decimal
import os
import sqlite3
import sympy as sp
from sympy import MutableDenseMatrix
from typing import Any

from Berechnungen import Einheitenumrechnung

class Datenbank_anlegen:
    """Legt die SQLite-Datenbank für die Ausgleichungsrechnung an.

    Die Klasse erstellt die Datenbankdatei nur dann, wenn am angegebenen Pfad noch
    keine Datenbank existiert. In diesem Fall werden die Tabellen und Constraints
    per SQL-Skript angelegt.

    Angelegte Tabellen:
    - Netzpunkte,
    - Beobachtungen,
    - Instrumente,
    - Genauigkeiten,
    - Varianzkomponentenschaetzung.

    :ivar pfad_datenbank: Pfad zur SQLite-Datenbankdatei.
    :vartype pfad_datenbank: str
    """
    def __init__(self, pfad_datenbank: str) -> None:
        """Initialisiert den Datenbankpfad und legt die Datenbank bei Bedarf an.

        Beim Erstellen der Instanz wird db_anlegen() aufgerufen. Existiert die
        Datenbankdatei unter dem übergebenen Pfad bereits, erfolgt keine Änderung.

        :param pfad_datenbank: Pfad zur SQLite-Datenbankdatei.
        :type pfad_datenbank: str
        :return: None
        :rtype: None
        """
        self.pfad_datenbank = pfad_datenbank
        self.db_anlegen()

    def db_anlegen(self) -> None:
        """Legt die SQLite-Datenbank an, sofern die Datei nicht existiert.

        Prüft, ob die Datenbankdatei am Pfad vorhanden ist. Falls nicht, wird eine neue
        SQLite-Datenbank erstellt.
        Anschließend werden die Änderungen committet und die Verbindung geschlossen.

        :return: None
        :rtype: None
        """
        if not os.path.exists(self.pfad_datenbank):
            con = sqlite3.connect(self.pfad_datenbank)
            cursor = con.cursor()
            cursor.executescript("""CREATE TABLE Netzpunkte (
                            punktnummer         TEXT(10),
                            naeherungx_lh       NUMERIC(9,3),
                            naeherungy_lh       NUMERIC(7,3),
                            naeherungz_lh       NUMERIC(8,3),
                            naeherungz_lh_niv   NUMERIC(8,3),
                            naeherungx_us       NUMERIC(9,3),
                            naeherungy_us       NUMERIC(7,3),
                            naeherungz_us       NUMERIC(8,3),
                            normalhoehe_hfp     NUMERIC(5,3),
                            datumskoordinate_x  INTEGER DEFAULT 0,
                            datumskoordinate_y  INTEGER DEFAULT 0, 
                            datumskoordinate_z  INTEGER DEFAULT 0,
                            stabw_vorinfo_x     NUMERIC(3, 8),
                            stabw_vorinfo_y     NUMERIC(3, 8),
                            stabw_vorinfo_z     NUMERIC(3, 8),
                            CONSTRAINT pk_Netzpunkte PRIMARY KEY (punktnummer)
                            );
                           """);
            cursor.executescript("""CREATE TABLE Beobachtungen(
                                    beobachtungenID                     INTEGER,
                                    beobachtungsgruppeID                INTEGER,
                                    punktnummer_sp                      TEXT(10),
                                    punktnummer_zp                      TEXT(10),
                                    instrumenteID                       INTEGER,
                                    tachymeter_richtung                 NUMERIC(8, 6),
                                    tachymeter_richtung_ausschalten     INTEGER DEFAULT 0,
                                    tachymeter_zenitwinkel_roh          NUMERIC(8, 6),
                                    tachymeter_zenitwinkel              NUMERIC(8, 6),
                                    tachymeter_zenitwinkel_ausschalten  INTEGER DEFAULT 0,
                                    tachymeter_distanz_roh              NUMERIC(8, 4),
                                    tachymeter_distanz                  NUMERIC(8, 4),
                                    tachymeter_distanz_auschalten       INTEGER DEFAULT 0,
                                    tachymeter_instrumentenhoehe        NUMERIC(3, 8),
                                    tachymeter_prismenhoehe             NUMERIC(3, 8),
                                    gnss_bx                             NUMERIC(7, 4),
                                    gnss_bx_ausschalten                 INTEGER             DEFAULT 0,
                                    gnss_by                             NUMERIC(7, 4),
                                    gnss_by_ausschalten                 INTEGER             DEFAULT 0,
                                    gnss_bz                             NUMERIC(7, 4),
                                    gnss_bz_ausschalten                 INTEGER             DEFAULT 0,
                                    gnss_s0                             NUMERIC(1, 8),
                                    gnss_cxx                            NUMERIC(1, 8),
                                    gnss_cxy                            NUMERIC(1, 8),
                                    gnss_cxz                            NUMERIC(1, 8),
                                    gnss_cyy                            NUMERIC(1, 8),
                                    gnss_cyz                            NUMERIC(1, 8),
                                    gnss_czz                            NUMERIC(1, 8),
                                    niv_dh                              NUMERIC(8, 6),
                                    niv_strecke                         NUMERIC(8, 6),
                                    niv_anz_standpkte                   INTEGER,
                                    niv_ausschalten                     INTEGER             DEFAULT 0,
                                    dateiname                           TEXT(200),
                                    CONSTRAINT pk_Beobachtungen PRIMARY KEY (beobachtungenID),
                                    CONSTRAINT fk_Beobachtungen_Netzpunktesp FOREIGN KEY (punktnummer_sp) REFERENCES Netzpunkte(punktnummer),
                                    CONSTRAINT fk_Beobachtungen_Netzpunktezp FOREIGN KEY (punktnummer_zp) REFERENCES Netzpunkte(punktnummer),
                                    CONSTRAINT fk_Beobachtungen_Instrumente FOREIGN KEY (instrumenteID) REFERENCES Instrumente(instrumenteID)
                                    );
                                 """);
            cursor.executescript("""CREATE TABLE Instrumente(
                                    instrumenteID   INTEGER,
                                    typ             TEXT(200),
                                    name            TEXT(200),
                                    CONSTRAINT pk_Instrumente PRIMARY KEY (instrumenteID)
                                    );
                                    """);
            cursor.executescript("""CREATE TABLE Genauigkeiten(
                                    genauigkeitenID             INTEGER,
                                    instrumenteID               INTEGER,
                                    beobachtungsart             TEXT(25),
                                    stabw_apriori_konstant      NUMERIC(3, 8),
                                    stabw_apriori_streckenprop  NUMERIC(3, 8),
                                    CONSTRAINT pk_Genauigkeiten PRIMARY KEY (genauigkeitenID),
                                    CONSTRAINT fk_Genauigkeiten_Instrumente FOREIGN KEY (instrumenteID) REFERENCES Instrumente(instrumenteID)
                                    );
                                    """);
            cursor.executescript("""CREATE TABLE Varianzkomponentenschaetzung(
                                    varianzkomponenteID                     INTEGER,
                                    instrumenteID                           INTEGER,
                                    beobachtungsgruppe                      TEXT(50),
                                    varianz_varianzkomponentenschaetzung    NUMERIC(3, 16) DEFAULT 1,
                                    CONSTRAINT pk_Varianzkomponentenschaetzung PRIMARY KEY (varianzkomponenteID),
                                    CONSTRAINT fk_Varianzkomponentenschaetzung_Instrumente FOREIGN KEY (instrumenteID) REFERENCES Instrumente(instrumenteID)
                                    );
                                    """)
            con.commit()
            cursor.close()
            con.close()

class Datenbankzugriff:
    """Zugriff auf die SQLite-Datenbank des Ausgleichungsprogramms.

    Die Klasse stellt Methoden zur Verfügung für:

    - Schreiben in die Datenbank durch alle set_* Methoden
    - Lesen aus der Datenbank durch alle get_* Methoden

    :ivar pfad_datenbank: Pfad zur SQLite-Datenbankdatei.
    :vartype pfad_datenbank: str
    """
    def __init__(self, pfad_datenbank: str) -> None:
        """Initialisiert den Datenbankzugriff.

        :param pfad_datenbank: Pfad zur SQLite-Datenbankdatei.
        :type pfad_datenbank: str
        """
        self.pfad_datenbank = pfad_datenbank

    def set_koordinaten(self, dict_koordinaten: dict, koordinatenart: str) -> None:
        """Schreibt Koordinaten in die Tabelle Netzpunkte.

        Die Koordinaten werden aus `dict_koordinaten` gelesen und abhängig von der koordinatenart
        in die entsprechenden Felder geschrieben. Es werden nur Einträge aktualisiert, deren
        Zielspalten aktuell ``NULL`` sind.

        :param dict_koordinaten: Dictionary mit Punktnummer → (X, Y, Z).
        :type dict_koordinaten: dict
        :param koordinatenart: Art der Koordinaten ("naeherung_lh" für die Koordinaten im lokalen Horizontsystem aus der Tachymetermessung oder "naeherung_us" für die Näherungskoordinaten der Netzpunkte im geozentrisch-kartesischen-system).
        :type koordinatenart: str
        :return: None
        :rtype: None
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()

        daten = []

        for punktnummer, wert in dict_koordinaten.items():
            daten.append((
                float(wert[0]),
                float(wert[1]),
                float(wert[2]),
                str(punktnummer)
            ))
        # Der Import für die Koordinatenart "naeherung_lh" ist im Zuge möglicher Erweiterungen zu implementieren.
        if koordinatenart == "naeherung_lh":
            pass
        elif koordinatenart == "naeherung_us":
            cursor.executemany(f"""UPDATE Netzpunkte SET naeherungx_us = ?, naeherungy_us = ?, naeherungz_us = ? WHERE punktnummer = ? AND naeherungx_us IS NULL AND naeherungy_us IS NULL AND naeherungz_us IS NULL""", daten)

        con.commit()
        cursor.close()
        con.close()

    def set_instrument(self, typ: str, name: str, liste_beobachtungsarten: list) -> None:
        """Fügt ein Instrument hinzu und initialisiert Varianzkomponenten.

        Ist das Instrument (typ, name) noch nicht in der Datenbank vorhanden, wird es in die Tabelle Instrumente eingefügt.
        Zusätzlich werden Einträge in Varianzkomponentenschaetzung für die angegebenen
        Beobachtungsarten erzeugt. Falls die Varianzkomponentenschätzung noch leer ist, wird
        ein zusätzlicher Eintrag für die Beobachtungsgruppe "Anschlusspunkte" angelegt.

        :param typ: Instrumenttyp.
        :type typ: str
        :param name: Instrumentname.
        :type name: str
        :param liste_beobachtungsarten: Liste der Beobachtungsgruppen zur Initialisierung.
        :type liste_beobachtungsarten: list
        :return: None
        :rtype: None
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_instrumente = cursor.execute("SELECT * FROM Instrumente WHERE typ = ? AND name =?", (typ, name)).fetchall()
        if liste_instrumente == []:
            cursor.execute(
                "INSERT INTO Instrumente (typ, name) VALUES (?, ?)", (typ, name)
            )
            id_instrument = cursor.lastrowid
            print(f"Das Instrument {name} wurde erfolgreich hinzugefügt.")

            if self.get_varianzkomponentenschaetzung() == []:
                cursor.execute(
                    "INSERT INTO Instrumente (typ, name) VALUES (?, ?)", ("Anschlusspunkte", "lA")
                )
                id_instrument_anschlusspunkte = cursor.lastrowid

                cursor.execute(
                    "INSERT INTO Varianzkomponentenschaetzung (instrumenteID, beobachtungsgruppe) VALUES (?, ?)",
                    (id_instrument_anschlusspunkte, "Anschlusspunkte")
                )

            for beobachtungsart in liste_beobachtungsarten:
                    cursor.execute(
                        "INSERT INTO Varianzkomponentenschaetzung (instrumenteID, beobachtungsgruppe) VALUES (?, ?)",
                        (id_instrument, beobachtungsart)
                    )
        else:
            id_instrument = cursor.execute(
                "SELECT instrumenteID FROM Instrumente WHERE typ = ? AND name =?", (typ, name))
            print(f"Das Instrument {name} ist bereits in der Datenbank vorhanden.\nEs hat die ID {id_instrument.fetchone()[0]}")
        con.commit()
        cursor.close()
        con.close()

    def set_genauigkeiten(self, instrumenteID: int, beobachtungsart: str, stabw_apriori_konstant: float = None,
                          stabw_apriori_streckenprop: float = None) -> None:
        """Speichert a-priori Genauigkeiten für die Beobachtungsgruppen pro Instrument.

        Prüft, ob instrumenteID existiert und ob mindestens eine Genauigkeitsangabe übergeben wurde.
        Je nach Beobachtungsart werden Einheitenumrechnungen durchgeführt (z. B. mgon → rad bzw. mm → m).
        Der Eintrag wird nur ergänzt, wenn in Genauigkeiten kein identischer Datensatz vorhanden ist.

        :param instrumenteID: ID des Instruments in der Tabelle Instrumente.
        :type instrumenteID: int
        :param beobachtungsart: Bezeichnung der Beobachtungsart.
        :type beobachtungsart: str
        :param stabw_apriori_konstant: Konstanter Anteil der Standardabweichung (optional).
        :type stabw_apriori_konstant: float | None
        :param stabw_apriori_streckenprop: Streckenproportionaler Anteil der Standardabweichung (optional).
        :type stabw_apriori_streckenprop: float | None
        :return: None
        :rtype: None
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()

        # Überprüfung auf vorhandensein der Instrumentes in der Tabelle Instrumente
        instrumentenname = cursor.execute("SELECT name FROM Instrumente WHERE instrumenteID = ?",
                                          (instrumenteID, )).fetchone()
        if instrumentenname is None:
            print(
                f"Die InstumentenID {instrumenteID} ist in der Datenbank nicht vorhanden. Bitte zuerst das Instrument hinzufügen.")
            cursor.close()
            con.close()
            return
        instrumentenname = instrumentenname[0]

        # Überprüfung, ob Genauigkeitsinformationen vom Benutzer übergeben wurden.
        if stabw_apriori_konstant is None and stabw_apriori_streckenprop is None:
            print(
                "Es wurden keine Genauigkeiten importiert. Bitte stabw_apriori_konstant und / oder stabw_apriori_streckenprop angeben.")
            cursor.close()
            con.close()
            return

        # Umrechnen der Einheiten in Radiant und Meter
        if beobachtungsart == "Tachymeter_Richtung" or beobachtungsart == "Tachymeter_Zenitwinkel" :
            stabw_apriori_konstant = Einheitenumrechnung.mgon_to_rad_Decimal(stabw_apriori_konstant)

        if beobachtungsart == "Tachymeter_Strecke" or beobachtungsart == "Geometrisches_Nivellement":
            stabw_apriori_konstant = Einheitenumrechnung.mm_to_m(stabw_apriori_konstant)

        if isinstance(stabw_apriori_konstant, Decimal):
            stabw_apriori_konstant = float(stabw_apriori_konstant)

        if isinstance(stabw_apriori_streckenprop, Decimal):
            stabw_apriori_streckenprop = float(stabw_apriori_streckenprop)

        # Überprüfen, ob die Genauigkeitsinformation für die jeweilige Beobachtungsart des Instruments bereits vorhanden ist. Wenn nein, wird die Benutzereingabe in die Datenbank gespeichert.
        sql = "SELECT 1 FROM Genauigkeiten WHERE instrumenteID = ? AND beobachtungsart = ?"
        params = [instrumenteID, beobachtungsart]

        if stabw_apriori_konstant is None:
            sql += " AND stabw_apriori_konstant IS NULL"
        else:
            sql += " AND stabw_apriori_konstant = ?"
            params.append(stabw_apriori_konstant)

        if stabw_apriori_streckenprop is None:
            sql += " AND stabw_apriori_streckenprop IS NULL"
        else:
            sql += " AND stabw_apriori_streckenprop = ?"
            params.append(stabw_apriori_streckenprop)

        liste_genauigkeiten = cursor.execute(sql, tuple(params)).fetchall()

        if liste_genauigkeiten == []:
            if stabw_apriori_konstant is not None and stabw_apriori_streckenprop is not None:
                cursor.execute(
                    "INSERT INTO Genauigkeiten (instrumenteID, beobachtungsart, stabw_apriori_konstant, stabw_apriori_streckenprop) VALUES (?, ?, ?, ?)",
                    (instrumenteID, beobachtungsart, stabw_apriori_konstant, stabw_apriori_streckenprop)
                )
                print(
                    f"Die Genauigkeitsangabe für die Beobachtungsart {beobachtungsart} des Instrumentes {instrumentenname} wurde erfolgreich hinzugefügt.")

            elif stabw_apriori_konstant is None and stabw_apriori_streckenprop is not None:
                cursor.execute(
                    "INSERT INTO Genauigkeiten (instrumenteID, beobachtungsart, stabw_apriori_streckenprop) VALUES (?, ?, ?)",
                    (instrumenteID, beobachtungsart, stabw_apriori_streckenprop)
                )
                print(
                    f"Die Genauigkeitsangabe für die Beobachtungsart {beobachtungsart} des Instrumentes {instrumentenname} wurde erfolgreich hinzugefügt.")

            elif stabw_apriori_streckenprop is None and stabw_apriori_konstant is not None:
                cursor.execute(
                    "INSERT INTO Genauigkeiten (instrumenteID, beobachtungsart, stabw_apriori_konstant) VALUES (?, ?, ?)",
                    (instrumenteID, beobachtungsart, stabw_apriori_konstant)
                )
                print(
                    f"Die Genauigkeitsangabe für die Beobachtungsart {beobachtungsart} des Instrumentes {instrumentenname} wurde erfolgreich hinzugefügt.")

        else:
            print("Die Genauigkeitsangabe ist bereits in der Datenbank vorhanden.")

        con.commit()
        cursor.close()
        con.close()

    def set_datumskoordinaten(self, liste_datumskoordinaten_x: list, liste_datumskoordinaten_y: list,
                              liste_datumskoordinaten_z: list,
                              liste_datumskoordinaten_x_y_z: list) -> None:
        """Setzt Datumskoordinaten für Netzpunkte unter Berücksichtigung von Vorinformationen.

        Für die übergebenen Punktlisten werden die Felder datumskoordinate_x, datumskoordinate_y
        und / oder datumskoordinate_z auf 1 gesetzt. Ein Setzen erfolgt nur, wenn zu der jeweiligen Koordinate
        eine Vorinformation zur Standardabweichung (stabw_vorinfo_*) vorhanden ist. Eine 1 bedeutet,
        dass dieser Punkt für die Datumdefinition verwendet wird.

        :param liste_datumskoordinaten_x: Liste der Punktnummern mit Datumsdefinition in X.
        :type liste_datumskoordinaten_x: list
        :param liste_datumskoordinaten_y: Liste der Punktnummern mit Datumsdefinition in Y.
        :type liste_datumskoordinaten_y: list
        :param liste_datumskoordinaten_z: Liste der Punktnummern mit Datumsdefinition in Z.
        :type liste_datumskoordinaten_z: list
        :param liste_datumskoordinaten_x_y_z: Liste der Punktnummern mit Datumsdefinition in X, Y und Z.
        :type liste_datumskoordinaten_x_y_z: list
        :return: None
        :rtype: None
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()

        # Überprüfen, ob Genauigkeitsinformationen zu den Punkten in der Datenbank gespeichert sind. Ist dies der Fall, wird eine 1 in das entsprechende Tupel geschrieben.
        liste_stabw_vorinfo_x = [str(row[0]).strip() for row in cursor.execute(
            "SELECT punktnummer FROM Netzpunkte WHERE stabw_vorinfo_x IS NOT NULL").fetchall()]
        liste_stabw_vorinfo_y = [str(row[0]).strip() for row in cursor.execute(
            "SELECT punktnummer FROM Netzpunkte WHERE stabw_vorinfo_y IS NOT NULL").fetchall()]
        liste_stabw_vorinfo_z = [str(row[0]).strip() for row in cursor.execute(
            "SELECT punktnummer FROM Netzpunkte WHERE stabw_vorinfo_z IS NOT NULL").fetchall()]
        liste_stabw_vorinfo_x_y_z = [str(row[0]).strip() for row in cursor.execute(
            "SELECT punktnummer FROM Netzpunkte WHERE stabw_vorinfo_x IS NOT NULL AND stabw_vorinfo_y IS NOT NULL AND stabw_vorinfo_z IS NOT NULL").fetchall()]

        if liste_datumskoordinaten_x != []:
                for punktnummer in liste_datumskoordinaten_x:
                    punktnummer = str(punktnummer).strip()
                    if punktnummer in liste_stabw_vorinfo_x:
                        cursor.execute(f"UPDATE Netzpunkte SET datumskoordinate_x = 1 WHERE punktnummer = ? AND stabw_vorinfo_x IS NOT NULL", (str(punktnummer),))
                    else:
                        print(f"Die X-Koordinate des Punktes {punktnummer} wurde nicht in eine Datumskoordinate geändert, weil keine Vorinformationen zur Standardabweichung der X-Koordinate des Punktes vorliegen. Diese bitte zuerst erfassen und Datumsdefinition wiederholen.")

        if liste_datumskoordinaten_y != []:
            for punktnummer in liste_datumskoordinaten_y:
                punktnummer = str(punktnummer).strip()
                if punktnummer in liste_stabw_vorinfo_y:
                    cursor.execute(f"UPDATE Netzpunkte SET datumskoordinate_y = 1 WHERE punktnummer = ? AND stabw_vorinfo_y IS NOT NULL", (str(punktnummer),))
                else:
                    print(f"Die Y-Koordinate des Punktes {punktnummer} wurde nicht in eine Datumskoordinate geändert, weil keine Vorinformationen zur Standardabweichung der Y-Koordinate des Punktes vorliegen. Diese bitte zuerst erfassen und Datumsdefinition wiederholen.")

        if liste_datumskoordinaten_z != []:
            for punktnummer in liste_datumskoordinaten_z:
                punktnummer = str(punktnummer).strip()
                if punktnummer in liste_stabw_vorinfo_z:
                    cursor.execute(f"UPDATE Netzpunkte SET datumskoordinate_z = 1 WHERE punktnummer = ? AND stabw_vorinfo_z IS NOT NULL", (str(punktnummer),))
                else:
                    print(f"Die Z-Koordinate des Punktes {punktnummer} wurde nicht in eine Datumskoordinate geändert, weil keine Vorinformationen zur Standardabweichung der Z-Koordinate des Punktes vorliegen. Diese bitte zuerst erfassen und Datumsdefinition wiederholen.")

        if liste_datumskoordinaten_x_y_z != []:
            for punktnummer in liste_datumskoordinaten_x_y_z:
                punktnummer = str(punktnummer).strip()
                if punktnummer in liste_stabw_vorinfo_x_y_z:
                    cursor.execute(f"UPDATE Netzpunkte SET datumskoordinate_x = 1, datumskoordinate_y = 1, datumskoordinate_z = 1 WHERE punktnummer = ? AND stabw_vorinfo_x IS NOT NULL AND stabw_vorinfo_y IS NOT NULL AND stabw_vorinfo_z IS NOT NULL", (str(punktnummer),))
                else:
                    print(f"Der Punkt {punktnummer} wurde nicht in einen Datumspunkt geändert, weil nicht alle Vorinformationen zur Standardabweichung der Koordinaten des Punktes vorliegen. Diese bitte zuerst erfassen und Datumsdefinition wiederholen.")

        con.commit()
        cursor.close()
        con.close()

    def set_datumskoordinaten_to_neupunkte(self, liste_datumskoordinaten_x: list, liste_datumskoordinaten_y: list,
                                           liste_datumskoordinaten_z: list,
                                           liste_datumskoordinaten_x_y_z: list) -> None:
        """Setzt Datumskoordinaten für angegebene Punkte wieder zurück.

        Für die übergebenen Punktlisten werden die Felder datumskoordinate_x, datumskoordinate_y
        und / oder datumskoordinate_z auf 0 gesetzt. Dadurch werden diese in der Ausgleichung wieder
        als Neupunkte behandelt und werden nicht für die Datumsdefinition verwendet.

        :param liste_datumskoordinaten_x: Liste der Punktnummern, deren X-Datumsfestlegung zurückgesetzt wird.
        :type liste_datumskoordinaten_x: list
        :param liste_datumskoordinaten_y: Liste der Punktnummern, deren Y-Datumsfestlegung zurückgesetzt wird.
        :type liste_datumskoordinaten_y: list
        :param liste_datumskoordinaten_z: Liste der Punktnummern, deren Z-Datumsfestlegung zurückgesetzt wird.
        :type liste_datumskoordinaten_z: list
        :param liste_datumskoordinaten_x_y_z: Liste der Punktnummern, deren X-, Y- und Z-Datumsfestlegung zurückgesetzt werden.
        :type liste_datumskoordinaten_x_y_z: list
        :return: None
        :rtype: None
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()

        # Überprüfung, ob eine Benutzereingabe in der entsprechenden Liste vorgenommen wurde. Wenn ja, wird ein UPDATE in der Datenbank durchgeführt.
        if liste_datumskoordinaten_x != []:
            for punktnummer in liste_datumskoordinaten_x:
                punktnummer = str(punktnummer).strip()
                cursor.execute(
                    f"UPDATE Netzpunkte SET datumskoordinate_x = 0 WHERE punktnummer = ?",
                    (str(punktnummer),))

        if liste_datumskoordinaten_y != []:
            for punktnummer in liste_datumskoordinaten_y:
                punktnummer = str(punktnummer).strip()
                cursor.execute(
                    f"UPDATE Netzpunkte SET datumskoordinate_y = 0 WHERE punktnummer = ?",
                    (str(punktnummer),))

        if liste_datumskoordinaten_z != []:
            for punktnummer in liste_datumskoordinaten_z:
                punktnummer = str(punktnummer).strip()
                cursor.execute(
                    f"UPDATE Netzpunkte SET datumskoordinate_z = 0 WHERE punktnummer = ?",
                    (str(punktnummer),))

        if liste_datumskoordinaten_x_y_z != []:
            for punktnummer in liste_datumskoordinaten_x_y_z:
                punktnummer = str(punktnummer).strip()
                cursor.execute(
                    f"UPDATE Netzpunkte SET datumskoordinate_x = 0, datumskoordinate_y = 0, datumskoordinate_z = 0 WHERE punktnummer = ?",
                    (str(punktnummer),))

        con.commit()
        cursor.close()
        con.close()

    def set_normalhoehe_hfp(self, liste_normalhoehe_hfp: list) -> LiteralString | str:
        """Speichert Normalhöhen von amtlichen Höhenfestpunkten (HFP) in Netzpunkte.

        Existiert die Punktnummer bereits in der Tabelle Netzpunkte, wird das Attribut normalhoehe_hfp aktualisiert.
        Andernfalls wird ein neuer Datensatz mit Punktnummer und Normalhöhe eingefügt.

        :param liste_normalhoehe_hfp: Liste mit HFP-Informationen (u. a. Punktnummer und Normalhöhe).
        :type liste_normalhoehe_hfp: list
        :return: Zusammenfassung der durchgeführten Aktualisierungen bzw. Hinweis, falls keine Daten übergeben wurden.
        :rtype: str
        """
        liste_hfp_in_db = self.get_normalhoehe_hfp()
        if liste_normalhoehe_hfp != []:
            con = sqlite3.connect(self.pfad_datenbank)
            cursor = con.cursor()
            liste_punktnummern_in_db = [str(r[0]) for r in cursor.execute(
                "SELECT punktnummer FROM Netzpunkte"
            ).fetchall()]
            ausgaben = []
            for hfp in liste_normalhoehe_hfp:
                if str(hfp[0]) in liste_punktnummern_in_db:
                    cursor.execute("UPDATE Netzpunkte SET normalhoehe_hfp = ? WHERE punktnummer = ?",
                        (hfp[3], hfp[0])
                    )
                    ausgaben.append(f"Der HFP {hfp[0]} wurde aktualisiert.")
                else:
                    cursor.execute(
                        "INSERT INTO Netzpunkte (punktnummer, normalhoehe_hfp) VALUES (?, ?)",
                        (hfp[0], hfp[1])
                    )
                    ausgaben.append(f"Der HFP {hfp[0]} wurde neu hinzugefügt.")

            con.commit()
            cursor.close()
            con.close()
            return "\n".join(ausgaben)
        else:
            return f"Es wurden keine neuen Normalhöhen übergeben. Folgende Normalhöhen sind in der Datenbank enthalten: {liste_hfp_in_db}"

    def set_beobachtung_ausschalten(self, dict_beobachtung_ausschalten: dict) -> None:
        """Schaltet ausgewählte Beobachtungen für die Ausgleichung aus.

        Die zu deaktivierenden Beobachtungen werden über dict_beobachtung_ausschalten übergeben.
        Der Schlüssel wird ausgewertet und das zugehörige *_ausschalten-Tupel in der Datenbank
        auf 1 gesetzt (z. B. Richtungen, Zenitwinkel, Strecken, GNSS-Komponenten, Geometrisches Nivellement).
        Dadurch wird die jeweilige Beobachtung in allen folgenden Iterationen nicht mehr berücksichtigt.

        :param dict_beobachtung_ausschalten: Dictionary zur Steuerung, welche Beobachtungen deaktiviert werden, in der Form: {Beobachtung: "beobachtung_ausschalten"} zum ausschalten und {Beobachtung: ""} fürs beibehalten.
        :type dict_beobachtung_ausschalten: dict
        :return: None
        :rtype: None
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()

        for beobachtung, ausschalten in dict_beobachtung_ausschalten.items():
            # Nur in die Schleife gehen, wenn im Value des Dicts "beobachtung_ausschalten" enthalten ist.
            if ausschalten == "beobachtung_ausschalten":
                beobachtung_gesplittet = beobachtung.split("_")

                # Distanz
                if beobachtung_gesplittet[1] == "SD":
                    try:
                        cursor.execute(f"""UPDATE Beobachtungen SET tachymeter_distanz_auschalten = 1 
                        WHERE beobachtungenID = ? AND beobachtungsgruppeID = ? AND punktnummer_sp = ? AND punktnummer_zp = ?""",
                                       (beobachtung_gesplittet[0], beobachtung_gesplittet[2], beobachtung_gesplittet[3], beobachtung_gesplittet[4]))
                    except:
                        print(f"Die Beobachtung {beobachtung} konnte aufgrund eines Fehlers nicht ausgeschaltet werden.")
                # Richtungen
                if beobachtung_gesplittet[1] == "R":
                    try:
                        cursor.execute(f"""UPDATE Beobachtungen SET tachymeter_richtung_ausschalten = 1 
                        WHERE beobachtungenID = ? AND beobachtungsgruppeID = ? AND punktnummer_sp = ? AND punktnummer_zp = ?""",
                                       (beobachtung_gesplittet[0], beobachtung_gesplittet[2], beobachtung_gesplittet[3], beobachtung_gesplittet[4]))
                    except:
                        print(f"Die Beobachtung {beobachtung} konnte aufgrund eines Fehlers nicht ausgeschaltet werden.")

                # Zenitwinkel
                if beobachtung_gesplittet[1] == "ZW":
                    try:
                        cursor.execute(f"""UPDATE Beobachtungen SET tachymeter_zenitwinkel_ausschalten = 1 
                        WHERE beobachtungenID = ? AND beobachtungsgruppeID = ? AND punktnummer_sp = ? AND punktnummer_zp = ?""",
                                       (beobachtung_gesplittet[0], beobachtung_gesplittet[2], beobachtung_gesplittet[3], beobachtung_gesplittet[4]))
                    except:
                        print(f"Die Beobachtung {beobachtung} konnte aufgrund eines Fehlers nicht ausgeschaltet werden.")

                if beobachtung_gesplittet[1] == "gnssbx":
                    try:
                        cursor.execute(f"""UPDATE Beobachtungen SET gnss_bx_ausschalten = 1 
                        WHERE beobachtungenID = ? AND punktnummer_sp = ? AND punktnummer_zp = ?""",
                                       (beobachtung_gesplittet[0], beobachtung_gesplittet[2], beobachtung_gesplittet[3]))
                    except:
                        print(f"Die Beobachtung {beobachtung} konnte aufgrund eines Fehlers nicht ausgeschaltet werden.")

                #GNSS-Basislinien
                if beobachtung_gesplittet[1] == "gnssby":
                    try:
                        cursor.execute(f"""UPDATE Beobachtungen SET gnss_by_ausschalten = 1 
                        WHERE beobachtungenID = ? AND punktnummer_sp = ? AND punktnummer_zp = ?""",
                                       (beobachtung_gesplittet[0], beobachtung_gesplittet[2], beobachtung_gesplittet[3]))
                    except:
                        print(f"Die Beobachtung {beobachtung} konnte aufgrund eines Fehlers nicht ausgeschaltet werden.")

                if beobachtung_gesplittet[1] == "gnssbz":
                    try:
                        cursor.execute(f"""UPDATE Beobachtungen SET gnss_bz_ausschalten = 1 
                        WHERE beobachtungenID = ? AND punktnummer_sp = ? AND punktnummer_zp = ?""",
                                       (beobachtung_gesplittet[0], beobachtung_gesplittet[2], beobachtung_gesplittet[3]))
                    except:
                        print(f"Die Beobachtung {beobachtung} konnte aufgrund eines Fehlers nicht ausgeschaltet werden.")

                # Geometrisches Nivellement
                if beobachtung_gesplittet[1] == "niv":
                    try:
                        cursor.execute(f"""UPDATE Beobachtungen SET niv_ausschalten = 1 
                        WHERE beobachtungenID = ? AND punktnummer_sp = ? AND punktnummer_zp = ?""",
                                       (beobachtung_gesplittet[0], beobachtung_gesplittet[2], beobachtung_gesplittet[3]))
                    except:
                        print(f"Die Beobachtung {beobachtung} konnte aufgrund eines Fehlers nicht ausgeschaltet werden.")
        print("Die ausgewählten Beobachtungen werden für die folgenden Iterationen ausgeschaltet.")
        con.commit()
        cursor.close()
        con.close()

    def set_varianzkomponente(self, liste_varianzkomponten_anpassen: list) -> None:
        """Passt Varianzkomponenten für die nächste Iteration an.

        Für jede angegebene Varianzkomponente wird der aktuelle Datenbankwert mit einem vom Benutzer übergebenen
        Faktor multipliziert und zurückgeschrieben.

        :param liste_varianzkomponten_anpassen: Liste mit Einträgen (instrumenteID, beobachtungsgruppe, faktor).
        :type liste_varianzkomponten_anpassen: list
        :return: None
        :rtype: None
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        if liste_varianzkomponten_anpassen != []:
            for varianzkomponente in liste_varianzkomponten_anpassen:
                # Faktor für die Varianzkomponentenschätzung der aktuellen Iteration aus der Datenbank abfragen
                varianz_db = cursor.execute(f"SELECT varianz_varianzkomponentenschaetzung FROM Varianzkomponentenschaetzung WHERE instrumenteID = ? AND beobachtungsgruppe = ?",
                    (varianzkomponente[0], varianzkomponente[1])).fetchone()[0]

                # Faktor für die Varianzkompontenschätzung aus der Datenbank mit der Benutzereingabe multiplizieren und das Produkt in der Datenbank für die nächste Iteration speichern.
                cursor.execute(
                    f"UPDATE Varianzkomponentenschaetzung SET varianz_varianzkomponentenschaetzung = ? WHERE instrumenteID = ? AND beobachtungsgruppe = ?",
                    (varianz_db * varianzkomponente[2], varianzkomponente[0], varianzkomponente[1]))
            print(f"Folgende Varianzkomponente wurde für die nächste Iteration gespeichert: {liste_varianzkomponten_anpassen}.")
        con.commit()
        cursor.close()
        con.close()

    def get_koordinaten(self, koordinatenart: str, ausgabeart: str = "Dict") -> dict[Any, MutableDenseMatrix] | None:
        """Liest Koordinaten aus der Tabelle Netzpunkte.

        Abhängig von der koordinatenart werden die entsprechenden Koordinatenspalten gelesen.
        Es werden nur Datensätze berücksichtigt, bei denen X, Y und Z nicht NULL sind.
        Bei ausgabeart="Dict" wird ein Dictionary in der Form {Punktnummer : sympy.Matrix([X, Y, Z])} zurückgegeben.

        :param koordinatenart: Art der Koordinaten ("naeherung_lh" für Koordinaten im lokalen Horizontsystem aus dem Tachymeterimport oder "naeherung_us" für Näherungskoordinaten im geozentrisch-kartesischen System).
        :type koordinatenart: str
        :param ausgabeart: Ausgabeformat (Standard: "Dict").
        :type ausgabeart: str
        :return: Dictionary mit Punktnummer → Koordinatenmatrix oder None (abhängig von ausgabeart).
        :rtype: dict | None
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()

        # Festlegen der Attribute, welche je nach Koordinatenart aus der Datenbank abgefragt werden sollen
        if koordinatenart == "naeherung_lh":
            values = "punktnummer, naeherungx_lh, naeherungy_lh, naeherungz_lh"
        elif koordinatenart == "naeherung_us":
            values = "punktnummer, naeherungx_us, naeherungy_us, naeherungz_us"

        # SQL-Abfrage
        liste_koordinaten = cursor.execute(f"""
        SELECT {values} FROM Netzpunkte;
        """).fetchall()
        cursor.close()
        con.close()

        # Nur ausgeben, wenn für einen Punkt X, Y und Z Koordinaten vorhanden sind
        liste_koordinaten = [
            koordinate for koordinate in liste_koordinaten
            if koordinate[1] is not None and koordinate[2] is not None and koordinate[3] is not None
        ]

        # Aktuell ist nur die Ausgabeart "Dict" implementiert. Weitere Ausgabearten sind im Zuge möglicher erweiterungen zu implementieren.
        if ausgabeart == "Dict":
            return {
                koordinate[0]: sp.Matrix([
                    sp.Float(str(koordinate[1])),
                    sp.Float(str(koordinate[2])),
                    sp.Float(str(koordinate[3]))
                ])
                for koordinate in liste_koordinaten
            }

    def get_normalhoehe_hfp(self) -> list[Any]:
        """Liest alle Normalhöhen von amtlichen Höhenfestpunkten (HFP) aus der Tabelle Netzpunkte.

        :return: Liste mit Tupeln (punktnummer, normalhoehe_hfp) für alle Punkte mit gesetzter Normalhöhe.
        :rtype: list
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_hfp = cursor.execute("SELECT punktnummer, normalhoehe_hfp FROM Netzpunkte WHERE normalhoehe_hfp IS NOT NULL").fetchall()
        cursor.close()
        con.close()
        return liste_hfp


    def get_instrument_liste(self, typ: str) -> list:
        """Liest Instrumente eines gegebenen Typs aus der Tabelle Instrumente.

        Gibt eine Liste der gefundenen Instrumente zurück. Falls keine Instrumente vorhanden sind,
        wird eine Textausgabe mit verfügbaren Typen zurückgegeben.

        :param typ: Instrumenttyp.
        :type typ: str
        :return: Liste der Instrumente oder Hinweistext, falls keine Instrumente gefunden wurden.
        :rtype: list | str
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_instrumente = cursor.execute("SELECT * FROM Instrumente WHERE typ = ?", (typ,)).fetchall()
        liste_typen = cursor.execute("SELECT DISTINCT typ FROM Instrumente").fetchall()
        cursor.close()
        con.close()
        if liste_instrumente == []:
            liste_instrumente = f"Kein Instrument vom Typ {typ} gefunden. Folgende Typen stehen aktuell zur Auswahl: {liste_typen}"
        return liste_instrumente

    def get_genauigkeiten_dict(self) -> dict[Any, Any]:
        """Liest alle Genauigkeiten aus der Tabelle Genauigkeiten und gibt diese als Dictionary zurück.

        :return: Dictionary {genauigkeitenID : restliche Tabellenwerte}.
        :rtype: dict
        """
        dict = {}
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_genauigkeiten = cursor.execute("SELECT * FROM Genauigkeiten").fetchall()
        for genauigkeit in liste_genauigkeiten:
            dict[genauigkeit[0]] = genauigkeit[1:]
        cursor.close()
        con.close()
        return dict

    def get_instrumenteID_beobachtungenID_dict(self) -> dict[Any, Any]:
        """Liest die Zuordnung BeobachtungenID → InstrumenteID aus der Tabelle Beobachtungen.

        :return: Dictionary {beobachtungenID : instrumenteID}.
        :rtype: dict
        """
        dict = {}
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_beobachtungen_instrumente = cursor.execute("SELECT beobachtungenID, instrumenteID FROM Beobachtungen").fetchall()
        for i in liste_beobachtungen_instrumente:
            dict[i[0]] = i[1]
        cursor.close()
        con.close()
        return dict

    def get_puntknummern_beobachtungen_tachymeter(self, beobachtungsart: str) -> list[Any]:
        """Liest Beobachtungen (ID, Gruppe, Standpunkt, Zielpunkt) für Tachymeterbeobachtungen.

        Es werden nur Beobachtungen berücksichtigt, bei denen die angegebene Beobachtungsart
        nicht NULL ist. Zudem werden zusätzlich werden nur Beobachtungen übergeben, die nicht
        ausgeschaltet werden.

        :param beobachtungsart: Name der Beobachtungsspalte.
        :type beobachtungsart: str
        :return: Liste von Tupeln (beobachtungenID, beobachtungsgruppeID, punktnummer_sp, punktnummer_zp).
        :rtype: list
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        sql_ausdruck = f"SELECT beobachtungenID, beobachtungsgruppeID, punktnummer_sp, punktnummer_zp FROM Beobachtungen WHERE {beobachtungsart} IS NOT NULL"
        if beobachtungsart == "tachymeter_distanz":
            sql_ausdruck += " AND tachymeter_distanz_auschalten = 0"
        elif beobachtungsart == "tachymeter_richtung":
            sql_ausdruck += " AND tachymeter_richtung_ausschalten = 0"
        elif beobachtungsart == "tachymeter_zenitwinkel":
            sql_ausdruck += " AND tachymeter_zenitwinkel_ausschalten = 0"
        liste_beobachtungen = cursor.execute(sql_ausdruck).fetchall()
        cursor.close()
        con.close()
        return liste_beobachtungen

    def get_beobachtungen_from_beobachtungenid(self) -> list[Any]:
        """Liest Tachymeterbeobachtungen aus der Tabelle ``Beobachtungen``.

        Es werden nur Datensätze mit gesetzter Tachymeter-Richtung berücksichtigt.

        :return: Liste von Tupeln (punktnummer_sp, punktnummer_zp, beobachtungenID, beobachtungsgruppeID, tachymeter_richtung, tachymeter_zenitwinkel, tachymeter_distanz).
        :rtype: list
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_beobachtungen = cursor.execute(f"SELECT punktnummer_sp, punktnummer_zp, beobachtungenID, beobachtungsgruppeID, tachymeter_richtung, tachymeter_zenitwinkel, tachymeter_distanz FROM Beobachtungen WHERE tachymeter_richtung IS NOT NULL").fetchall()
        cursor.close()
        con.close()
        return liste_beobachtungen

    def get_beobachtungen_gnssbasislinien(self) -> list[Any]:
        """Liest GNSS-Basislinienbeobachtungen inklusive Kovarianzangaben.

        Es werden nur Datensätze zurückgegeben, bei denen alle benötigten GNSS-Felder (bx, by, bz, s0
        und Kovarianzelemente) nicht NULL sind.

        :return: Liste von Tupeln mit GNSS-Basisliniendaten und Kovarianzelementen.
        :rtype: list
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_beobachtungen = cursor.execute(f"SELECT beobachtungenID, punktnummer_sp, punktnummer_zp, gnss_bx, gnss_by, gnss_bz, gnss_s0, gnss_cxx, gnss_cxy, gnss_cxz, gnss_cyy, gnss_cyz, gnss_czz FROM Beobachtungen WHERE gnss_bx IS NOT NULL AND gnss_by IS NOT NULL AND gnss_bz IS NOT NULL AND gnss_s0 IS NOT NULL AND gnss_cxx IS NOT NULL AND gnss_cxy IS NOT NULL AND gnss_cxz IS NOT NULL AND gnss_cyy IS NOT NULL AND gnss_cyz IS NOT NULL AND gnss_czz IS NOT NULL").fetchall()
        cursor.close()
        con.close()
        return liste_beobachtungen

    def get_beobachtungen_nivellement(self) -> list[Any]:
        """Liest Nivellementbeobachtungen aus der Tabelle Beobachtungen.

        Es werden nur Datensätze zurückgegeben, bei denen alle Nivellementfelder Daten enthalten
        und niv_ausschalten = 0 ist, also diese Beobachtung in der Ausgleichung verwendet wird.

        :return: Liste von Tupeln (beobachtungenID, punktnummer_sp, punktnummer_zp, niv_dh, niv_strecke, niv_anz_standpkte).
        :rtype: list
        """

        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_beobachtungen = cursor.execute(f"SELECT beobachtungenID, punktnummer_sp, punktnummer_zp, niv_dh, niv_strecke, niv_anz_standpkte FROM Beobachtungen WHERE niv_dh IS NOT NULL AND niv_strecke IS NOT NULL AND niv_anz_standpkte IS NOT NULL AND niv_ausschalten = 0").fetchall()
        cursor.close()
        con.close()
        return liste_beobachtungen

    def get_datumskoordinate(self) -> list[Any]:
        """Liest die aktuell gesetzten Datumskoordinaten aus der Tabelle Netzpunkte.

        Die Rückgabe erfolgt als Liste von Symbolnamen der Form "X<punktnummer>", "Y<punktnummer>"
        und "Z<punktnummer>".

        :return: Liste der Datumskoordinatenbezeichner.
        :rtype: list
        """
        # Abragen der Koordinaten mit einer 1 im Attribut datumskoordinate_*, da diese für die Lagerung verwendet werden sollen.
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_datumskoordinate_x = cursor.execute(
            f"SELECT punktnummer FROM Netzpunkte WHERE datumskoordinate_x = 1").fetchall()
        liste_datumskoordinate_y = cursor.execute(
            f"SELECT punktnummer FROM Netzpunkte WHERE datumskoordinate_y = 1").fetchall()
        liste_datumskoordinate_z = cursor.execute(
            f"SELECT punktnummer FROM Netzpunkte WHERE datumskoordinate_z = 1").fetchall()

        cursor.close()
        con.close()

        # Hinzufügen der jeweiligen Punktnummer als Symbol mit X, Y oder Z-Präfix zur Rückgabeliste
        liste_datumskoordinaten = []
        if liste_datumskoordinate_x != []:
            for datumskoordinate in liste_datumskoordinate_x:
                datumskoordinate = str(datumskoordinate[0]).strip()
                liste_datumskoordinaten.append(f"X{datumskoordinate}")
        if liste_datumskoordinate_y != []:
            for datumskoordinate in liste_datumskoordinate_y:
                datumskoordinate = str(datumskoordinate[0]).strip()
                liste_datumskoordinaten.append(f"Y{datumskoordinate}")
        if liste_datumskoordinate_z != []:
            for datumskoordinate in liste_datumskoordinate_z:
                datumskoordinate = str(datumskoordinate[0]).strip()
                liste_datumskoordinaten.append(f"Z{datumskoordinate}")

        return liste_datumskoordinaten

    def get_stabw_AA_Netzpunkte(self) -> dict[Any, Any]:
        """Liest Vorinformationen zur Standardabweichung der Netzpunkte aus der Tabelle Netzpunkte.

        Die Rückgabe erfolgt als Dictionary mit Schlüsseln der Form "StabwAA_X<punktnummer>",
        "StabwAA_Y<punktnummer>" und "StabwAA_Z<punktnummer>".

        :return: Dictionary der Vorinformationen zur Standardabweichung je Koordinate.
        :rtype: dict
        """
        # Abfragen aller Punktnummern und s0 apriori Vorinformationen aus der Datenbank, wenn die Vorinformationen vorliegen.
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_stabwAA_x = cursor.execute(f"SELECT punktnummer, stabw_vorinfo_x FROM Netzpunkte WHERE stabw_vorinfo_x IS NOT NULL").fetchall()
        liste_stabwAA_y = cursor.execute(
            f"SELECT punktnummer, stabw_vorinfo_y FROM Netzpunkte WHERE stabw_vorinfo_y IS NOT NULL").fetchall()
        liste_stabwAA_z = cursor.execute(
            f"SELECT punktnummer, stabw_vorinfo_z FROM Netzpunkte WHERE stabw_vorinfo_z IS NOT NULL").fetchall()

        cursor.close()
        con.close()

        # Erstellen des Rückgabedictionaries.
        dict_stabwAA = {}
        for stabwAA_x in liste_stabwAA_x:
            punktnummer = str(stabwAA_x[0]).strip()
            wert = stabwAA_x[1]
            dict_stabwAA[f"StabwAA_X{punktnummer}"]= wert

        for stabwAA_y in liste_stabwAA_y:
            punktnummer = str(stabwAA_y[0]).strip()
            wert = stabwAA_y[1]
            dict_stabwAA[f"StabwAA_Y{punktnummer}"]= wert

        for stabwAA_z in liste_stabwAA_z:
            punktnummer = str(stabwAA_z[0]).strip()
            wert = stabwAA_z[1]
            dict_stabwAA[f"StabwAA_Z{punktnummer}"]= wert

        return dict_stabwAA

    def get_gnss_beobachtungen_punktnummern(self, gnss_komponente: str) -> list[Any]:
        """Liest GNSS-Beobachtungen (ID, Standpunkt, Zielpunkt) für eine GNSS-Komponente.

        Es werden nur Datensätze berücksichtigt, bei denen die geforderte Komponente sowie alle
        zugehörigen GNSS-Genauigkeitsangaben (s0 und Kovarianzelemente) vorhanden sind.
        Zusätzlich wird das jeweilige Ausschalt-Attribut berücksichtigt.

        :param gnss_komponente: Name der GNSS-Basislinienkomponente (z. B. "gnss_bx", "gnss_by", "gnss_bz").
        :type gnss_komponente: str
        :return: Liste von Tupeln (beobachtungenID, punktnummer_sp, punktnummer_zp).
        :rtype: list
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        sql = f"""SELECT beobachtungenID, punktnummer_sp, punktnummer_zp
                      FROM Beobachtungen
                      WHERE {gnss_komponente} IS NOT NULL
                        AND gnss_s0 IS NOT NULL
                        AND gnss_cxx IS NOT NULL AND gnss_cxy IS NOT NULL AND gnss_cxz IS NOT NULL
                        AND gnss_cyy IS NOT NULL AND gnss_cyz IS NOT NULL AND gnss_czz IS NOT NULL"""

        if gnss_komponente == "gnss_bx":
            sql += " AND gnss_bx_ausschalten = 0"
        elif gnss_komponente == "gnss_by":
            sql += " AND gnss_by_ausschalten = 0"
        elif gnss_komponente == "gnss_bz":
            sql += " AND gnss_bz_ausschalten = 0"

        liste_gnss_beobachtungen = cursor.execute(sql).fetchall()
        cursor.close()
        con.close()
        return liste_gnss_beobachtungen

    def get_nivellement_beobachtungen_punktnummern(self) -> list[Any]:
        """Liest Beobachtungen vom geometrischen Nivellement (ID, Standpunkt, Zielpunkt) aus der Tabelle Beobachtungen.

        Es werden nur Datensätze berücksichtigt, bei denen alle Attribute gesetzt sind
        und niv_ausschalten = 0 ist.

        :return: Liste von Tupeln (beobachtungenID, punktnummer_sp, punktnummer_zp).
        :rtype: list
        """

        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_nivellement_beobachtungen = cursor.execute(f"SELECT beobachtungenID, punktnummer_sp, punktnummer_zp FROM Beobachtungen WHERE niv_dh IS NOT NULL AND niv_strecke IS NOT NULL AND niv_anz_standpkte IS NOT NULL AND niv_ausschalten = 0").fetchall()
        cursor.close()
        con.close()
        return liste_nivellement_beobachtungen

    def get_varianzkomponentenschaetzung(self) -> list[Any]:
        """Liest alle Einträge der Varianzkomponentenschätzung aus der Datenbank.

        :return: Liste von Tupeln (varianzkomponenteID, instrumenteID, beobachtungsgruppe, varianz_varianzkomponentenschaetzung).
        :rtype: list
        """
        con = sqlite3.connect(self.pfad_datenbank)
        cursor = con.cursor()
        liste_varianzkomponenten = cursor.execute(f"SELECT varianzkomponenteID, instrumenteID, beobachtungsgruppe, varianz_varianzkomponentenschaetzung FROM Varianzkomponentenschaetzung").fetchall()
        cursor.close()
        con.close()
        return liste_varianzkomponenten


