Utwórz łuk linii z linii i wartości

9

Próbuję odtworzyć fabułę Origin-Destination w następujący sposób:

wprowadź opis zdjęcia tutaj

Udało mi się przekształcić dane w tabelę MSOA do LAD i mogę narysować taką mapę dla jednego z początkowych plików MSOA.

wprowadź opis zdjęcia tutaj

Który raz, kiedy pozwolisz na (teraz absurdalne) odległości, ludzie dojeżdżają do pracy w Peak District, jest blisko.

Ale podoba mi się efekt, jaki autor osiągnął, „rozrzucając” linie. Oczywiście przy przepływach 522 i 371 nie mogę wybrać pojedynczej linii na osobę dojeżdżającą do pracy, ale fajnie byłoby stworzyć proporcjonalny łuk linii pokazujący liczbę osób odbywających podróż.

Pomyślałem, że będę mógł użyć Generatora Geometrii, ale bez konstrukcji pętli nie wydaje mi się, żebym robił postępy.

Ian Turton
źródło
To narzędzie ESRI może Cię zainteresować lub przynajmniej planszę do pomysłów na kod na temat tworzenia „klina” linii.
Hornbydd
A kiedy linia symbolizuje, powiedzmy 50 (100, 200) osób dojeżdżających do pracy na linię? Za pomocą jakiegoś kodu python (lub generatora geometrii, nie jestem pewien) możesz obrócić linie (x / 50) z wyraźną ilością.
Stefan

Odpowiedzi:

5

Wielkie wyzwanie!

Ta odpowiedź korzysta głównie z generatora geometrii i została napisana w QGIS 3.2. QGIS rozbił się (bez zapisania!) Tuż po tym, jak zbudowałem linie i prawie się poddałem, ale ostatnio używana lista wyrażeń uratowała dzień - kolejna korzyść z używania generatora geometrii

Zacząłem od dwóch zestawów punktów, jednego źródła i trzech miejsc docelowych. Miejsca docelowe są oznaczone liczbami:

Punkty początkowe

Następnie wygenerowałem linie łączące punkt źródłowy ze wszystkimi miejscami docelowymi za pomocą warstwy wirtualnej przy użyciu następującego kodu:

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

Połączone punkty

Następnie użyłem następującego wyrażenia generatora geometrii, aby stylizować linie:

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

To pobiera każdą linię i stosuje następujące kroki:

  1. Generuje bufor zwężający się od zerowej szerokości u źródła do szerokości skalowanej przez liczbę miejsc docelowych na końcu miejsca docelowego. Gęstość punktu buforowego jest również skalowana przez atrybut liczby miejsc docelowych.
  2. Wierzchołki bufora wielokąta są konwertowane na punkty (jest to prawdopodobnie zbędne), a następnie eksportowane do WKT, a nawiasy są usuwane za pomocą wyrażenia regularnego, przed konwersją na tablicę
  3. Tablica jest następnie rozszerzana z powrotem do ciągu WKT dla multilinestingu, wstawiając współrzędne punktu źródłowego i odpowiednie formatowanie - tworzy to osobną linię dla każdego wyodrębnionego wierzchołka podłączonego do punktu źródłowego
  4. WKT jest przekształcany z powrotem w obiekt geometrii i ostatecznie przecinany jest z buforem punktu źródłowego, aby przyciąć je z powrotem do okręgu, na którym znajduje się punkt docelowy (patrz dane wyjściowe a, tapered_bufferaby zrozumieć, dlaczego jest to konieczne)

Fani

Pisząc kroki, zdaję sobie sprawę, że konwersja do i z tablicy jest niepotrzebna, a cała manipulacja WKT może być wykonana za pomocą wyrażeń regularnych. To wyrażenie znajduje się poniżej, a jeśli tapered_arrayfunkcję można zastąpić inną, można ją również zastosować w 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))
Andy Harfoot
źródło
6

Twoje pytanie mnie zainteresowało.

To rozwiązanie działa tylko dla QGIS 2.x w konsoli Python

Jak wspomniano w moim komentarzu, tutaj jest mój pomysł stworzenia łuku linii za pomocą Pythona.

Mam dwupunktową warstwę:

ja. Jeden posiadający kapitał (identyfikator, kapitał)

ii. Jeden trzyma miasta (identyfikator, miasto, osoby dojeżdżające do pracy)

Liczba osób dojeżdżających do pracy jest „rozdzielana na banknoty” i będą to linie tworzące łuk. A zatem 371 osób dojeżdżających do pracy to kombinacja 3x100, 1x50, 2x10 i 1x1 i łącznie 7 banknotów. Następnie linie są stylizowane stylem opartym na regułach.

Oto kod:

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

Wynik może wyglądać następująco:

wprowadź opis zdjęcia tutaj

AKTUALIZACJA: rozróżnienie mężczyzna / kobieta

Wyniki w 4 warstwach pamięci.

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

Wynik może wyglądać następująco:wprowadź opis zdjęcia tutaj

Jedna rzecz, która nie jest idealna z kartograficznego punktu widzenia:

Wielkość łuku linii może na pierwszy rzut oka być irytująca, ponieważ większy łuk może reprezentować więcej osób dojeżdżających do pracy. Łuk może być większy przy mniejszej liczbie osób dojeżdżających do pracy (289 osób / 11 banknotów) niż inny przy większej liczbie osób (311 osób / 5 banknotów).

Stefan
źródło