Erstellen Sie einen Linienbogen aus einer Linie und einem Wert

9

Ich versuche, ein Origin-Destination-Diagramm wie folgt neu zu erstellen :

Geben Sie hier die Bildbeschreibung ein

Ich habe es geschafft, die Daten in eine MSOA-zu-LAD-Tabelle zu mischen und kann eine Karte wie diese für eine der Ursprungs-MSOA zeichnen.

Geben Sie hier die Bildbeschreibung ein

Was, wenn Sie einmal die (jetzt lächerlichen) Entfernungen berücksichtigen, die Menschen im Peak District zur Arbeit pendeln, ist nahe.

Aber ich mag den Effekt, den der Autor erzielt hat, indem er die Zeilen "gespreizt" hat. Natürlich kann ich bei Flüssen von 522 und 371 nicht eine einzige Linie pro Pendler wählen, aber es wäre schön, einen proportionalen Linienbogen zu erzeugen, um die Anzahl der Personen anzuzeigen, die die Reise machen.

Ich dachte, ich könnte den Geometrie-Generator verwenden, aber ohne ein Schleifenkonstrukt kann ich keine Fortschritte machen.

Ian Turton
quelle
Dieses ESRI-Tool könnte für Sie von Interesse sein oder zumindest ein Sprungbrett für Code-Ideen zum Erstellen eines "Keils" von Linien.
Hornbydd
Und wenn eine Linie symbolisiert, sagen wir 50 (100, 200) Pendler pro Linie? Mit etwas Python-Code (oder dem Geometriegenerator, bei dem ich mir nicht sicher bin) können Sie die Linien (x / 50) mit einem bestimmten Betrag drehen.
Stefan

Antworten:

5

Eine große Herausforderung!

Diese Antwort verwendet hauptsächlich den Geometriegenerator und wurde in QGIS 3.2 geschrieben. QGIS stürzte ab (ohne dass ich gespeichert habe!), Kurz nachdem ich die Linien zum ersten Mal erstellt hatte und ich fast aufgegeben hätte, aber die kürzlich verwendete Ausdrucksliste hat den Tag gerettet - ein weiterer Bonus für die Verwendung des Geometriegenerators

Ich begann mit zwei Punktsätzen, einer Quelle und drei Zielen. Die Ziele sind mit den Zählungen gekennzeichnet:

Anfangspunkte

Ich habe dann Linien erzeugt, die den Quellpunkt mit allen Zielen unter Verwendung einer virtuellen Schicht unter Verwendung des folgenden Codes verbinden:

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

Verbundene Punkte

Dann habe ich den folgenden Geometriegeneratorausdruck verwendet, um die Linien zu formatieren:

 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))

Dies dauert jede Zeile und führt die folgenden Schritte aus:

  1. Erzeugt einen sich verjüngenden Puffer, der von der Breite Null an der Quelle bis zu einer Breite reicht, die durch die Zielanzahl am Zielende skaliert wird. Die Pufferpunktdichte wird auch durch das Zielzählattribut skaliert.
  2. Die Scheitelpunkte des Pufferpolygons werden in Punkte konvertiert (dies ist möglicherweise überflüssig) und dann in WKT exportiert. Die Klammern werden mithilfe eines regulären Ausdrucks entfernt, bevor sie in ein Array konvertiert werden
  3. Das Array wird dann für einen Multilinestring wieder zu einer WKT-Zeichenfolge erweitert, wobei die Koordinaten des Quellpunkts sowie die relevante Formatierung eingefügt werden. Dadurch wird für jeden der extrahierten Scheitelpunkte, die mit dem Quellpunkt verbunden sind, eine separate Linie erstellt
  4. Die WKT wird zurück in ein Geometrieobjekt konvertiert und schließlich mit dem Puffer des Quellpunkts geschnitten, um sie wieder in den Kreis zu schneiden, auf dem sich der Zielpunkt befindet (siehe die Ausgabe von a, um tapered_bufferzu verstehen, warum dies erforderlich ist).

Fans

Beim Schreiben der Schritte stelle ich fest, dass die Konvertierung zu und von einem Array nicht erforderlich ist und alle WKT-Manipulationen mit regulären Ausdrücken durchgeführt werden können. Dieser Ausdruck ist unten aufgeführt. Wenn die tapered_arrayFunktion durch eine andere ersetzt werden kann, kann dies auch in QGIS 2.18 verwendet werden.

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))
Andy Harfoot
quelle
6

Ihre Frage hat mich neugierig gemacht.

Diese Lösung funktioniert nur für QGIS 2.x in der Python-Konsole

Wie in meinem Kommentar hier erwähnt, ist meine Idee, den Linienbogen mit Python zu erstellen.

Ich habe zwei Punktebenen:

ich. Einer, der das Kapital hält (id, Kapital)

ii. Einer hält die Städte (Ausweis, Stadt, Pendler)

Die Anzahl der Pendler wird "in Banknoten aufgeteilt" und dies sind die Linien, die den Bogen bilden. 371 Pendler sind also eine Kombination aus 3x100, 1x50, 2x10 und 1x1 und insgesamt 7 Banknoten. Anschließend werden die Linien durch ein regelbasiertes Styling gestaltet.

Hier ist der Code:

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()

Das Ergebnis könnte folgendermaßen aussehen:

Geben Sie hier die Bildbeschreibung ein

UPDATE: Unterscheidung männlich / weiblich

Ergebnisse in 4 Speicherschicht.

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()

Das Ergebnis könnte folgendermaßen aussehen:Geben Sie hier die Bildbeschreibung ein

Eine Sache, die aus kartografischer Sicht nicht ideal ist:

Die Größe eines Linienbogens kann auf den ersten Blick irritierend sein, da ein größerer Bogen mehr Pendler darstellen könnte. Ein Bogen kann mit weniger Pendlern (289 Pendler / 11 Banknoten) größer sein als ein anderer mit mehr Pendlern (311 Pendler / 5 Banknoten).

Stefan
quelle