Software-Tipps
Q(GIS)&A – Wie ordne ich die Beschriftungen aufeinanderliegender Punkte um diesen herum an?
Mein Kollege Pascal hat im firmeninternen QGIS-Chat die Frage gestellt, ob jemand eine einfache Möglichkeit kennt, aufeinanderliegende Punkte in unserem Lieblings-GIS so zu beschriften, dass alle Beschriftungen kreisförmig um den Punkt herum dargestellt werden (anstatt sich gegenseitig zu überdecken und damit gar nicht dargestellt zu werden).
QGIS bietet einige einfache Möglichkeiten an, mit denen man versuchen kann, das Problem anzugehen. Ein Beispiel: Man stellt den Modus der Platzierung auf „Um Punkt“ und setzt die Entfernung so hoch, dass alle Beschriftungen sichtbar werden. Schön und gut, nur gelten diese Einstellungen dann für alle Beschriftungen – auch die einzeln liegenden Punkte. Auch der Darstellungstyp „Punktversatz“ in der Symbolisierung mit den dazugehörigen Beschriftungseinstellungen konnte Pascal nicht zufriedenstellen.
Doch QGIS wäre ja nicht QGIS, wenn es nicht eine Lösung für das Problem geben würde – in diesem Fall hat sie Pascal selbst unter Zuhilfenahme des Geometriegenerators und komplexer Ausdrücke geschrieben. Ich habe seinen Vorschlag weiter verfeinert und möchte euch die Lösung in unserem heutigen Q(GIS)&A vorstellen.
Der Geometriegenerator – Ein mächtiges Werkzeug für kartographische Zwecke
Für all diejenigen, die gerade über das schöne Wort „Geometriegenerator“ gestolpert sind, kurz zum Einstieg: Diesen findet ihr bei der Symbolgestaltung als Symbollayertyp und in den Beschriftungseinstellungen für die Platzierung (beides unabhängig vom Geometrietyp) – letzteres werden wir verwenden. Sinn und Zweck dieses mächtigen Werkzeugs ist es im Allgemeinen, räumliche Operationen mit den Geometrien eines Vektordatensatzes durchzuführen, die dann anstelle der originalen Geometrie für die Symbolisierung oder Beschriftungsplatzierung genutzt werden. Dafür steht die umfangreiche Funktionsbibliothek des Ausdruckseditors zur Verfügung. Wichtige Geometriefunktionen in diesem sind beispielsweise
- der Zugriff auf die originale Geometrie mit @geometry
- die Berechnung abgeleiteter Geometrien mit centroid(), buffer(), simplify(), …
- die Erstellung neuer Geometrien mit make_point(), make_line(), …
- geometrische Vergleiche mit intersects(), overlay(), ...
Ein einfaches Beispiel ist das visuelle Puffern von Vektordaten, ohne auf den gleichnamigen Verarbeitungsalgorithmus zugreifen zu müssen.
Eine Sammlung komplexer Anwendungen, welche sehr gut die Möglichkeiten des Geometriegenerators aufzeigen, findet ihr z.B. von Michel Stuyts bei Codeberg (Englisch).
Bitte überprüfe, ob mehrere Punkte exakt die gleiche Geometrie haben...
Kommen wir nun zurück zu unserer Problemstellung. QGIS soll die Beschriftungen der Punkte gesondert platzieren, wenn diese aufeinander liegen, also die gleiche Geometrie (X- und Y-Koordinate) haben - und nur genau dann! Liegt ein Punkt einzeln, sollen die manuell eingegebenen Werte weiterhin angewendet werden. Wir brauchen also eine Fallunterscheidung. Daher fängt unser Ausdruck wie folgt an:
with_variable('key', geom_to_wkt($geometry),
with_variable('ids',
array_agg(
$id,
order_by := $id,
filter := geom_to_wkt($geometry) = @key
),
Der Ausdruck arbeitet mit selbstdefinierten Variablen (with_variable()), welche im weiteren Verlauf Schreibarbeit ersparen werden. Für jede im Punkt-Layer vorhandene Geometrie (Zeile 1) wird mittels array_agg() (Zeile 3) ein Array (eine Liste) erstellt, in welchem alle Punkte des Layers landen, die die identische Geometrie zu diesem Punkt (Zeile 6) haben. Liegt ein Punkt einzeln, hat diese Liste nur einen Eintrag, nämlich den Punkt selbst. Liegen jedoch beispielsweise vier Punkte aufeinander, entsteht für jeden dieser Punkte eine Liste mit vier Einträgen – dank des Parameters order_by (Zeile 5) ist dabei die Reihenfolge in jeder dieser Listen identisch (geordnet nach einer QGIS-internen Kennung der Objekte), was noch wichtig werden wird.
with_variable('n', array_length(@ids),
with_variable('i', array_find(@ids, $id),
Nun werden weitere Variablen definiert, welche sich auf das entstandene Array beziehen. Mit n (Zeile 1) wird die Länge des Arrays abgegriffen (die Anzahl der aufeinanderliegenden Punkte bzw. 1 bei einem einzelnen Punkt) und mit i (Zeile 2) die Position des aktuell bearbeiteten Punktes in diesem Array. Betrachten wir beispielhaft einen Punkt, welcher mit drei anderen Punkten an derselben Position liegt und der die niedrigste interne Kennung im gesamten Layer hat – in diesem Fall gilt also n=4 und i=1.
Die Definition des Kreises
with_variable('r_mm', 3 * @n,
with_variable('r_map', (@map_scale * @r_mm) / 1000.0,
with_variable('angle', (2*pi()*@i)/@n,
Hier kommt nun Kreis-Mathematik ins Spiel. Es wird ein Radius in Abhängigkeit von n definiert (Zeile 1) – je mehr Punkte aufeinander liegen, desto weiter entfernt müssen die Beschriftungen vom Punkt entfernt dargestellt werden. Dieser Radius wird außerdem mit dem aktuellen Kartenmaßstab (@map_scale (Zeile 2)) multipliziert – möchte man mit einem Referenzmaßstab arbeiten, muss dieser Schritt wegfallen. Beim Winkel (Zeile 3) werden n und i verwendet, damit die Beschriftung jedes Punkts eine eindeutige Position zugeordnet bekommt. Mit der Formel (2 * π * i) / n erhält man einen Richtungswinkel im Bogenmaß, welcher gleich benötigt wird.
| i | Winkel (Bogenmaß) | Winkel in Grad |
|---|---|---|
| 1 | 0,5 · π | 90 |
| 2 | π | 180 |
| 3 | 1,5 · π | 270 |
| 4 | 2 · π | 360 |
Tabelle 1: Die Werte der Variable angle in Abhängigkeit von i bei n=4 und die resultierenden Winkel in Grad zum besseren Verständnis.
case
when @n <= 1 then $geometry
else project($geometry, @r_map, @angle)
end
Hier findet nun die eigentliche Fallunterscheidung mit Hilfe einer verzweigten Bedingung statt (CASE-Funktion). Hat ein Array nur einen Eintrag (einzelner Punkt), soll für die Beschriftung die Geometrie des dazugehörigen Punktes verwendet werden (Zeile 2) – das ganz normale Verhalten. Für die Punkte aller anderen Arrays, bei denen also mindestens ein weiterer Punkt an der identischen Stelle liegt, wird jedoch die Funktion project() (Zeile 3) verwendet, um den Beschriftungspunkt mit den zuvor berechneten Parametern (Radius und Winkel) zu versetzen. Wenn man die Einzelbestandteile des Ausdrucks nun zusammensetzt und die vielen geöffneten Klammern noch schließt, haben wir den finalen Ausdruck.
Die Lösung
Falls du gerade keinen passenden Datensatz zur Hand hast, um die Einstellungen zu testen, kannst du dir ein GeoPackage mit meinem Testdatensatz herunterladen: Zum Datensatz
Lade den Layer „Ausgangssituation“ in ein leeres QGIS-Projekt und zoome zu diesem (im Layer „Lösung“ ist der fertige Stil bereits integriert).
Befolge nun die folgenden Schritte (siehe auch Abbildung 4 weiter unten):
- Aktiviere die Beschriftung und wechsle in den Reiter mit den Platzierungseinstellungen
- Stelle den Modus auf Abstand vom Punkt
- Setze weiter unten den Haken bei Geometriegenerator und kopiere den folgenden Ausdruck hinein:
with_variable('key', geom_to_wkt($geometry),
with_variable('ids',
array_agg(
$id,
order_by := $id,
filter := geom_to_wkt($geometry) = @key
),
with_variable('n', array_length(@ids),
with_variable('i', array_find(@ids, $id),
with_variable('r_mm', 3*@n,
with_variable('r_map', (@map_scale * @r_mm) / 1000.0,
with_variable('angle', (2*pi()*@i)/@n,
case
when @n <= 1 then $geometry
else project($geometry, @r_map, @angle)
end
)
)
)
)
)
)
)
Zur weiteren Verfeinerung nutzen wir noch die datendefinierte Übersteuerung (QGIS Dokumentation) für die Parameter Quadrant sowie X,Y-Versatz. Dies führt dazu, dass wir diese Parameter für Beschriftungen von Einzelpunkten manuell wie gewünscht anpassen können, ohne damit die Beschriftungen der aufeinanderliegenden Punkte zu verschieben.
4. Trage bei der datendefinierten Übersteuerung für den X,Y Versatz folgendes ein:
with_variable('key', geom_to_wkt($geometry),
with_variable('ids',
array_agg(
$id,
order_by := $id,
filter := geom_to_wkt($geometry) = @key
),
with_variable('n', array_length(@ids),
case
when @n <= 1 then NULL
else '0,0'
end
)
)
)
5. Trage bei der datendefinierten Übersteuerung für den Quadranten folgendes ein:
with_variable('key', geom_to_wkt($geometry),
with_variable('ids',
array_agg(
$id,
order_by := $id,
filter := geom_to_wkt($geometry) = @key
),
with_variable('n', array_length(@ids),
case
when @n <= 1 then NULL
else 4
end
)
)
)
Wir erstellen in beiden Fällen das Array analog zu unserem Ausdruck im Geometriegenerator und überprüfen dessen Länge. Bei Einzelpunkten gibt der Ausdruck NULL heraus, was bedeutet, dass die manuellen Einstellungen der Parameter genommen werden, während bei unseren Punktsammlungen kein Versatz und der zentrale Quadrant erzwungen werden.
Abschließende Hinweise
Fühlt euch frei, die verwendeten Ausdrücke weiter zu bearbeiten – bei sehr langen oder mehrzeiligen Texten kann es beispielsweise durchaus sein, dass der Radius vergrößert werden muss. Auch andere individuelle Einstellungen der Beschriftung setzen möglicherweise weitere Feinabstimmung voraus. Und wie löst man das, wenn die Punkte zwar nah aneinander, aber nicht direkt übereinander liegen…? Auch dafür bietet QGIS Lösungen. Ich wünsche euch viel Freude dabei, diese zu entdecken!
Über den Autor
Steckbrief
Florian Meier wollte immer Mathematik und Geographie lehren, hat aber während seines Lehramtsstudiums die Geoinformatik kennen und lieben gelernt. Deshalb unterrichtet der UNIGIS MSc - Absolvent seit 2020 die Nutzung geographischer Informationssysteme und ist seit 2026 bei der WhereGroup als GIS-Trainer tätig.