Verschieben der Anzeige überlappender Linien in QGIS?

10

Wenn sich Punkte überlappen, gibt es diese Eigenschaft, mit der das Los automatisch getrennt angezeigt werden kann. Dies wird als "Punktverschiebung" bezeichnet. Aber es funktioniert nicht für Linien, obwohl es mir konzeptionell durchaus machbar erscheint, um so etwas zu erreichen:

Geben Sie hier die Bildbeschreibung ein

Ich muss unbedingt die verschiedenen Leitungen sehen, die sich in Wirklichkeit alle am selben Ort befinden (ich arbeite in der Telekommunikationsvernetzung). Der einzige Weg, den ich jetzt sehe, besteht darin, wirklich verschiedene Linien wie im obigen Bild zu erstellen und so räumliche Fehler zu erzeugen.

Ich verwende QGIS 2.14.

GuiOm Clair
quelle
Ich denke, es könnte etwas getan werden, um das Styling zu wiederholen. Ist die Linie in der Mitte die Startlinie? Dann sehe ich, dass Sie jede der anderen Linien mit drei verschiedenen Geometrien erstellt haben. Meine Frage ist also, ob es bestimmte zusätzliche Regeln für das Rendern gibt.
Mgri
@mgri Ich bin mir nicht sicher, ob ich deine Frage verstehe. Das Bild ist ein Beispiel, in dem ich zur Demonstration fünf verschiedene Linien gezeichnet habe. In Wirklichkeit wäre es mehr so, dass diese 5 Leitungen tatsächlich an der Stelle der mittleren liegen (es sind Drähte, also stecken alle in derselben Hülle).
GuiOm Clair
1
Sie können auch Linien mit einer Verschiebung ("Versatz") rendern, die sich jedoch am Start- und Endpunkt nicht treffen würden.
AndreJ
@AndreJ Ja, und ein weiteres Problem wäre, dass es sich um eine recht manuelle Bedienung handelt, bei der ich etwas Automatischeres benötigen würde, da es von vielen Benutzern verwendet wird.
GuiOm Clair
1
@GuiOmClair Nach dem angehängten Bild habe ich angenommen, dass Sie von einer Zeile ausgehen, die (zum Beispiel) vier andere Zeilen überlappt, und dass Sie einen Weg finden müssen, sie separat anzuzeigen, auch wenn sie sich überlappen. Ich habe nur gesagt, dass es möglich sein könnte, das, was im angehängten Bild angezeigt wird, zu reproduzieren, ohne neue Geometrien erstellen zu müssen (aber nur die Stileigenschaften der Startebene wiederzugeben). Ein anderer Weg wäre der von AndreJ vorgeschlagene, aber es scheint, dass er nicht Ihren Bedürfnissen entspricht.
Mgri

Antworten:

12

Ich schlage einen Ansatz vor, der nur auf einen Geometriegenerator und eine benutzerdefinierte Funktion zurückgreift.

Bevor ich anfange, möchte ich unterstreichen, dass ich die Aufmerksamkeit auf die Erklärung der minimalen Dinge richten werde, die zur Wiedergabe des gewünschten Ergebnisses zu tun sind: Dies bedeutet, dass einige andere kleinere Parameter (wie Größen, Breiten usw.) von Ihnen leicht angepasst werden sollten für eine bessere Anpassung an Ihre Bedürfnisse.

Daher funktioniert diese Lösung sowohl für geografische als auch für projizierte Referenzsysteme: Im Folgenden habe ich angenommen, dass ein projiziertes CRS verwendet wird (dh Maßeinheiten sind Meter), aber Sie können sie entsprechend Ihrem CRS ändern.


Kontext

Nehmen wir an, wir beginnen mit dieser linearen Vektorebene, die die Drähte darstellt (die Beschriftungen geben die Anzahl der überlappenden (zusammenfallenden) Drähte an):

Geben Sie hier die Bildbeschreibung ein


Lösung

Gehen Sie zuerst zu Layer Properties | Styleund wählen Sie dann den Single symbolRenderer aus.

Symbol selectorWählen Sie im Dialogfeld einen Geometry generatorTyp Linestring / MultiLinestringals Symbolebene und einen Geometrietyp aus. Klicken Sie dann auf die Function EditorRegisterkarte:

Geben Sie hier die Bildbeschreibung ein

Klicken Sie dann auf New fileund geben Sie draw_wiresden Namen der neuen Funktion ein:

Geben Sie hier die Bildbeschreibung ein

Sie werden sehen, dass eine neue Funktion erstellt wurde und diese auf der linken Seite des Dialogfelds aufgeführt ist. Klicken Sie nun auf den Namen der Funktion und ersetzen Sie die Standardeinstellung @qgsfunctiondurch den folgenden Code (vergessen Sie nicht, alle hier angehängten Bibliotheken hinzuzufügen):

from qgis.core import *
from qgis.gui import *
from math import sin, cos, radians

@qgsfunction(args='auto', group='Custom')
def draw_wires(angle, percentage, curr_feat, layer_name, feature, parent):

    def wires(polyline, new_angle, percentage):
        for x in range(0, len(polyline)-1):
            vertices = []
            first_point = polyline[x]
            second_point = polyline[x +1]
            seg = QgsGeometry.fromPolyline([first_point, second_point])
            len_feat = seg.length()
            frac_len = percentage * len_feat
            limb = frac_len/cos(radians(new_angle))
            tmp_azim = first_point.azimuth(second_point)
            angle_1 = radians(90 - (tmp_azim+new_angle))
            dist_x, dist_y = (limb * cos(angle_1), limb * sin(angle_1))
            point_1 = QgsPoint(first_point[0] + dist_x, first_point[1] + dist_y)
            angle_2 = radians(90 - (tmp_azim-new_angle))
            dist_x, dist_y = (limb * cos(angle_2), limb * sin(angle_2))
            point_2 = QgsPoint(second_point[0] - dist_x, second_point[1] - dist_y)
            tmp_azim = second_point.azimuth(first_point)
            angle_3 = radians(90 - (tmp_azim+new_angle))
            dist_x, dist_y = (limb * cos(angle_3), limb * sin(angle_3))
            point_3 = QgsPoint(second_point[0] + dist_x, second_point[1] + dist_y)
            angle_4 = radians(90 - (tmp_azim-new_angle))
            dist_x, dist_y = (limb * cos(angle_4), limb * sin(angle_4))
            point_4 = QgsPoint(first_point[0] - dist_x, first_point[1] - dist_y)
            vertices.extend([first_point, point_1, point_2, second_point, point_3, point_4, first_point])
            tempGeom = QgsGeometry.fromPolyline(vertices)
            num.append(tempGeom)
        return num


    layer = QgsMapLayerRegistry.instance().mapLayersByName(layer_name)[0]

    all_feats = {}
    index = QgsSpatialIndex()
    for ft in layer.getFeatures():
        index.insertFeature(ft)
        all_feats[ft.id()] = ft

    first = True

    tmp_geom = curr_feat.geometry()
    polyline = tmp_geom.asPolyline()
    idsList = index.intersects(tmp_geom.boundingBox())
    occurrences = 0
    for id in idsList:
        test_feat = all_feats[id]
        test_geom = test_feat.geometry()
        if tmp_geom.equals(test_geom):
            occurrences += 1
    if occurrences & 0x1:
        num = [tmp_geom]
    else:
        num = []

    rapp = occurrences/2
    i=2
    new_angle = angle

    while i <= occurrences:
        draw=wires(polyline, new_angle, percentage)
        i += 2
        new_angle -= new_angle/rapp
    first = True
    for h in num:
        if first:
            geom = QgsGeometry(h)
            first = False
        else:
            geom = geom.combine(h)
    return geom

Wenn Sie dies getan haben, klicken Sie auf die LoadSchaltfläche und Sie können die Funktion im CustomMenü des ExpressionDialogfelds sehen.

Geben Sie nun diesen Ausdruck ein (siehe Abbildung unten als Referenz):

draw_wires(40, 0.3, $currentfeature, @layer_name)

Geben Sie hier die Bildbeschreibung ein

Sie haben gerade eine Funktion ausgeführt, die auf imaginäre Weise sagt:

" Zeigen Sie für die aktuelle Ebene ( @layer_name ) und das aktuelle Feature ( $ currentfeature ) die Drähte zusammen mit einer anfänglichen maximalen Öffnung von 40 Grad und einer Richtungsänderung in einem Abstand von 0,3 mal der Länge des aktuellen Segments an."

Das einzige, was Sie ändern müssen, ist der Wert der ersten beiden Parameter, wie Sie möchten, aber offensichtlich in angemessener Weise (lassen Sie die anderen Funktionsparameter wie angegeben).

Klicken Sie abschließend auf die ApplySchaltfläche, um die Änderungen zu übernehmen.

Sie werden so etwas sehen:

Geben Sie hier die Bildbeschreibung ein

wie erwartet.


BEARBEITEN

Auf eine spezielle Anfrage des OP in einem Kommentar hin:

"Wäre es möglich, dieses Muster nur zwischen dem Anfang und dem Ende jeder Polylinie anstatt zwischen jedem Scheitelpunkt zu erstellen?"

Ich habe den Code leicht bearbeitet. Die folgende Funktion sollte das erwartete Ergebnis zurückgeben:

from qgis.core import *
from qgis.gui import *
from math import sin, cos, radians

@qgsfunction(args='auto', group='Custom')
def draw_wires(angle, percentage, curr_feat, layer_name, feature, parent):

    def wires(polyline, new_angle, percentage):
        vertices = []
        len_feat = polyline.length()
        frac_len = percentage * len_feat
        limb = frac_len/cos(radians(new_angle))
        tmp_azim = first_point.azimuth(second_point)
        angle_1 = radians(90 - (tmp_azim+new_angle))
        dist_x, dist_y = (limb * cos(angle_1), limb * sin(angle_1))
        point_1 = QgsPoint(first_point[0] + dist_x, first_point[1] + dist_y)
        angle_2 = radians(90 - (tmp_azim-new_angle))
        dist_x, dist_y = (limb * cos(angle_2), limb * sin(angle_2))
        point_2 = QgsPoint(second_point[0] - dist_x, second_point[1] - dist_y)
        tmp_azim = second_point.azimuth(first_point)
        angle_3 = radians(90 - (tmp_azim+new_angle))
        dist_x, dist_y = (limb * cos(angle_3), limb * sin(angle_3))
        point_3 = QgsPoint(second_point[0] + dist_x, second_point[1] + dist_y)
        angle_4 = radians(90 - (tmp_azim-new_angle))
        dist_x, dist_y = (limb * cos(angle_4), limb * sin(angle_4))
        point_4 = QgsPoint(first_point[0] - dist_x, first_point[1] - dist_y)
        vertices.extend([first_point, point_1, point_2, second_point, point_3, point_4, first_point])
        tempGeom = QgsGeometry.fromPolyline(vertices)
        num.append(tempGeom)

    layer = QgsMapLayerRegistry.instance().mapLayersByName(layer_name)[0]

    all_feats = {}
    index = QgsSpatialIndex()
    for ft in layer.getFeatures():
        index.insertFeature(ft)
        all_feats[ft.id()] = ft
    first = True
    tmp_geom = curr_feat.geometry()
    coords = tmp_geom.asMultiPolyline()
    if coords:
        new_coords = [QgsPoint(x, y) for x, y in z for z in coords]
    else:
        coords = tmp_geom.asPolyline()
        new_coords = [QgsPoint(x, y) for x, y in coords]
    first_point = new_coords[0]
    second_point = new_coords[-1]
    polyline=QgsGeometry.fromPolyline([first_point, second_point])
    idsList = index.intersects(tmp_geom.boundingBox())
    occurrences = 0
    for id in idsList:
        test_feat = all_feats[id]
        test_geom = test_feat.geometry()
        if tmp_geom.equals(test_geom):
            occurrences += 1
    if occurrences & 0x1:
        num = [polyline]
    else:
        num = []

    rapp = occurrences/2
    i=2
    new_angle = angle

    while i <= occurrences:
        draw=wires(polyline, new_angle, percentage)
        i += 2
        new_angle -= new_angle/rapp
    first = True
    for h in num:
        if first:
            geom = QgsGeometry(h)
            first = False
        else:
            geom = geom.combine(h)
    return geom
mgri
quelle
Beeindruckend! Das ist eine beeindruckende Antwort! Vielen Dank, dass Sie sich die Zeit genommen haben, es zu finden und zu teilen. Allerdings: 1. Ich habe Probleme, es auf meine Daten anzuwenden (wenn ich die Funktion anwende, verschwinden die Linien), aber ich denke, das Problem kommt von meinen Daten, da es auf einer temporären Ebene funktioniert und 2. es möglich wäre, es zu erstellen Dieses Muster nur zwischen dem Anfang und dem Ende jeder Polylinie statt zwischen jedem Scheitelpunkt?
GuiOm Clair
@GuiOmClair die Zeilen verschwinden, weil mit der Funktion etwas schief geht. Das Problem liegt nicht in der Verwendung einer temporären Ebene, sondern könnte in der Verwendung von MultiLine-Geometrien anstelle von Liniengeometrien liegen. Laden Sie die Ebene in QGIS und geben Sie diese beiden Zeilen in die Python-Konsole ein: layer=iface.activeLayer()und dann print layer.wkbType(). Klicken Sie auf Run: Welchen Wert hat die gedruckte Nummer?
Mgi
Die Zahl ist 5 (was bedeutet das?)
GuiOm Clair
@GuiOmClair Dies bedeutet, dass Ihre Ebene eine MultiLineString-Ebene ist, während ich davon ausgegangen bin, dass es sich um eine LineString-Ebene handelt (da Sie sie nicht angegeben haben). Dies wäre kein Problem und ich werde den Code so schnell wie möglich (möglicherweise morgen) ordnungsgemäß bearbeiten. Außerdem sollte ich in der Lage sein, die Drähte nur zwischen dem ersten und dem letzten Punkt jedes (Mehr-) Linien-Features zu rendern.
Mgi
1
Ja, die Features sind gerade Linien (da sie im Allgemeinen einfacher zu verwalten und zu exportieren sind). Daher ist es besser, die tatsächliche Länge der Drähte zu berücksichtigen.
GuiOm Clair