Mudando a exibição de linhas sobrepostas no QGIS?

10

Quando os pontos se sobrepõem, existe esta propriedade que permite exibir automaticamente o lote separadamente em torno de onde estão, chamado 'deslocamento de ponto'. Mas não funciona para linhas, mesmo assim parece-me bastante conceitualmente viável para conseguir algo assim:

insira a descrição da imagem aqui

Eu absolutamente preciso ver as diferentes linhas que, na realidade, estão todas no mesmo lugar (estou trabalhando em redes de telecomunicações). A única maneira que vejo por agora é realmente criar linhas diferentes, como na figura acima, criando erros espaciais.

Estou usando o QGIS 2.14.

GuiOm Clair
fonte
Eu acho que algo pode ser feito recorrendo ao estilo. A linha do meio é a linha de partida? Então, vejo que você criou cada uma das outras linhas usando três geometrias diferentes. Portanto, minha pergunta é se existem regras adicionais específicas para renderizá-las.
Mgri 4/17
@gri Não tenho certeza de entender sua pergunta. A figura fornecida é um exemplo em que desenhei cinco linhas diferentes para fins de demonstração. Na realidade, seria mais do que essas 5 linhas, de fato, estão no local da linha do meio (são fios, portanto, todas presas na mesma bainha).
GuiOm Clair
1
Você também pode renderizar linhas com um deslocamento ("deslocamento"), mas elas não se encontrariam nos pontos inicial e final.
precisa saber é
@ AndreJ Sim, e outro problema seria que seria uma operação bastante manual, onde eu precisaria de algo mais automático, pois seria usado por muitos usuários.
GuiOm Clair
1
@GuiOmClair Seguindo a imagem anexa, presumi que você partisse de uma linha que se sobrepusesse (por exemplo) a outras quatro linhas e que precisasse encontrar uma maneira de exibi-las separadamente, mesmo que se sobreponham. Acabei de dizer que seria possível reproduzir o que é exibido na imagem anexada sem a necessidade de criar novas geometrias (mas apenas recorrendo às propriedades de estilo da camada inicial). Outra maneira seria a proposta por AndreJ, mas parece que não se encaixa nas suas necessidades.
Mgri 4/17

Respostas:

12

Proponho uma abordagem que só se repete em um gerador de geometria e em uma função personalizada.

Antes de começar, quero enfatizar que vou focar a atenção na explicação das coisas mínimas a serem feitas para reproduzir o resultado desejado: isso significa que alguns outros parâmetros menores (como tamanhos, larguras etc.) devem ser facilmente ajustados por você para melhor atender às suas necessidades.

Portanto, esta solução funciona tanto para sistemas de referência geográfica quanto projetados: a seguir, assumi o uso de um CRS projetado (ou seja, as unidades de medida são metros), mas você pode alterá-lo de acordo com o seu CRS.


Contexto

Vamos assumir que comece a partir desta camada vetorial de cadeia de linhas que representa os fios (os rótulos representam o número de fios coincidentes):

insira a descrição da imagem aqui


Solução

Primeiro, vá para Layer Properties | Stylee escolha o Single symbolrenderizador.

Na Symbol selectorcaixa de diálogo, escolha um Geometry generatorcomo tipo de camada de símbolo e Linestring / MultiLinestringcomo tipo de geometria. Em seguida, clique na Function Editorguia:

insira a descrição da imagem aqui

Em seguida, clique New filee digite draw_wirescomo o nome da nova função:

insira a descrição da imagem aqui

Você verá que uma nova função foi criada e está listada no lado esquerdo da caixa de diálogo. Agora, clique no nome da função e substitua o padrão @qgsfunctionpelo seguinte código (não esqueça de adicionar todas as bibliotecas anexadas aqui):

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

Depois de fazer isso, clique no Loadbotão e você poderá ver a função no Custommenu da Expressioncaixa de diálogo.

Agora, digite esta expressão (veja a imagem abaixo como referência):

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

insira a descrição da imagem aqui

Você acabou de executar uma função que está dizendo, de uma maneira imaginária:

"Para a camada atual ( @layer_name ) e o recurso atual ( $ currentfeature ), exiba os fios juntos usando uma abertura máxima inicial de 40 graus e com uma mudança de direção a uma distância de 0,3 vezes o comprimento do segmento atual."

A única coisa que você precisa alterar é o valor dos dois primeiros parâmetros que desejar, mas obviamente de uma maneira razoável (deixe os outros parâmetros de função conforme fornecido).

Por fim, clique no Applybotão para aplicar as alterações.

Você verá algo assim:

insira a descrição da imagem aqui

como esperado.


EDITAR

De acordo com uma solicitação específica apresentada pelo OP em um comentário:

"Seria possível criar esse padrão apenas entre o início e o final de cada polilinha, em vez de entre cada vértice?"

Editei um pouco o código. A seguinte função deve retornar o resultado esperado:

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
fonte
Uau! Essa é uma resposta impressionante! Muito obrigado por dedicar esse tempo para encontrá-lo e compartilhá-lo. No entanto: 1. Estou tendo problemas para aplicá-lo aos meus dados (quando aplico a função, as linhas desaparecem), mas acho que o problema vem dos meus dados, pois funciona em uma camada temporária e 2. seria possível criar esse padrão apenas entre o início e o final de cada polilinha em vez de entre cada vértice?
GuiOm Clair
@GuiOmClair as linhas desaparecem porque algo dá errado com a função. O problema não vem do uso da camada temporária, mas pode estar relacionado ao uso das geometrias do MultiLine em vez das geometrias de linha. Por favor, carregue a camada no QGIS e digite essas duas linhas no console Python: layer=iface.activeLayer()e então print layer.wkbType(). Clique em Run: qual é o valor do número impresso?
Mgri 9/05
O número é 5 (o que significa isso?)
Guiom Clair
@GuiOmClair Significa que sua camada é uma camada MultiLineString, enquanto eu supus que era uma camada LineString (já que você não a especificou). Isso não seria um problema e editarei o código adequadamente assim que possível (talvez amanhã). Além disso, eu devo ser capaz de processar os fios apenas entre o primeiro e o último ponto de cada recurso de (multi) linha.
Mgri 9/05
1
Sim, os recursos são linhas retas (já que geralmente são mais fáceis de gerenciar e exportar), portanto seria melhor considerar o comprimento real dos fios.
GuiOm Clair