diff --git a/docs/apps/info.rst b/docs/apps/info.rst index 8791d1dc..173ed20a 100644 --- a/docs/apps/info.rst +++ b/docs/apps/info.rst @@ -12,6 +12,10 @@ typer indhold i FIRE databasen. :prog: fire info punkt :nested: full +.. click:: fire.cli.info:koordinater + :prog: fire info koordinater + :nested: full + .. click:: fire.cli.info:sag :prog: fire info sag :nested: full diff --git a/src/fire/api/firedb/hent.py b/src/fire/api/firedb/hent.py index a41b838a..75b559b2 100644 --- a/src/fire/api/firedb/hent.py +++ b/src/fire/api/firedb/hent.py @@ -19,6 +19,7 @@ PunktSamling, PunktInformation, PunktInformationType, + PunktInformationTypeAnvendelse, GeometriObjekt, Grafik, Observation, @@ -145,6 +146,50 @@ def hent_punkter( return result + def hent_punkter_med_flag( + self, infotype: PunktInformationType, inkluder_historiske: bool = False + ) -> list[Punkt]: + """ + Returnerer alle punkter der har en given FLAG-infotype + + Bruges fx til at hente alle 5D-punkterne (NET:5D) eller alle punkter på Færøerne + (REGION:FO). Sættes `inkluder_historiske` til True, så søges der også iblandt + afregistrede attributter. + + Der kan potentielt returneres mange punkter med denne, så brug den klogt. Det + frarådes derfor at søge på de meget almindelige infotyper, som fx. REGION:DK eller + ATTR:højdefikspunkt, da disse vil resultere i ca. 650.000 hhv. 250.000 punkter. + + Hvis intet punkt findes udsendes en NoResultFound exception. + """ + + if not infotype.anvendelse == PunktInformationTypeAnvendelse.FLAG: + raise ValueError(f"Ugyldig infotype, fik {infotype}.") + + query = ( + self.session.query(Punkt) + .options( + joinedload(Punkt.geometriobjekter), + joinedload(Punkt.koordinater), + ) + .join(PunktInformation) + .join(PunktInformationType) + .filter( + PunktInformationType.name == infotype.name, + Punkt._registreringtil == None, # NOQA + ) + ) + + if not inkluder_historiske: + query = query.filter(PunktInformation._registreringtil == None) + + result = query.all() + + if not result: + raise NoResultFound(f"Ingen punkter med attributten {infotype.name} fundet") + + return result + def hent_punkter_fra_uuid_liste(self, uuids: List[str]): """ Hent alle punkter med punkt ID'er matchende listen `uuids`. diff --git a/src/fire/api/model/punkttyper.py b/src/fire/api/model/punkttyper.py index 2d134285..cb7b32be 100644 --- a/src/fire/api/model/punkttyper.py +++ b/src/fire/api/model/punkttyper.py @@ -190,7 +190,7 @@ class Punkt(FikspunktregisterObjekt): @reconstructor def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._identer = [] + self._identer: list[Ident] = [] def _populer_identer(self): """ @@ -265,11 +265,9 @@ def tabtgået(self) -> bool: return True return False - def _hent_ident_af_type(self, identtype: str) -> str: - numre = [] - for punktinfo in self.punktinformationer: - if punktinfo.infotype.name == identtype and not punktinfo.registreringtil: - numre.append(punktinfo.tekst) + def _hent_ident_af_type(self, identtype: Ident.IdentType) -> str: + self._populer_identer() + numre = [ident.tekst for ident in self._identer if ident._type == identtype] if numre: return sorted(numre)[0] @@ -291,7 +289,7 @@ def gældende_koordinat(self, srid: str) -> Koordinat: @property def landsnummer(self) -> str: - _landsnummer = self._hent_ident_af_type("IDENT:landsnr") + _landsnummer = self._hent_ident_af_type(Ident.IdentType.LANDSNR) if _landsnummer: return _landsnummer @@ -300,11 +298,11 @@ def landsnummer(self) -> str: @property def gnss_navn(self) -> str: - return self._hent_ident_af_type("IDENT:GNSS") + return self._hent_ident_af_type(Ident.IdentType.GNSS) @property def jessennummer(self) -> str: - return self._hent_ident_af_type("IDENT:jessen") + return self._hent_ident_af_type(Ident.IdentType.JESSEN) def __lt__(self, other: Punkt) -> bool: return self.landsnummer < other.landsnummer diff --git a/src/fire/cli/info/__init__.py b/src/fire/cli/info/__init__.py new file mode 100644 index 00000000..2b563c2e --- /dev/null +++ b/src/fire/cli/info/__init__.py @@ -0,0 +1,29 @@ +import click + + +@click.group() +def info(): + """ + Information om objekter i FIRE + """ + pass + + +# Udstil kommandoer +from fire.cli.info._info import ( + punkt, + punktsamling, + srid, + obstype, + infotype, + sag, + sagsevent, +) +from fire.cli.info._koordinater import ( + koordinater +) + +# ... og visse hjælpefunktioner som bruges andre steder +from fire.cli.info._info import ( + punktinforapport, +) diff --git a/src/fire/cli/info.py b/src/fire/cli/info/_info.py similarity index 99% rename from src/fire/cli/info.py rename to src/fire/cli/info/_info.py index e9ddac80..112185ba 100644 --- a/src/fire/cli/info.py +++ b/src/fire/cli/info/_info.py @@ -13,6 +13,7 @@ from pyproj.exceptions import CRSError import fire.cli +from fire.cli.info import info from fire.ident import klargør_ident_til_søgning from fire.io.geojson import skriv_sagsrapport_geojson from fire.api.model import ( @@ -37,14 +38,6 @@ DATE_FORMAT = "%d-%m-%Y" -@click.group() -def info(): - """ - Information om objekter i FIRE - """ - pass - - def observation_linje(obs: Observation) -> str: if obs.observationstypeid > 2: return "" diff --git a/src/fire/cli/info/_koordinater.py b/src/fire/cli/info/_koordinater.py new file mode 100644 index 00000000..839ee52f --- /dev/null +++ b/src/fire/cli/info/_koordinater.py @@ -0,0 +1,418 @@ +import click +from pyproj import CRS, Transformer +from pyproj.exceptions import ProjError +from sqlalchemy.orm.exc import NoResultFound + +import fire.cli +from fire.cli.info import info +from fire.api.model import Punkt, Srid +from fire.api.model.tidsserier import GNSSTidsserie +from fire.cli.ts import ( + _print_tidsserier, +) +from fire.cli.ts.plot_ts import ( + plot_data, + plot_tidsserie, +) +from fire.ident import klargør_identer_til_søgning, bestem_identtype + + +@info.command() +@click.option( + "-s", + "--source", + "sources", + type=str, + default="DVR90", + callback=lambda ctx, param, val: [s.strip() for s in val.split(",")], + help="Vælg koordinat-type der skal udtrækkes. Vælg flere srider, ved at angive dem som en kommasepareret liste.", +) +@click.option( + "-t", + "--target", + type=str, + default="", + help="Vælg koordinatsystem der skal transformeres til.", +) +@click.option( + "-H", + "--historik", + is_flag=True, + default=False, + help="Udskriv også ikke-gældende (historiske) elementer", +) +@click.option( + "-P", + "--plot", + is_flag=True, + default=False, + help="Plot koordinater som tidsserie. Hvis denne er sat, skal ``--historik/-H`` også være sat.", +) +@click.option( + "-f", + "--fil", + required=False, + type=click.Path(), + help="Skriv den udtrukne tidsserie til Excel fil.", +) +@click.argument( + "objekter", + required=True, + type=str, + nargs=-1, +) +@fire.cli.default_options() +def koordinater( + objekter: list[str], + sources: list[str], + target: str, + historik: bool, + plot: bool, + fil: click.Path, + **kwargs, +) -> None: + """ + Udtræk koordinater for ét eller flere punkter + + Med **OBJEKTER** angives en liste af punkter eller fikspunktsnet som skal udtrækkes. + Vælg fx. NET:5D, NET:DMI, eller NET:RTKCONNECT for at udtrække koordinater til hhv. + 5D-punkter, DMIs vandstandsmålere eller RTKConnects referencestationer. Se en fuld + liste over nettene med ``fire info infotype NET``. + + Som standard udtrækkes den gældende DVR90-kote for hvert af de valgte punkter. + + Med parameteren ``--source/-s`` kan angives en alternativ koordinat-type som skal + udtrækkes. Der kan vælges alle srid'er som findes i FIRE, se en liste med ``fire info + srid``. Dog kan der ikke vælges tidsserie-koordinattyper. Hertil skal anvendes ``fire + ts hts`` og ``fire ts gnss``. + + Parameteren ``--target/-t`` bruges til at angive referencerammen som de udtrukne + koordinater skal transformeres til og vises i. Den valgte referenceramme skal kunne + fortolkes af PROJ. + + Der kan vælges flere ``--source`` koordinatsystemer ved at angive dem som en + kommasepareret liste. De vil da alle blive transformeret til det valgte ``--target`` + koordinatsystem. I tilfælde af man ikke har valgt noget ``--target``, så sættes target + til det sidst angivne ``source``-system. + + Programmet gør opmærksom på, hvis transformationerne er af typerne "noop" (no + operation) eller "ballpark". Begge typer angiver, at der reelt ikke foretages en + transformation, og at de resulterende koordinater derfor ikke er nøjagtige. + + Med parameteren ``--historik/-H`` tilvælges historiske koordinater. Dette er fravalgt + som standard, så der derved kun udtrækkes gældende koordinater. + + Hvis historik er tilvalgt, kan man med ``--plot/-P`` desuden få vist de resulterende + tidsserier i et simpelt plot. Ønskes mere avancerede plots og anden analyse, kan + resultaterne gemmes som excel-fil ved at angive et outputfilnavn med ``--fil/-f``. + + + \b **EKSEMPLER** + + Vis gældende DVR90-kote for ALBN, BFYR og CHAK:: + + fire info koordinater ALBN BFYR CHAK + + Vis historiske DVR90-koter for ALBN, BFYR og CHAK:: + + fire info koordinater ALBN BFYR CHAK -H + + Vis historiske DVR90-koter for ALBN, BFYR og CHAK og gem som fil:: + + fire info koordinater ALBN BFYR CHAK -H -f "ABC.xlsx" + + Vis historiske ETRS89-koordinater for ALBN, BFYR og CHAK:: + + fire info koordinater ALBN BFYR CHAK -H -s EPSG:4937 + + Udtræk gældende ETRS89 koordinater for alle GPSNET punkterne og vis dem i UTM32N + + DVR90(2023):: + + fire info koordinater NET:GPSNET -s EPSG:4937 -t EPSG:25832+EPSG:10485 + + Udtræk alle IGb08, IGS14 og IGS20 koordinater for 5D-punkterne, transformer til IGS20 + geografiske koordinater, og plot:: + + fire info koordinater NET:5D -s IGb08,IGS14,IGS20 -t EPSG:10177 -H -P + + For mere info om hvilke transformationer som programmet foretager, kan anvendes + `projinfo` der kaldes med de samme parametre som denne kommando:: + + projinfo -s EPSG:4937 -t EPSG:25832+EPSG:10485 + + """ + if not target: + target = sources[0] + sources, target = oversæt_srid_alias(sources, target) + + transformers = klargør_transformationer(sources, target) + + punkter_identer = håndter_punkter(list(objekter)) + + tidsserier = konstruer_tidsserier(punkter_identer, transformers, historik) + + if not tidsserier: + fire.cli.print( + f"Fejl: Ingen af punkterne har koordinater i det valgte referencesystem.", + fg="red", + ) + raise SystemExit(1) + + _print_tidsserier(tidsserier, fil) + + if not (historik and plot): + return + + # Vi plotter bare tidsseriens x,y,z værdier som de er, (medmindre de er None) da de + # umiddelbart er de eneste vi er sikre på eksisterer. + for ts in tidsserier: + parms = [ + parm + for parm, dim in zip("xyz", [ts.srid.x, ts.srid.y, ts.srid.z]) + if dim is not None + ] + plot_tidsserie(ts, plot_data, parametre=parms, y_enhed="m") + + +def håndter_punkter(objekter: list[str]) -> list[tuple[Punkt, str]]: + """ + Omsæt `objekter` til en liste af punkter. + + `objekter` kan indeholde identer eller navne på "NET"-infotyper, fx. NET:5D. + + Hvert punkt matches med dén ident som ligger tættest på den søgestreng som punktet + blev fundet med, hvilket kan bruges til visning i tabeller, plots etc. + """ + nets = [ + objekter.pop(i) for i, o in enumerate(objekter) if o.upper().startswith("NET:") + ] + identer = objekter + + identer = klargør_identer_til_søgning(identer) + fire.cli.print("Søg i databasen efter punkter til hver ident", fg="yellow") + punkter: list[Punkt] = fire.cli.firedb.hent_punkt_liste( + identer, ignorer_ukendte=False + ) + + # Ingen punkter bliver sprunget over da vi ovenfor har sat ignorer_ukendte=False + # Vi kan derfor antage at punkter og identtyper kommer i samme rækkefølge + identtyper = [bestem_identtype(ident) for ident in identer] + punkter_identer = [ + ( + p, + p._hent_ident_af_type(it) if it is not None else p.ident, + ) # hvis identtypen ikke kunne bestemmes bruger vi bare den almindelige ident + for p, it in zip(punkter, identtyper) + ] + + for net in nets: + infotype = fire.cli.firedb.hent_punktinformationtype(net) + punkter = fire.cli.firedb.hent_punkter_med_flag(infotype) + punkter_identer.extend([(p, p.ident) for p in punkter]) + + punkter_identer = sorted(punkter_identer, key=lambda x: x[1]) + + return punkter_identer + + +def oversæt_srid_alias(sources: list[str], target: str = None) -> tuple[list[str], str]: + """ + Oversæt srid-alias til sridens rigtige navn + + Hvis alias (kortnavn) ikke kan oversættes, returneres inputtet som det blev givet. + """ + srids_med_alias = ( + fire.cli.firedb.session.query(Srid).filter(Srid.kortnavn != None).all() + ) + srids_alias_mapper = {s.kortnavn: s.name for s in srids_med_alias} + + sources = [srids_alias_mapper.get(s, s) for s in sources] + target = srids_alias_mapper.get(target, target) + + return sources, target + + +def klargør_transformationer( + sources: list[str], target: str +) -> dict[Srid, dict[Srid, Transformer]]: + """ + Klargør transformationer fra alle sources til target + + Returnerer en dict-over-dict der mapper alle source-target kombinationer til den + rette transformation, fx.: + { + src_1: { + trg_1: transformation_11 + }, + src_2: { + trg_1: transformation_21, + }, + } + """ + try: + transformers = { + fire.cli.firedb.hent_srid(src := source): { + target: (trans := lav_transformer(source, target)) + } + for source in sources + } + except NoResultFound: + fire.cli.print(f"Fejl: Source-srid '{src}' ikke fundet!", fg="red") + raise SystemExit(1) + + try: + target_srid = fire.cli.firedb.hent_srid(target) + except NoResultFound: + # Konstruér en ny Srid ud fra target_crs + target_srid = Srid_fra_CRS(trans.target_crs, navn=target) + + # Konstruér ny dict med srids som nøgler + transformers = { + src: {target_srid: trans for trg, trans in trg_trans.items()} + for src, trg_trans in transformers.items() + } + return transformers + + +def lav_transformer(s_crs: str | CRS, t_crs: str | CRS) -> Transformer: + """Opret Transformer-objekt""" + if s_crs == t_crs: + return Transformer.from_pipeline("+proj=noop") + + try: + transformer = Transformer.from_crs(crs_from=s_crs, crs_to=t_crs, always_xy=True) + except ProjError as e: + fire.cli.print( + f"Fejl: Kan ikke transformere fra {s_crs} til {t_crs}. Mulig årsag:", + fg="red", + ) + fire.cli.print(e) + raise SystemExit(1) + + if "proj=noop" in transformer.definition: + fire.cli.print( + f'Bemærk: Klargjorde en "noop" transformation fra {s_crs} til {t_crs}', + fg="yellow", + ) + elif "ballpark" in transformer.description.lower(): + fire.cli.print( + f'Bemærk: Klargjorde en "ballpark" transformation fra {s_crs} til {t_crs}', + fg="yellow", + ) + else: + fire.cli.print( + f"Klargjorde transformation fra {s_crs} til {t_crs}.", + ) + + return transformer + +# Her fastsættes dicts til oversættelse af PROJ's interne akse- og enhedsnavne +# til nogle mere kortfattede og danske navne. +danske_akser = { + "Easting": "Easting", + "Northing": "Northing", + "Westing": "Westing", + "Southing": "Southing", + "Geodetic latitude": "Breddegrad", + "Geodetic longitude": "Længdegrad", + "Geocentric X": "X", + "Geocentric Y": "Y", + "Geocentric Z": "Z", + "Ellipsoidal height": "Ellipsoidehøjde", + "Gravity-related height": "Kote", + "Depth": "Dybde", +} +danske_enheder = { + "metre": "[m]", + "degree": "[decimalgrader]", + "degree minute second hemisphere": "[°]", +} + + +def Srid_fra_CRS(crs: CRS, navn: str) -> Srid: + """Oversæt en pyproj.CRS til en Srid""" + + axes = [danske_akser.get(ax.name, ax.name) for ax in crs.axis_info] + units = [danske_enheder.get(ax.unit_name, "") for ax in crs.axis_info] + + akser_enheder = [f"{ax} {un}" for ax, un in zip(axes, units)] + + x, y, z = None, None, None + if len(akser_enheder) == 1: + (z,) = akser_enheder + elif len(akser_enheder) == 2: + x, y = akser_enheder + else: + x, y, z = akser_enheder + + return Srid(name=navn, x=x, y=y, z=z) + + +def konstruer_tidsserier( + punkter_identer: list[tuple[Punkt, str]], + transformers: dict[Srid, dict[Srid, Transformer]], + historik: bool, +) -> list[GNSSTidsserie]: + """ + Konstruér tidsserier til visning i tabeller og plots + + For hvert punkt i listen `punkter_identer` udtrækkes alle koordinater med srid'er + svarende til source_srid-nøglerne givet i `transformers`. + + Koordinaterne transformeres derefter via de givne "transformers", til `target_srid`, + og gemmes i en tidsserie til hvert punkt. + """ + if historik: + historik_filter = lambda k: True + else: + historik_filter = lambda k: k.registreringtil is None + + # Tag target_srid fra første transformation + target_srid = [ + trg for trg_trans in transformers.values() for trg in trg_trans.keys() + ][0] + + # Konstruér tidsserier ud fra punkternes koordinater + tidsserier = [] + for punkt, matchet_ident in punkter_identer: + + tidsserie = GNSSTidsserie( + punkt=punkt, + navn=f"{matchet_ident}", + formål=f"", + srid=target_srid, + ) + + # Hver af punktets koordinater transformeres via dets Srid til det valgte + # output-crs, og tilføjes til tidsserien + koordinater = [] + for k in punkt.koordinater: + if not ( + k.srid in transformers.keys() + and k.fejlmeldt == False + and historik_filter(k) + ): + continue + + # Transformér koordinaterne. Vi ændrer deres x,y,z værdier in-place. Det er + # stadig database-objekter, så man skal IKKE committe noget fra denne session. + # Man vil nok blive reddet af database-triggerne, som forbyder updates, men + # stadig... + k.x, k.y, k.z = transformers[k.srid][target_srid].transform( + k.x or 0.0, k.y or 0.0, k.z or 0.0 + ) + k.transformeret = True + k.srid = target_srid + + koordinater.append(k) + + if not koordinater: + fire.cli.print( + f"Springer {matchet_ident} over. Fandt ingen koordinater.", + ) + continue + + tidsserie.koordinater = sorted(koordinater, key=(lambda k: k.t)) + tidsserier.append(tidsserie) + + return tidsserier diff --git a/src/fire/cli/ts/__init__.py b/src/fire/cli/ts/__init__.py index ce00a6fe..67f0ddcc 100644 --- a/src/fire/cli/ts/__init__.py +++ b/src/fire/cli/ts/__init__.py @@ -1,4 +1,5 @@ from datetime import datetime +import re import click import pandas as pd @@ -17,6 +18,7 @@ Punkt, PunktSamling, Koordinat, + Srid, ) @@ -28,6 +30,29 @@ def ts(): pass +def bestem_labels(srid: Srid): + """Bestem kolonnenavne til en tabel ud fra en Srid""" + + tid = ["t", "decimalår"] + xyz = ["x", "y", "z"] + sxyz = ["sx", "sy", "sz"] + labels_xyz = [srid.x, srid.y, srid.z] + labels_sxyz = ["sx [mm]", "sy [mm]", "sz [mm]"] + + # Fjern de steder hvor srid.x y eller z er None + indekser = [i for i, lab in enumerate(labels_xyz) if lab is not None] + + xyz = [xyz[i] for i in indekser] + sxyz = [sxyz[i] for i in indekser] + labels_xyz = [labels_xyz[i] for i in indekser] + labels_sxyz = [labels_sxyz[i] for i in indekser] + + parms = tid + xyz + sxyz + labels = tid + labels_xyz + labels_sxyz + + return parms, labels + + def _print_tidsserieoversigt( tidsserier: list[Tidsserie], ) -> None: @@ -54,6 +79,7 @@ def foretrukken_ident(ts: Tidsserie): console = Console() console.print(tabel) + def _udtræk_tidsserie( objekt: str, tidsserieklasse: type[Tidsserie], @@ -80,7 +106,9 @@ def _udtræk_tidsserie( srid_filter = lambda ts: True # Prøv først at søge med objekt som søgestreng på tidsserienavn og filtrer på srid - tidsserier = fire.cli.firedb.hent_tidsserier(objekt, tidsserieklasse=tidsserieklasse) + tidsserier = fire.cli.firedb.hent_tidsserier( + objekt, tidsserieklasse=tidsserieklasse + ) tidsserier = [ts for ts in tidsserier if srid_filter(ts)] # Hvis ingen tidsserier, prøver vi med objekt som ident @@ -91,7 +119,11 @@ def _udtræk_tidsserie( raise SystemExit("Punkt eller tidsserie ikke fundet") else: # Udtræk punktets tidsserier og filtrer på ts-type og srid - tidsserier=[ts for ts in punkt.tidsserier if isinstance(ts, tidsserieklasse) and srid_filter(ts)] + tidsserier = [ + ts + for ts in punkt.tidsserier + if isinstance(ts, tidsserieklasse) and srid_filter(ts) + ] if not tidsserier: raise SystemExit("Fandt ingen tidsserier") @@ -100,7 +132,7 @@ def _udtræk_tidsserie( _print_tidsserieoversigt(tidsserier) # Hvis der kun blev fundet én tidsserie så printer vi den - if len(tidsserier)==1: + if len(tidsserier) == 1: _print_tidsserie(tidsserier[0], parametre_alle, parametre, fil) @@ -110,7 +142,7 @@ def _print_tidsserie( parametre: str, fil: click.Path, ): - """Print en tabel over en tidsserie med de givne parametre """ + """Print en tabel over én tidsserie med de givne parametre""" if parametre.lower() == "alle": parametre = ",".join(parametre_alle.keys()) @@ -124,7 +156,70 @@ def _print_tidsserie( overskrifter.append(p) kolonner.append(tidsserie.__getattribute__(parametre_alle[p])) - tabel = Table(*overskrifter, box=box.SIMPLE) + _print_tabel( + overskrifter, + kolonner, + ) + + if not fil: + return + + _gem_tabel(overskrifter, kolonner, fil) + + +def _print_tidsserier( + tidsserier: list[Tidsserie], + fil: click.Path, +): + """ + Print en tabel over flere tidsserier med de givne parametre + + Alle tidsserierne skal have samme Srid + """ + srid = tidsserier[0].srid + if not all(ts.srid == srid for ts in tidsserier): + fire.cli.print("Fejl: Alle tidsserierne skal have samme Srid", fg="red") + raise SystemExit(1) + + overskrifter = ["Navn", "Srid"] + + # Bestem kolonnenavne ud fra Sriden på første Tidsserie + parametre, labels = bestem_labels(srid) + overskrifter.extend(labels) + + kolonner = [[] for i in range(len(overskrifter))] + navne, srider = zip( + *[ + (ts.navn, (ts.srid.kortnavn or ts.srid.name)) + for ts in tidsserier + for _ in range(len(ts)) + ] + ) + kolonner[0] = navne + kolonner[1] = srider + for ts in tidsserier: + for idx, p in enumerate(parametre, 2): + kolonner[idx].extend(ts.__getattribute__(p)) + + _print_tabel( + overskrifter, + kolonner, + ) + + if not fil: + return + + _gem_tabel(overskrifter, kolonner, fil) + + +def _print_tabel(overskrifter: list, kolonner: list[list]): + + # Erstat "[" med "\\[" så console.Print ikke opfatter det der står inde i [parentesen] + # som et "markup tag", se https://rich.readthedocs.io/en/latest/markup.html# + # Tiltænkt steder hvor kolonnen fx hedder "Kote [m]" eller "sz [mm]" + overskrifter = [re.sub(r"\[(?=.*\])", "\\[", o) for o in overskrifter] + + tabel = Table(*overskrifter, box=box.SIMPLE, header_style="") data = list(zip(*kolonner)) def klargør_celle(input): @@ -134,6 +229,7 @@ def klargør_celle(input): return f"{input:.4f}" if not input: return "" + return str(input) for række in data: tabel.add_row( @@ -143,9 +239,8 @@ def klargør_celle(input): console = Console() console.print(tabel) - if not fil: - raise SystemExit +def _gem_tabel(overskrifter: list, kolonner: list[list], fil: click.Path): data = { overskrift: kolonne for (overskrift, kolonne) in zip(overskrifter, kolonner) } diff --git a/src/fire/cli/ts/plot_ts.py b/src/fire/cli/ts/plot_ts.py index df0ccf25..c56fd241 100644 --- a/src/fire/cli/ts/plot_ts.py +++ b/src/fire/cli/ts/plot_ts.py @@ -46,6 +46,7 @@ def plot_tidsserie( ts: Tidsserie, plot_funktion: Callable, parametre: list = ["n", "e", "u"], + ylabels: list = [], y_enhed: str = "m", ): """ @@ -54,27 +55,25 @@ def plot_tidsserie( Denne funktion håndterer figuropsætningen, og kalder ``plot_funktion``, som forventes at foretage selve plottingen af data. """ - n_parm = min(len(parametre), 3) + # Plot max 3 parametre + parametre = parametre[:3] + n_parm = len(parametre) + + if not ylabels: + ylabels = [TS_PLOTTING_LABELS.get(parm, parm) for parm in parametre] skalafaktor = ENHEDER_SKALAFAKTOR[y_enhed] ax = plt.figure() plt.suptitle(ts.navn) - for i, parm in enumerate(parametre, start=1): - if i > 3: - break + for i, (parm, ylabel) in enumerate(zip(parametre, ylabels), start=1): y = [skalafaktor * yy for yy in getattr(ts, parm)] - try: - label = TS_PLOTTING_LABELS[parm] - except KeyError: - label = parm - ax = plt.subplot(int(f"{n_parm}{1}{i}")) plot_funktion(ts.decimalår, y, y_enhed=y_enhed) - plt.ylabel(f"{label} [{y_enhed}]") + plt.ylabel(f"{ylabel} [{y_enhed}]") plt.grid() # Vis kun xlabel for nederste subplot @@ -330,9 +329,9 @@ def plot_data(x: list, y: list, **kwargs): plt.plot( x, y, - ".", - markersize=4, - color="black", + "o", + markersize=6, + color="blue", ) @@ -452,7 +451,7 @@ def plot_tidsserier( markersize=p[0].get_markersize() * 2, ) - if len(y)<2: + if len(y) < 2: plt.text( x[-1] + 2e-1, y[-1], diff --git a/src/fire/ident.py b/src/fire/ident.py index 48aba885..a0b68225 100644 --- a/src/fire/ident.py +++ b/src/fire/ident.py @@ -6,6 +6,8 @@ import re from typing import Iterable +from fire.api.model.punkttyper import Ident + # Vær mindre pedantisk mht. foranstillede nuller hvis identen er et landsnummer LANDSNUMMERMØNSTER = re.compile("^[0-9]*-[0-9]*-[0-9]*$") @@ -146,6 +148,20 @@ def klargør_ident_til_søgning(ident: str) -> str: return ident +def bestem_identtype(ident: str) -> Ident.IdentType: + if ( + kan_være_landsnummer(ident) or + kan_være_købstadsnummer(ident) or + kan_være_vandstandsbræt(ident) + ): + return Ident.IdentType.LANDSNR + + if kan_være_gnssid(ident): + return Ident.IdentType.GNSS + + if kan_være_gi_nummer(ident): + return Ident.IdentType.GI + def klargør_identer_til_søgning(identer: Iterable[str]): """Klargør flere identer til søgning."""