Publiceret: 9. juni 2024 Læsetid: 15 min.
Aggregeringsfunktioner i QGIS
“Aggregeringsfunktioner?” tænker du måske, “hvad er nu det for noget?”. I så fald er du helt sikkert ikke alene! Aggregeringsfunktioner er i den lidt mere avancerede ende af QGIS-anvendelsen, men det er et supereffektivt våben at kende, og det er derfor et blogindlæg værd!
Hvad er aggregeringsfunktioner?
QGIS rummer en række funktioner, som kan anvendes til at opsamle værdier af f.eks. attributter i et givet lag, og sammen med QGIS’ arrayfunktioner udgør de et stærkt værktøj til forskellige formål, hvoraf vi vil dykke ned i 3 eksempler i dette blog-indlæg.
Eksemplerne, som vi vil kigge nærmere på, er følgende:
- Supplering af lagsymbolikken med et fortløbende nummer
- Opgørelse af bygningsarealer pr. matrikelflade
- Automatisk opsamling af §3 naturtype ved registrering af plantefund
Disse 3 eksempler er konkrete problemløsninger, som jeg er stødt på i min rådgivning om QGIS-anvendelse, og jeg håber, at de kan illustrere mulighederne og give en introduktion til anvendelsen, som gør, at du selv kan opsætte tilsvarende løsninger, der hvor du har brug for det!
Supplering af lagsymbolikken med fortløbende numre
Fra tid til anden kan man løbe ind i situationer, hvor man gerne vil kategorise f.eks. et fladelag med forskellige farver, men hvor der er så mange kategorier, så det er vanskeligt for den, der skal læse kortet, at skelne de mange farver fra hinanden. Generelt er der en kartografisk tommelfingerregel, som siger, at man maksimalt må anvende 6-8 forskellige farver, hvis læseren skal have en chance for at skelne disse.
Men hvad gør man, hvis man nu har måske endda mange flere kategorier end 8? Man kan selvfølgelig anvende forskellige fyldtyper, som helt udfyldt, skraveringer og/eller punkt-udfyldninger, men desværre øger det ikke læseligheden væsentligt.
En løsning kan så være at give hver kategori et nummer – udover den farve- og symboltypesymbolik, som normalt anvendes.
I nedenstående eksempel har jeg taget et markkort fra Landbrugsstyrelsen. Disse markkort indeholder en attribut, som beskriver årets afgrøde, og dem er der cirka 60 forskellige af! Det siger sig selv, at så mange kategorier ikke kan skelnes alene på farver og symboltyper. Men hvis man udover dette indsætter et heltal i hver polygon, så får man et kort, som nok stadig ikke kan overskues lynhurtigt, men hvor man med sikkerhed – udfra afgrødenummeret – kan afgøre, hvad man har med at gøre.
Med en standardsymbolisering (‘Simple Fill’ med tilfældige farver – og jeg indrømmer, at det kunne gøres bedre ved lidt individuel tilpasning) ser kortet ud som nedenstående:
Tilføjer vi nu et nummer, bliver kortet stadig ikke letlæst og overskueligt, men vi får en sikkerhed for, at man kan afkode afgrøden på den enkelte mark uden fejl:
Hvordan opnår man dette – uden at skulle sidde og nummerere kategorierne manuelt?
Fremgangsmåden indeholder 2 trin:
- Opsæt lagsymbolikken, så der tilføjes et forløbende nummer i hver polygon.
- Tilpas signaturforklaringen i dit print layout, så nummeret medtages her.
Opsæt lagsymbolik med nummerering
Udover en standard ‘Simple Fill’ symbolisering tilføjes et punkt til hver polygon ved hjælp af en Geometry Generator:
Geometry Generator’en defineres som et punkt, der beregnes med udtrykket: pole_of_inaccessibility($geometry, 0.1).
Funktionen pole_of_inaccessibility() er lidt som centroid() funktionen, bortset fra, at pole_of_inaccessibility() altid genererer et punkt, som ligger indenfor polygonen (det gør centroid() ikke, hvis polygonen er irregulær).
Punktet symboliseres herefter med en ‘Font Marker’, hvor teksten beregnes med udtrykket:
array_find( array_distinct( aggregate('marker_seneste', 'array_agg', "Afgroede", order_by:="Afgroede") ), "Afgroede") + 1
Et sådant sammensat udtryk tolkes bedst indefra og ud:
- aggregate()-funktionen opsamler alle attributværdier i feltet “Afgroede” i laget ‘marker_seneste’ sorteret efter værdien i “Afgroede”.
- array_distinct()-funktionen sørger for at fjerne alle dubletter.
- array_find()-funktionen finder herefter placeringen af den aktuelle værdi af feltet “Afgroede” i det filtrerede array.
- Da array_find() returnerer placeringen med første værdi i position 0, lægges 1 til denne værdi, så vores nummerering går fra 1 og opefter.
Hermed har vi fået et nummer, som svarer til en given afgrøde i hver mark på kortet.
Tilpas signaturforklaringen i plot layoutet
For at få vist nummereringen i signaturforklaringen skal der foretages lidt tilpasninger her også:
-
- Indsæt en signaturforklaring i plot layoutet, og slå ‘Auto update’ fra.
- Fjern alle irrelevante elementer i signaturforklaringen.
- I elementet, som viser det lag, der er nummereret, trykkes på epsilon-symbolet under listen (markeret med rød cirkel nedenfor):
- Herefter indsættes følgende udtryk i udtryksdialogen:
to_string( array_find( array_distinct( aggregate( 'marker_seneste', 'array_agg',"Afgroede", order_by:="Afgroede") ),@symbol_label) + 1 ) || ' - ' || @symbol_label
Vi laver samme opsamling, fjernelse af dubletter og opslag af værdi, som under symboliseringen, men her finder vi den aktuelle @symbol_label’s placering i arrayet og lægger igen 1 til dette nummer. Nu vises den pågældende afgrødes nummer sammen med teksten i signatur-forklaringen.
Hvis listen er meget lang (som den er i dette tilfælde), kan det være hensigtsmæssigt at sætte flueben i feltet ‘Only show items inside linked maps’.
Opgørelse af bygningsarealer pr. matrikelflade
For at kunne beregne ”ledige” bebyggelses-kvadratmeter i et givet område er det hensigtsmæssigt at kunne beregne det samlede bygningsareal pr. jordstykke/matrikel. Det kan gøres med et udtryk, der anvender aggregeringsfunktioner, og som ved hjælp af feltberegneren bruges til at tilføje/beregne et felt på jordstykkelaget, hvor det samlede bygningsareal på hver enkelt jordstykke angives.
Der er dog en problematik, som man skal forholde sig til: Hvad skal der ske, hvis en bygning bliver delt af ét eller flere jordstykkeskel (som det ses meget hyppigt)?
Scenario 1: Bygningsarealer fordeles med den arealandel, som ligger på hvert givet jordstykke.
I det tilfælde kan bygningsarealet på det enkelte jordstykke beregnes således:
aggregate( layer:= 'bygning', aggregate:='sum', expression:=area(intersection($geometry, geometry(@parent))), filter:=intersects($geometry, geometry(@parent)) )
Udtrykket beregner summen af alle bygningsdele (aggregate:=’sum’), efter at de er skåret med jordstykkeskellet (expression:=area(intersection($geometry, geometry(@parent)))), såfremt bygning og jordstykke overhovedet skærer hinanden (filter:=intersects($geometry, geometry(@parent)))
Scenario 2: Bygningsarealer tælles fuldt og helt med på det jordstykke, hvor størstedelen af bygningen ligger (og ikke på andre jordstykker).
Hvis bygningens areal skal tælles med på det jordstykke, hvor bygningen fortrinsvis ligger (udfra antagelsen om, at skellet nok ikke ligger helt korrekt), anvendes følgende udtryk:
aggregate( layer:= 'bygning', aggregate:='sum', expression:=area($geometry), filter:=intersects($geometry, geometry(@parent)) and area( intersection( $geometry, geometry(@parent))) > area( difference($geometry, geometry(@parent))) )
Som det kan ses, er såvel expression som filter ændret: Førstnævnte sørger for at hele bygningens areal tælles med i summen, og filteret tæller alle bygninger med, som skærer jordstykket, og hvor arealet indenfor jordstykket (area( intersection( $geometry, geometry(@parent)))) er større end arealet udenfor (area( difference($geometry, geometry(@parent)))).
Bemærk: Sidstnævnte løsning er kun præcis, hvis det er mindre dele af bygningen, som ligger på andre jordstykker. Hvis bygningen er præcis ligeligt fordelt på to jordstykker eller fordelt med relativt store arealer på 3 eller flere jordstykker, vil resultatet ikke blive korrekt! Men i sådanne tilfælde er hele fremgangsmåden nok heller ikke den rigtige!
Automatisk opsamling af §3 naturtype ved registrering af plantefund
Det tredje og sidste eksempel på anvendelse af aggregeringsfunktionerne er en opsætning, hvor man ønsker at registrere plantefund som punkt-objekter, og hvor man ønsker, at det enkelte plantefund “stemples” med en §3 naturtype, såfremt fundet ligger i et sådant område.
Dette kan gøres ved at tilføje et felt i plantefund-laget, hvor naturtypen kan registreres, og herefter oprette en default-værdi på feltet, som beregnes med følgende udtryk:
array_get( aggregate( layer:= 'dai:bes_naturtyper', aggregate:='array_agg', expression:="natyp_navn", filter:=intersects($geometry,geometry(@parent)) ), 0 )
Igen læses udtrykket bedst indefra og ud:
- aggregate()-funktionen opsamler alle værdier i feltet “natyp_navn” i laget ‘dai:bes_naturtyper’, som opfylder filteret (som siger, at punktet skal ligge i polygonen).
- Da naturtype-polygonerne ikke er overlappende, opsamler ovennævnte funktion kun én (eller nul – hvis punktet ikke ligger i en beskyttet natur polygon) værdier, så array_get()-funktionen skal blot hente første værdi (som har nummer 0).
Såfremt plantefundspunktet ikke ligger i en beskyttet natur polygon, returneres ‘NULL’, hvilket jo giver go’ mening!
Afslutning
Jeg håber, at ovenstående 3 eksempler dels kan give konkret inspiration til QGIS brugere, som sidder med de beskrevne problemstillinger, og dels kan inspirere til andre anvendelser af aggregreringsfunktionerne, der som nævnt indledningsvist er ganske kraftfulde og kan bruges til mangt og meget.
Hvis din gennemlæsning af ovenstående eller egne eksperimenter med aggregeringsfunktioner afføder spørgsmål, er du naturligvis meget velkommen til at kontakte mig.