Crea un arco di linee da una linea e un valore


9

Sto cercando di ricreare una trama Origine-Destinazione come questa:

inserisci qui la descrizione dell'immagine

Sono riuscito a trasferire i dati in una tabella da MSOA a LAD e posso disegnare una mappa come questa per uno degli MSOA di origine.

inserisci qui la descrizione dell'immagine

Il che una volta che permetti per le distanze (ora ridicole) che le persone al lavoro nel Peak District vanno al lavoro è vicino.

Ma mi piace abbastanza l'effetto che l'autore ha ottenuto "allargando" le linee. Ovviamente, con flussi di 522 e 371, non posso scegliere una sola linea per pendolare, ma sarebbe bello produrre un arco proporzionale di linee per mostrare il numero di persone che effettuano il viaggio.

Pensavo che sarei stato in grado di utilizzare Geometry Generator ma senza un costrutto loop, non riesco a fare progressi.


Questo strumento ESRI potrebbe interessarti, o almeno un trampolino di lancio per idee di codice sulla creazione di un "cuneo" di linee.
Hornbydd,

E quando una linea simboleggia, diciamo 50 (100, 200) pendolari, per linea? Con un po 'di codice Python (o il generatore di geometria, non ne sono sicuro) potresti ruotare le linee (x / 50) con una quantità distinta.
Stefan

Risposte:


5

Una grande sfida!

Questa risposta utilizza principalmente il generatore di geometria ed è stata scritta in QGIS 3.2. QGIS si è arrestato in modo anomalo (senza che io avessi salvato!) Subito dopo aver creato le linee per la prima volta e ho quasi rinunciato, ma l'elenco delle espressioni utilizzate di recente ha salvato la giornata - un altro vantaggio nell'uso del generatore di Geometria

Ho iniziato con due set di punti, una sorgente e tre destinazioni. Le destinazioni sono etichettate con i conteggi:

Punti iniziali

Ho quindi generato linee che collegano il punto di origine a tutte le destinazioni utilizzando un livello virtuale utilizzando il seguente codice:

SELECT d.Count_MF, Makeline( s.geometry, d.geometry) 'geometry' 
  FROM Source AS s JOIN Destinations AS d

Punti collegati

Quindi ho usato la seguente espressione del generatore di geometria per dare uno stile alle linee:

 intersection(
   geom_from_wkt( 
     'MULTILINESTRING ((' ||  $x_at( 0)  || ' ' || $y_at( 0)  || ', ' || 
     array_to_string(
       array_remove_at( string_to_array( regexp_replace(
             geom_to_wkt(nodes_to_points( tapered_buffer(  $geometry ,0, "Count_MF" * 200, floor("Count_MF" / 10)),true)),
             '[\\(\\)]','')),0)
     , ') , ('  ||  $x_at( 0)  || ' ' || $y_at( 0)  || ', ' )
    || '))')
    ,buffer( point_n(  $geometry ,1), $length))

Questo prende ogni riga e applica i seguenti passi:

  1. Genera un buffer conico che va dalla larghezza zero alla sorgente a una larghezza ridimensionata dal conteggio della destinazione alla fine della destinazione. Anche la densità del punto del buffer viene ridimensionata dall'attributo conteggio delle destinazioni.
  2. I vertici del poligono del buffer vengono convertiti in punti (questo è probabilmente superfluo), quindi esportati in WKT e le parentesi vengono rimosse utilizzando una regex, prima di convertirle in un array
  3. L'array viene quindi espanso nuovamente in una stringa WKT per una multistringa, inserendo le coordinate del punto di origine più la formattazione pertinente: questo crea una linea separata per ciascuno dei vertici estratti collegati al punto di origine
  4. Il WKT viene riconvertito in un oggetto geometria e infine intersecato con il buffer del punto di origine per ritagliarli al cerchio su cui si trova il punto di destinazione (vedere l'output di a tapered_bufferper capire perché ciò è necessario)

Fans

Nel redigere i passaggi, mi rendo conto che la conversione da e verso un array non è necessaria e che tutte le manipolazioni WKT possono essere eseguite con regex. Questa espressione è di seguito, e se la tapered_arrayfunzione può essere sostituita con un'altra, questa potrebbe essere usata anche in QGIS 2.18.

intersection(
   geom_from_wkt(
    'MULTILINESTRING ((' ||  $x_at( 0)  || ' ' || $y_at( 0)  || ', ' ||
  replace(
    regexp_replace(
      regexp_replace(
        geom_to_wkt(tapered_buffer(  $geometry ,0, "Count_MF" * 200, floor("Count_MF" / 10))),
      '^[^,]*,',''),
    ',[^,]*$',''),
  ',',') , ('  ||  $x_at( 0)  || ' ' || $y_at( 0)  || ', ')
  || '))')
,buffer( point_n(  $geometry ,1), $length))

6

La tua domanda mi ha incuriosito.

Questa soluzione funziona solo per QGIS 2.x in Python Console

Come menzionato nel mio commento qui è la mia idea di creare l'arco di linee con Python.

Ho due livelli di punti:

io. Uno che detiene il capitale (id, capitale)

ii. Uno che detiene le città (id, città, pendolari)

La quantità di pendolari è "separata in banconote" e queste saranno le linee che costruiscono l'arco. Quindi 371 pendolari sono una combinazione di 3x100, 1x50, 2x10 e 1x1 e in totale 7 banconote. Successivamente le linee sono definite da uno stile basato su regole.

Ecco il codice:

from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4 import QtGui, uic
from PyQt4.QtGui import *
from PyQt4.QtCore import *

for lyr in QgsMapLayerRegistry.instance().mapLayers().values():
    if lyr.name() == "capital":
        capital_layer = lyr

for lyr in QgsMapLayerRegistry.instance().mapLayers().values():
    if lyr.name() == "town":
        town_layer = lyr

    # creating the memory layer
d_lyr = QgsVectorLayer('LineString', 'distance', 'memory')
QgsMapLayerRegistry.instance().addMapLayer(d_lyr)
prov = d_lyr.dataProvider()
prov.addAttributes( [ QgsField("id", QVariant.Int), QgsField("banknote",QVariant.Int)])

    # function to create the banknotes
def banknoteOutput(number):
    number_list = []
    number_list.append(number)
    banknote_count = []
    temp_list = []
    banknote_list = []
    for n in number_list:
        total_sum = 0
        total = int(n/100)
        total_sum = total_sum + total
        if total > 0:
            banknote_count.append([total, 100])
        n = n-(total*100)
        total = int(n/50)
        total_sum = total_sum + total
        if total > 0:
            banknote_count.append([total, 50])
        n = n-(total*50)
        total = int(n/10)
        total_sum = total_sum + total
        if total > 0:
            banknote_count.append([total, 10])
        n = n-(total*10)
        total = int(n/5)
        total_sum = total_sum + total
        if total > 0:
            banknote_count.append([total, 5])
        n = n-(total*5)
        total = int(n/1)
        total_sum = total_sum + total
        if total > 0:
            banknote_count.append([total, 1])
        for i in banknote_count:
            temp_list.append(i*i[0])
        banknote_list = [item for sublist in temp_list for item in sublist][1::2]
        return banknote_list

        # creating lines with the amount of banknotes
for capital in capital_layer.getFeatures():
    for town in town_layer.getFeatures():
        commuter_splitting = banknoteOutput(town['commuters'])
        for i,banknote in enumerate(commuter_splitting):
            angle = 2
            distance = QgsDistanceArea()
            distance.measureLine(capital.geometry().asPoint(), town.geometry().asPoint())
            vect = QgsFeature()
            vect.setGeometry(QgsGeometry.fromPolyline([capital.geometry().asPoint(), town.geometry().asPoint()]))
            vect.geometry().rotate(0+(i*angle), capital.geometry().asPoint())
            vect.setAttributes([int(town["id"]), int(banknote)])
            prov.addFeatures([vect])

d_lyr.updateExtents()
d_lyr.triggerRepaint()
d_lyr.updateFields()

Il risultato potrebbe essere simile al seguente:

inserisci qui la descrizione dell'immagine

AGGIORNAMENTO: distinzione maschio / femmina

Risultati in 4 livelli di memoria.

from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4 import QtGui, uic
from PyQt4.QtGui import *
from PyQt4.QtCore import *

for lyr in QgsMapLayerRegistry.instance().mapLayers().values():
    if lyr.name() == "capital":
        capital_layer = lyr

for lyr in QgsMapLayerRegistry.instance().mapLayers().values():
    if lyr.name() == "town":
        town_layer = lyr

    # function to create the banknotes
def banknoteOutput(number):
    number_list = []
    number_list.append(number)
    banknote_count = []
    temp_list = []
    banknote_list = []
    for n in number_list:
        total_sum = 0
        total = int(n/100)
        total_sum = total_sum + total
        if total > 0:
            banknote_count.append([total, 100])
        n = n-(total*100)
        total = int(n/50)
        total_sum = total_sum + total
        if total > 0:
            banknote_count.append([total, 50])
        n = n-(total*50)
        total = int(n/10)
        total_sum = total_sum + total
        if total > 0:
            banknote_count.append([total, 10])
        n = n-(total*10)
        total = int(n/5)
        total_sum = total_sum + total
        if total > 0:
            banknote_count.append([total, 5])
        n = n-(total*5)
        total = int(n/1)
        total_sum = total_sum + total
        if total > 0:
            banknote_count.append([total, 1])
        for i in banknote_count:
            temp_list.append(i*i[0])
        banknote_list = [item for sublist in temp_list for item in sublist][1::2]
        return banknote_list

    # creating the male memory layer
cmt_male = QgsVectorLayer('LineString', 'Commuters_Male', 'memory')
QgsMapLayerRegistry.instance().addMapLayer(cmt_male)
prov_male = cmt_male.dataProvider()
prov_male.addAttributes( [ QgsField("id", QVariant.Int), QgsField("banknote",QVariant.Int)])

    # creating the male polygon memory layer
cmt_male_polygon = QgsVectorLayer('Polygon', 'Commuters_Male_Poly', 'memory')
QgsMapLayerRegistry.instance().addMapLayer(cmt_male_polygon)
prov_cmt_male_polygon = cmt_male_polygon.dataProvider()
prov_cmt_male_polygon.addAttributes( [ QgsField("id", QVariant.Int), QgsField("banknote",QVariant.Int)])

    # creating lines with the amount of banknotes
for capital in capital_layer.getFeatures():
    for town in town_layer.getFeatures():
        commuter_splitting = banknoteOutput(town['cmt_male'])
        points = []
        for i,banknote in enumerate(reversed(commuter_splitting)):
            angle = 2
            distance = QgsDistanceArea()
            distance.measureLine(capital.geometry().asPoint(), town.geometry().asPoint())
            vect = QgsFeature()
            vect.setGeometry(QgsGeometry.fromPolyline([capital.geometry().asPoint(), town.geometry().asPoint()]))
            vect.geometry().rotate(0+(i*angle), capital.geometry().asPoint())
            vect.setAttributes([int(town["id"]), int(banknote)])
            points.append(vect.geometry().asPolyline()[1])
            prov_male.addFeatures([vect])
        polygon = QgsFeature()
        points.insert(0,capital.geometry().asPoint())
        points.insert(len(points),capital.geometry().asPoint())
        polygon.setGeometry(QgsGeometry.fromPolygon([points]))
        polygon.setAttributes([1, 2])
        prov_cmt_male_polygon.addFeatures([polygon])

cmt_male.updateExtents()
cmt_male.triggerRepaint()
cmt_male.updateFields()
cmt_male_polygon.updateExtents()
cmt_male_polygon.triggerRepaint()
cmt_male_polygon.updateFields()

    # creating the female memory layer
cmt_female = QgsVectorLayer('LineString', 'Commuters_Female', 'memory')
QgsMapLayerRegistry.instance().addMapLayer(cmt_female)
prov_female = cmt_female.dataProvider()
prov_female.addAttributes( [ QgsField("id", QVariant.Int), QgsField("banknote",QVariant.Int)])

    # creating the female polygon memory layer
cmt_female_polygon = QgsVectorLayer('Polygon', 'Commuters_Female_Poly', 'memory')
QgsMapLayerRegistry.instance().addMapLayer(cmt_female_polygon)
prov_cmt_female_polygon = cmt_female_polygon.dataProvider()
prov_cmt_female_polygon.addAttributes( [ QgsField("id", QVariant.Int), QgsField("banknote",QVariant.Int)])

    # creating lines with the amount of banknotes
for capital in capital_layer.getFeatures():
    for town in town_layer.getFeatures():
        commuter_splitting = banknoteOutput(town['cmt_female'])
        points = []
        for i,banknote in enumerate(commuter_splitting):
            angle = 2
            distance = QgsDistanceArea()
            distance.measureLine(capital.geometry().asPoint(), town.geometry().asPoint())
            vect = QgsFeature()
            vect.setGeometry(QgsGeometry.fromPolyline([capital.geometry().asPoint(), town.geometry().asPoint()]))
            vect.geometry().rotate(-angle-(i*angle), capital.geometry().asPoint())
            vect.setAttributes([int(town["id"]), int(banknote)])
            points.append(vect.geometry().asPolyline()[1])
            prov_female.addFeatures([vect])
        polygon = QgsFeature()
        points.insert(0,capital.geometry().asPoint())
        points.insert(len(points),capital.geometry().asPoint())
        polygon.setGeometry(QgsGeometry.fromPolygon([points]))
        polygon.setAttributes([1, 2])
        prov_cmt_female_polygon.addFeatures([polygon])

cmt_female.updateExtents()
cmt_female.triggerRepaint()
cmt_female.updateFields()
cmt_female_polygon.updateExtents()
cmt_female_polygon.triggerRepaint()
cmt_female_polygon.updateFields()

Il risultato potrebbe essere simile al seguente:inserisci qui la descrizione dell'immagine

Una cosa che non è ideale dal punto di vista cartografico:

La dimensione di un arco di linea può essere irritante a prima vista, nel modo in cui un arco più grande potrebbe rappresentare più pendolari. Un arco può essere più grande con meno pendolari (289 pendolari / 11 banconote) rispetto a un altro con più pendolari (311 pendolari / 5 banconote).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.