Créer un arc de lignes à partir d'une ligne et d'une valeur

9

J'essaie de recréer un tracé origine-destination comme celui-ci:

entrez la description de l'image ici

J'ai réussi à fusionner les données dans une table MSOA vers LAD et je peux dessiner une carte comme celle-ci pour l'une des MSOA d'origine.

entrez la description de l'image ici

Ce qui une fois que vous autorisez les distances (maintenant ridicules) que les gens du Peak District font pour se rendre au travail est proche.

Mais j'aime bien l'effet que l'auteur a obtenu en "écartant" les lignes. Évidemment, avec des flux de 522 et 371, je ne peux pas opter pour une seule ligne par banlieue, mais ce serait bien de produire un arc proportionnel de lignes pour montrer le nombre de personnes qui font le trajet.

Je pensais que je serais capable d'utiliser le générateur de géométrie, mais sans construction de boucle, je n'arrive pas à avancer.

Ian Turton
la source
Cet outil ESRI peut vous intéresser, ou au moins un tremplin pour des idées de code sur la création d'un "coin" de lignes.
Hornbydd
Et quand une ligne symbolise, disons 50 (100, 200) navetteurs, par ligne? Avec du code python (ou le générateur de géométrie, je ne suis pas sûr), vous pouvez faire pivoter les lignes (x / 50) avec une quantité distincte.
Stefan

Réponses:

5

Un grand défi!

Cette réponse utilise principalement le générateur de géométrie et a été écrite dans QGIS 3.2. QGIS s'est écrasé (sans que j'aie enregistré!) Juste après avoir construit les lignes et j'ai presque abandonné, mais la liste d'expressions récemment utilisée a sauvé la journée - un autre bonus à l'utilisation du générateur de géométrie

J'ai commencé avec deux ensembles de points, une source et trois destinations. Les destinations sont étiquetées avec les décomptes:

Points initiaux

J'ai ensuite généré des lignes reliant le point source à toutes les destinations à l'aide d'une couche virtuelle à l'aide du code suivant:

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

Points connectés

Ensuite, j'ai utilisé l'expression du générateur de géométrie suivante pour styliser les lignes:

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

Cela prend chaque ligne et applique les étapes suivantes:

  1. Génère un tampon effilé allant de la largeur nulle à la source à une largeur mise à l'échelle par le nombre de destinations à l'extrémité de destination. La densité du point tampon est également mise à l'échelle par l'attribut count de destination.
  2. Les sommets du polygone tampon sont convertis en points (c'est peut-être superflu), puis exportés vers WKT, et les crochets sont supprimés à l'aide d'une expression régulière, avant de se convertir en tableau
  3. Le tableau est ensuite développé en une chaîne WKT pour une chaîne multiligne, en insérant les coordonnées du point source et la mise en forme appropriée - cela crée une ligne distincte pour chacun des sommets extraits connectés au point source
  4. Le WKT est reconverti en objet géométrique et finalement croisé avec le tampon du point source pour les clipser de nouveau sur le cercle sur lequel se trouve le point de destination (voir la sortie de a tapered_bufferpour comprendre pourquoi cela est nécessaire)

Ventilateurs

En écrivant les étapes, je me rends compte que la conversion vers et depuis un tableau est inutile, et toutes les manipulations WKT peuvent être effectuées avec des expressions régulières. Cette expression est ci-dessous, et si la tapered_arrayfonction peut être remplacée par une autre, alors elle pourrait également être utilisée dans 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
la source
6

Votre question m'a rendu curieux.

Cette solution fonctionne uniquement pour QGIS 2.x dans la console Python

Comme mentionné dans mon commentaire, voici mon idée de créer l'arc de lignes avec Python.

J'ai une couche à deux points:

je. Un tenant le capital (id, capital)

ii. Un tenant les villes (id, ville, navetteurs)

Le nombre de navetteurs est "séparé en billets de banque" et ce seront les lignes qui construiront l'arc. Ainsi, 371 navetteurs sont une combinaison de 3x100, 1x50, 2x10 et 1x1 et au total 7 billets. Ensuite, les lignes sont stylisées par un style basé sur des règles.

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

Le résultat pourrait ressembler à ceci:

entrez la description de l'image ici

MISE À JOUR: distinction homme / femme

Résultats en 4 couches mémoire.

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

Le résultat pourrait ressembler à ceci:entrez la description de l'image ici

Une chose qui n'est pas idéale d'un point de vue cartographique:

La taille d'un arc de ligne peut être irritante à première vue, dans la mesure où un arc plus grand pourrait représenter plus de navetteurs. Un arc peut être plus grand avec moins de navetteurs (289 navetteurs / 11 billets) qu'un autre avec plus de navetteurs (311 navetteurs / 5 billets).

Stefan
la source