Affichage décalé des lignes qui se chevauchent dans QGIS?

10

Lorsque les points se chevauchent, il existe cette propriété qui permet d'afficher automatiquement le lot d'entre eux séparément autour de l'endroit où ils se trouvent, appelé «déplacement de point». Mais cela ne fonctionne pas pour les lignes, même si cela me semble tout à fait conceptuellement réalisable afin de réaliser quelque chose comme ça:

entrez la description de l'image ici

J'ai absolument besoin de voir les différentes lignes qui sont en réalité toutes au même endroit (je travaille dans les réseaux de télécommunications). La seule façon que je vois pour l'instant est de créer vraiment des lignes différentes comme dans l'image ci-dessus, créant ainsi des erreurs spatiales.

J'utilise QGIS 2.14.

GuiOm Clair
la source
Je pense que quelque chose pourrait être fait en revenant au style. La ligne du milieu est-elle la ligne de départ? Ensuite, je vois que vous avez créé chacune des autres lignes en utilisant trois géométries différentes, donc ma question est de savoir s'il existe des règles supplémentaires spécifiques pour les rendre?
mgri
@mgri Je ne suis pas sûr de comprendre votre question. L'image fournie est un exemple dans lequel j'ai dessiné cinq lignes différentes à des fins de démonstration. En réalité ce serait plus que ces 5 lignes sont en effet bien à l'endroit de la moyenne (ce sont des fils, donc tous coincés dans la même gaine).
GuiOm Clair
1
Vous pouvez également restituer des lignes avec un déplacement ("offset"), mais elles ne se rencontreraient pas aux points de début et de fin.
AndreJ
@AndreJ Oui, et un autre problème serait que ce serait une opération assez manuelle où j'aurais besoin de quelque chose de plus automatique car il serait utilisé par de nombreux utilisateurs.
GuiOm Clair
1
@GuiOmClair Suite à l'image ci-jointe, j'ai supposé que vous partiez d'une ligne qui chevauche (par exemple) quatre autres lignes et que vous devez trouver un moyen de les afficher séparément, même si elles se chevauchent. Je viens de dire qu'il pourrait être possible de reproduire ce qui est affiché dans l'image jointe sans avoir besoin de créer de nouvelles géométries (mais seulement en revenant aux propriétés de style de la couche de départ). Une autre façon serait celle proposée par AndreJ, mais il semble qu'elle ne correspond pas à vos besoins.
mgri

Réponses:

12

Je propose une approche qui ne revient qu'à un générateur de géométrie et une fonction personnalisée.

Avant de commencer, je tiens à souligner que je concentrerai l'attention sur l'explication des choses minimales à faire pour reproduire le résultat souhaité: cela signifie que certains autres paramètres mineurs (comme les tailles, les largeurs, etc.) devraient être facilement ajustés par vous pour mieux répondre à vos besoins.

Par conséquent, cette solution fonctionne à la fois pour les systèmes de référence géographiques et projetés: dans ce qui suit, j'ai supposé utiliser un CRS projeté (c'est-à-dire que les unités de mesure sont des mètres), mais vous pouvez les modifier en fonction de votre CRS.


Le contexte

Supposons que nous partions de cette couche de vecteur de ligne linéaire représentant les fils (les étiquettes représentent le nombre de fils qui se chevauchent (coïncidents)):

entrez la description de l'image ici


Solution

Tout d'abord, accédez à Layer Properties | Style, puis choisissez le Single symbolmoteur de rendu.

Dans la Symbol selectorboîte de dialogue, choisissez un Geometry generatortype de calque de symbole et un type Linestring / MultiLinestringde géométrie. Cliquez ensuite sur l' Function Editoronglet:

entrez la description de l'image ici

Ensuite, cliquez sur New fileet tapez draw_wirescomme nom de la nouvelle fonction:

entrez la description de l'image ici

Vous verrez qu'une nouvelle fonction a été créée et elle est répertoriée sur le côté gauche de la boîte de dialogue. Maintenant, cliquez sur le nom de la fonction et remplacez la valeur @qgsfunctionpar défaut par le code suivant (n'oubliez pas d'ajouter toutes les bibliothèques attachées ici):

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

Une fois que vous avez fait cela, cliquez sur le Loadbouton et vous pourrez voir la fonction dans le Custommenu de la Expressionboîte de dialogue.

Maintenant, tapez cette expression (voir l'image ci-dessous comme référence):

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

entrez la description de l'image ici

Vous venez d'exécuter une fonction qui dit, de manière imaginaire:

"Pour la couche actuelle ( @layer_name ) et la fonction actuelle ( $ currentfeature ), affichez les fils ensemble en utilisant une ouverture maximale initiale de 40 degrés et avec un changement de direction à une distance de 0,3 fois la longueur du segment actuel."

La seule chose que vous devez changer est la valeur des deux premiers paramètres comme vous le souhaitez, mais évidemment de manière raisonnable (laissez les autres paramètres de fonction tels qu'ils sont fournis).

Enfin, cliquez sur le Applybouton pour appliquer les modifications.

Vous verrez quelque chose comme ceci:

entrez la description de l'image ici

comme prévu.


ÉDITER

Selon une demande spécifique soulevée par le PO dans un commentaire:

"Serait-il possible de créer ce motif uniquement entre le début et la fin de chaque polyligne plutôt qu'entre chaque sommet?"

J'ai légèrement modifié le code. La fonction suivante doit retourner le résultat attendu:

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
la source
Hou la la! Voilà une réponse impressionnante! Merci beaucoup d'avoir pris ce temps pour le trouver et le partager. Cependant: 1. J'ai du mal à l'appliquer à mes données (lorsque j'applique la fonction, les lignes disparaissent), mais je suppose que le problème vient de mes données car il fonctionne sur une couche temporaire et 2. serait-il possible de créer ce motif uniquement entre le début et la fin de chaque polyligne plutôt qu'entre chaque sommet?
GuiOm Clair
@GuiOmClair les lignes disparaissent car quelque chose ne va pas avec la fonction. Le problème ne vient pas de l'utilisation d'une couche temporaire, mais il pourrait être lié à l'utilisation de géométries MultiLine au lieu de géométries Line. Veuillez charger la couche dans QGIS, puis tapez ces deux lignes dans la console Python: layer=iface.activeLayer()et ensuite print layer.wkbType(). Cliquez Run: quelle est la valeur du numéro imprimé?
mgri
Le nombre est 5 (qu'est-ce que cela signifie?)
GuiOm Clair
@GuiOmClair Cela signifie que votre couche est une couche MultiLineString, alors que je supposais que c'était une couche LineString (puisque vous ne l'avez pas spécifié). Ce ne serait pas un problème et je modifierai correctement le code dès que possible (peut-être demain). De plus, je ne devrais pouvoir rendre les fils qu'entre le premier et le dernier point de chaque entité (multi) ligne.
mgri
1
Oui, les fonctionnalités sont des lignes droites (car elles sont généralement plus faciles à gérer et à exporter), il serait donc préférable de prendre en compte la longueur réelle des fils.
GuiOm Clair