def sagsevent(firedb, sag): e0 = Sagsevent(sag=sag, eventtype=EventType.KOMMENTAR) firedb.session.add(e0) return e0
def ilæg_revision( projektnavn: str, sagsbehandler: str, **kwargs, ) -> None: """Læg reviderede punktdata i databasen""" er_projekt_okay(projektnavn) sag = find_sag(projektnavn) sagsgang = find_sagsgang(projektnavn) fire.cli.print(f"Sags/projekt-navn: {projektnavn} ({sag.id})") fire.cli.print(f"Sagsbehandler: {sagsbehandler}") fire.cli.print("") revision = find_faneblad(f"{projektnavn}-revision", "Revision", arkdef.REVISION) # Tildel navne til endnu ikke oprettede punkter oprettelse = revision.query("Attribut == 'OPRET'") for i, _ in oprettelse.iterrows(): revision.loc[i, "Punkt"] = f"NYTPUNKT{i}" # Udfyld udeladte identer punkter = list(revision["Punkt"]) udfyldningsværdi = "" for i in range(len(punkter)): if punkter[i].strip() != "": udfyldningsværdi = punkter[i].strip() continue punkter[i] = udfyldningsværdi revision["Punkt"] = punkter # Find alle punkter, der skal nyoprettes nye_punkter = [] oprettelse = revision.query("Attribut == 'OPRET'") for row in oprettelse.to_dict("records"): if row["id"] == -1: continue punkt = opret_punkt(row["Ny værdi"]) fire.cli.print(f"Opretter nyt punkt {punkt.ident}: {row['Ny værdi']}") nye_punkter.append(punkt) # indsæt nyt punkt ID i Punkt kolonnen, for at kunne trække # dem ud med hent_punkt() senere erstat = lambda x: punkt.id if x == row["Punkt"] else x revision["Punkt"] = revision.Punkt.apply(erstat) revision = revision.query("Attribut != 'OPRET'") # Find alle lokationskoordinater, der skal korrigeres nye_lokationer = [] lokation = revision.query("Attribut == 'LOKATION'") lokation = lokation.query("`Ny værdi` != ''") for row in lokation.to_dict("records"): punkt = fire.cli.firedb.hent_punkt(row["Punkt"]) # gem her inden ny geometri tilknyttes punktet (λ1, φ1) = punkt.geometri.koordinater go = læs_lokation(row["Ny værdi"]) go.punkt = punkt nye_lokationer.append(go) (λ2, φ2) = go.koordinater g = Geod(ellps="GRS80") _, _, dist = g.inv(λ1, φ1, λ2, φ2) if dist >= 25: fire.cli.print( f" ADVARSEL: Ny lokationskoordinat for {punkt.landsnummer} afviger {dist:.0f} m fra den gamle", fg="yellow", bold=True, ) if len(nye_punkter) > 0 or len(nye_lokationer) > 0: sagsevent = Sagsevent( id=uuid(), sagsid=sag.id, sagseventinfos=[ SagseventInfo(beskrivelse="Oprettelse af nye punkter") ], eventtype=EventType.PUNKT_OPRETTET, punkter=nye_punkter, geometriobjekter=nye_lokationer, ) fire.cli.firedb.indset_sagsevent(sagsevent, commit=False) sagsgang = opdater_sagsgang(sagsgang, sagsevent, sagsbehandler) flush() revision = revision.query("Attribut != 'LOKATION'") # Find alle koordinater, der skal oprettes # Først skal vi bruge alle gyldige koordinatsystemnavne srider = fire.cli.firedb.hent_srider() sridnavne = [srid.name.upper() for srid in srider] # Så itererer vi over hele rammen og ignorerer ikke-koordinaterne nye_koordinater = [] opdaterede_punkter = [] for r in revision.to_dict("records"): sridnavn = r["Attribut"].upper() if sridnavn not in sridnavne: continue try: koord = [float(k.replace(",", ".")) for k in r["Ny værdi"].split()] except ValueError as ex: fire.cli.print( f"Ukorrekt koordinatformat:\n{' '.join(r['Ny værdi'])}\n{ex}" ) fire.cli.print( "Skal være på formen: 'x y z t sx sy sz', hvor ubrugte værdier sættes til 'nan'" ) sys.exit(1) # Oversæt NaN til None koord = [None if isnan(k) else k for k in koord] # Tæt-på-kopi af kode fra "niv/ilæg_nye_koter.py". Her bør mediteres og overvejes # hvordan denne opgave kan parametriseres på en rimeligt generel måde, så den kan # udstilles i et "højniveau-API" srid = fire.cli.firedb.hent_srid(sridnavn) punkt = fire.cli.firedb.hent_punkt(r["Punkt"]) opdaterede_punkter.append(r["Punkt"]) # Det er ikke helt så nemt som i C at oversætte decimal-år til datetime år = trunc(koord[3]) rest = koord[3] - år startdato = datetime(år, 1, 1) årlængde = datetime(år + 1, 1, 1) - startdato tid = startdato + rest * årlængde koordinat = Koordinat( srid=srid, punkt=punkt, x=koord[0], y=koord[1], z=koord[2], t=tid, sx=koord[4], sy=koord[5], sz=koord[6], ) nye_koordinater.append(koordinat) # I Grønland er vi nødt til at duplikere geografiske koordinater til UTM24, # da Oracles indbyggede UTM-rutine er for ringe til at vi kan generere # udstillingskoordinater on-the-fly. if sridnavn in ("EPSG:4909", "EPSG:4747"): srid_utm24 = fire.cli.firedb.hent_srid("EPSG:3184") utm24 = Proj("proj=utm zone=24 ellps=GRS80", preserve_units=False) x, y = utm24(koord[0], koord[1]) koordinat = Koordinat( srid=srid_utm24, punkt=punkt, x=x, y=y, z=None, t=tid, sx=koord[4], sy=koord[5], sz=None, ) nye_koordinater.append(koordinat) n = len(opdaterede_punkter) if n > 0: punktnavne = sorted(list(set(opdaterede_punkter))) if len(punktnavne) > 10: punktnavne[9] = "..." punktnavne[10] = punktnavne[-1] punktnavne = punktnavne[0:10] koordinatoprettelsestekst = ( f"Opdatering af {n} koordinater til {', '.join(punktnavne)}") sagsevent = Sagsevent( id=uuid(), sagsid=sag.id, sagseventinfos=[ SagseventInfo(beskrivelse=koordinatoprettelsestekst) ], eventtype=EventType.KOORDINAT_BEREGNET, koordinater=nye_koordinater, ) fire.cli.firedb.indset_sagsevent(sagsevent, commit=False) sagsgang = opdater_sagsgang(sagsgang, sagsevent, sagsbehandler) flush() # Så tager vi fat på punktinformationerne til_opret = [] til_ret = [] til_sluk = [] punkter_med_oprettelse = set() punkter_med_rettelse = set() punkter_med_slukning = set() # Først, tilknyt regionspunktinfo til nyoprettede punkter for p in nye_punkter: til_opret.append(opret_region_punktinfo(p)) # Find identer for alle punkter, der indgår i revisionen identer = tuple(sorted(set(revision["Punkt"]) - set(["nan", ""]))) fire.cli.print("") fire.cli.print(f"Behandler {len(identer)} punkter") # Så itererer vi over alle punkter for ident in identer: fire.cli.print(ident, fg="yellow", bold=True) # Hent punkt og alle relevante punktinformationer i databasen. # Her er det lidt sværere end for koordinaternes vedkommende: # Ved opdatering af eksisterende punkter vil vi gerne checke # infonøglerne, så vi er nødt til at hente det faktiske punkt, # med tilørende infonøgler, fra databasen try: punkt = fire.cli.firedb.hent_punkt(ident) infonøgler = { info.objektid: i for i, info in enumerate(punkt.punktinformationer) } except NoResultFound as ex: fire.cli.print( f"FEJL: Kan ikke finde punkt {ident}!", fg="yellow", bg="red", bold=True, ) fire.cli.print(f"Mulig årsag: {ex}") sys.exit(1) # Hent alle revisionselementer for punktet fra revisionsarket rev = revision.query(f"Punkt == '{ident}'") for r in rev.to_dict("records"): pitnavn = r["Attribut"] sluk = r["Sluk"].strip() != "" # Tom attribut? if pitnavn == "": continue # Koordinatilægning? if pitnavn in sridnavne: continue # Midlertidigt aflukket element? if r["id"] < 0: continue # Det er en fejl at angive ny værdi for et element man slukker for. if sluk and r["Ny værdi"].strip() != "": fire.cli.print( f" * FEJL: 'Sluk' og 'Ny værdi' begge udfyldt: {r['Ny værdi']}", fg="red", bold=False, ) continue if r["Tekstværdi"] != "" and not sluk: # Undgå at overskrive en eksisterende værdi med en usynlig blankværdi if r["Ny værdi"].strip() == "": continue # Det er almindelig praksis ved revision at kopiere "Tekstværdi" til # "Ny værdi", så hvis de to er ens går vi til næste element. if r["Tekstværdi"] == r["Ny værdi"]: continue if pitnavn is None: fire.cli.print( " * Ignorerer uanført punktinformationstype", fg="red", bold=False, ) continue pit = fire.cli.firedb.hent_punktinformationtype(pitnavn) if pit is None: fire.cli.print( f" * Ignorerer ukendt punktinformationstype '{pitnavn}'", fg="red", bold=True, ) continue # Nyt punktinfo-element? if pd.isna(r["id"]): # ATTR:muligt_datumstabil+slukket == ikke eksisterende i DB # Indsat af fire niv udtræk-revision if pitnavn == "ATTR:muligt_datumstabil" and r["Sluk"]: continue fire.cli.print( f" Opretter nyt punktinfo-element: {pitnavn}") if pit.anvendelse == PunktInformationTypeAnvendelse.FLAG: if r["Ny værdi"]: fire.cli.print( f" BEMÆRK: {pitnavn} er et flag. Ny værdi '{r['Ny værdi']}' ignoreres", fg="yellow", bold=True, ) pi = PunktInformation(infotype=pit, punkt=punkt) elif pit.anvendelse == PunktInformationTypeAnvendelse.TEKST: # Excel *kan* finde på at proppe "_x000D_" ind i stedet for \r, # her rydder vi op for at undgå vrøvl i databasen. tekst = r["Ny værdi"].replace("_x000D_", "") # Ingen definitiv test her: Tom tekst kan være gyldig. # Men vi sørger for at den ikke er None if tekst is None or tekst == "": fire.cli.print( f" ADVARSEL: Tom tekst anført for {pitnavn}.", fg="yellow", bold=True, ) tekst = "" pi = PunktInformation(infotype=pit, punkt=punkt, tekst=tekst) else: try: # Både punktum og komma er accepterede decimalseparatorer tal = float(r["Ny værdi"].replace(",", ".")) except ValueError as ex: fire.cli.print( f" FEJL: {pitnavn} forventer numerisk værdi [{ex}].", fg="yellow", bold=True, ) tal = 0 pi = PunktInformation(infotype=pit, punkt=punkt, tal=tal) til_opret.append(pi) punkter_med_oprettelse.add(ident) continue # Ingen ændringer? - så afslutter vi og går til næste element. if not sluk and r["Ny værdi"].strip() == "": continue # Herfra håndterer vi kun punktinformationer med indførte ændringer # Nu kan vi bruge objektid som heltal (ovenfor havde vi brug for NaN-egenskaben) oid = int(r["id"]) if sluk: try: pi = punkt.punktinformationer[infonøgler[oid]] except KeyError: fire.cli.print( f" * Ukendt id - ignorerer element '{oid}'", fg="red", bold=True, ) continue fire.cli.print(f" Slukker: {pitnavn}") # pi._registreringtil = func.current_timestamp() til_sluk.append(pi) punkter_med_slukning.add(punkt.ident) continue fire.cli.print(f" Retter punktinfo-element: {pitnavn}") if pit.anvendelse == PunktInformationTypeAnvendelse.FLAG: pi = PunktInformation(infotype=pit, punkt=punkt) elif pit.anvendelse == PunktInformationTypeAnvendelse.TEKST: # Fjern overflødigt whitespace og duplerede punktummer tekst = r["Ny værdi"] tekst = re.sub(r"[ \t]+", " ", tekst.strip()) tekst = re.sub(r"[.]+", ".", tekst) pi = PunktInformation(infotype=pit, punkt=punkt, tekst=tekst) else: try: tal = float(r["Ny værdi"]) except ValueError as ex: fire.cli.print( f" FEJL: {pitnavn} forventer numerisk værdi [{ex}].", fg="yellow", bold=True, ) tal = 0 pi = PunktInformation(infotype=pit, punkt=punkt, tal=tal) til_ret.append(pi) punkter_med_rettelse.add(punkt.ident) continue fikspunktstyper = [FikspunktsType.GI for _ in nye_punkter] landsnumre = fire.cli.firedb.tilknyt_landsnumre(nye_punkter, fikspunktstyper) til_opret.extend(landsnumre) for p in nye_punkter: punkter_med_oprettelse.add(p.ident) if len(til_opret) > 0 or len(til_ret) > 0: sagsevent = Sagsevent( id=uuid(), sagsid=sag.id, sagseventinfos=[ SagseventInfo(beskrivelse="Opdatering af punktinformationer") ], eventtype=EventType.PUNKTINFO_TILFOEJET, punktinformationer=[*til_opret, *til_ret], ) fire.cli.firedb.indset_sagsevent(sagsevent, commit=False) sagsgang = opdater_sagsgang(sagsgang, sagsevent, sagsbehandler) flush() if len(til_sluk) > 0: sagsevent = Sagsevent( id=uuid(), sagsid=sag.id, sagseventinfos=[ SagseventInfo(beskrivelse="Lukning af punktinformationer") ], eventtype=EventType.PUNKTINFO_FJERNET, punktinformationer_slettede=til_sluk, ) fire.cli.firedb.indset_sagsevent(sagsevent, commit=False) sagsgang = opdater_sagsgang(sagsgang, sagsevent, sagsbehandler) flush() opret_tekst = f"- oprette {len(til_opret)} attributter fordelt på {len(punkter_med_oprettelse)} punkter" sluk_tekst = f"- slukke for {len(til_sluk)} attributter fordelt på {len(punkter_med_slukning)} punkter" ret_tekst = f"- rette {len(til_ret)} attributter fordelt på {len(punkter_med_rettelse)} punkter" lok_tekst = f"- rette {len(nye_lokationer)} lokationskoordinater" fire.cli.print("") fire.cli.print("-" * 50) fire.cli.print("Punkter færdigbehandlet, klar til at") fire.cli.print(opret_tekst) fire.cli.print(sluk_tekst) fire.cli.print(ret_tekst) fire.cli.print(lok_tekst) spørgsmål = click.style( f"Er du sikker på du vil indsætte ovenstående i {fire.cli.firedb.db}-databasen", fg="white", bg="red", ) if bekræft(spørgsmål): fire.cli.firedb.session.commit() skriv_ark(projektnavn, {"Sagsgang": sagsgang}) else: fire.cli.firedb.session.rollback()