Skip to content

Profilo altimetrico con etichette sui waypoint (QGIS, Python)

I nostri migliori sviluppatori hanno esaurito i loro depositi di caffè, ricercando quotidianamente la soluzione, finché Simón non ha trovato la soluzione in Gitea e oggi la condividiamo con noi.

Soluzione:

Utilizzando il file xml e matplotlib è possibile ottenere dei bei risultati se ci si impegna un po'. Le trame di Matplotlib possono essere modificate per assomigliare molto al vostro esempio. La parte più difficile è stata quella di calcolare dove posizionare le etichette.

import matplotlib.pyplot as plt
import numpy as np

#Function to project geometries to get distances in meters instead of degrees
#From: https://gis.stackexchange.com/questions/163645/transforming-single-qgsgeometry-object-from-one-crs-to-another-using-pyqgis
def pr(ingeom):
    sourceCrs = QgsCoordinateReferenceSystem(4326)
    destCrs = QgsCoordinateReferenceSystem(32632)
    tr = QgsCoordinateTransform(sourceCrs, destCrs, QgsProject.instance())
    ingeom.transform(tr)
    return ingeom

tp = QgsProject.instance().mapLayersByName('track_points')[0]
zfield = 'ele'
orderfield = 'track_seg_point_id'
tpfeats = [f for f in tp.getFeatures()] #List all trackpoints
tpfeats.sort(key=lambda x: x[orderfield]) #Sort by orderfield (maybe they already are(?), doesnt matter)

#List all horizontal distances, the plot x values
hdist = [pr(p1.geometry()).distance(pr(p2.geometry())) for p1,p2 in zip(tpfeats, tpfeats[1:])]
hdist.insert(0,0) #First point
hdist = np.cumsum(hdist)
#List heights
zs = [f[zfield] for f in tpfeats] #The plot y values

#Which track point is closest to each waypoint?
#To know where to place each label in horizontal and height directions 
wp = QgsProject.instance().mapLayersByName('waypoints')[0]
placenamefield = 'name'
wpfeats = [f for f in wp.getFeatures()]
labels = [] #Each waypoints name attribute and the index of the closest trackpoint
for wpf in wpfeats:
    closest_trackpoint = min(tpfeats, key=lambda x: pr(x.geometry()).distance(pr(wpf.geometry())))
    ix = [tpf.id() for tpf in tpfeats].index(closest_trackpoint.id())
    labels.append([wpf[placenamefield], ix])

labels = [[l[0], hdist[l[1]]-100, zs[l[1]]+25] for l in labels] #List of lists like this: [['somelabeltext',horizontal distance, height], ['somelabeltext2',...

#Plot
plt.plot(hdist, zs, 'g')
plt.xlabel("Distance")
plt.ylabel("Elevation")
plt.title("Forgiving terrain 50k")
plt.hlines(range(1600,2700,100), xmin=0, xmax=4500, colors='silver', linestyles='solid')
x1,x2,y1,y2 = plt.axis()
plt.axis((x1,x2,1600,2600))
plt.fill_between(hdist, zs, color='#c1daba')

for labeltext, x, y, in labels:
    plt.annotate(labeltext, (x, y), color='g')
plt.show()

enter image description here

enter image description here

Si potrebbe combinare QGIS con HTML/Javascript (Highcharts o qualsiasi altro framework) per produrre profili altimetrici accattivanti. Con l'aiuto di alcune funzioni di espressione Python, è possibile utilizzare la funzione Atlante di QGIS per mostrare più tracce insieme ai waypoint corrispondenti. L'idea è quella di creare un livello virtuale di waypoint che sarà filtrato in base al nome delle tracce:

enter image description here
Tutto ciò che si deve fare è:

  1. Trascinare il file GPX nell'area di disegno di QGIS, importare e convertire il livello delle tracce da MultiLinestring a Linestring. Chiamare il livello risultante "traccia". Quindi importare il layer waypoint e rinominarlo in "waypoints_orig".
  2. Aggiungere una colonna "nome" al layer "traccia" e dare alle tracce un nome significativo. Quindi aggiungere una colonna "track" al layer "waypoints_orig" e assegnare i nomi delle tracce corrispondenti ai waypoint. Aggiungere il layer virtuale "waypoints" al progetto:
vlayer = QgsVectorLayer("?query=select * from waypoints_orig where track=''&geometry=geometry",'waypoints','virtual')
QgsProject.instance().addMapLayer(vlayer)
  1. Inserite il seguente codice Python nella sezione Macro del vostro progetto QGIS e attivate le macro Python:
from qgis.core import qgsfunction,QgsCoordinateTransform,QgsProject
from qgis.PyQt.QtCore import QTimer,QEventLoop
from itertools import accumulate
@qgsfunction(args=0, group='Custom', usesgeometry=True)
def GetProfileData(values, feature, parent):
    vertices = list(feature.geometry().vertices())
    dList,zList = zip(*[[vertices[i-1].distance(vertex),vertex.z()] for i,vertex in enumerate(vertices) if i > 0])
    dList = tuple(accumulate(dList))
    data = [list(elem) for elem in list(zip(dList,zList))]
    data.insert(0,[0,vertices[0].z()])
    return str(data)

@qgsfunction(args=0, group='Custom')
def wait1000(values, feature, parent):
    loop = QEventLoop()
    QTimer.singleShot(1000,loop.quit)
    loop.exec_()
    return 0

@qgsfunction(args=3, group='Custom', usesgeometry=True)
def GetWaypoints(values, feature, parent):
    waypointLayer = values[0]
    trackLayer = values[1]
    nameFilter = values[2]
    wplayer = QgsProject.instance().mapLayersByName(waypointLayer)[0]
    wplayer.setDataSource("?query=select * from waypoints_orig where track='%s'&geometry=geometry" % nameFilter,waypointLayer,'virtual')

    loop = QEventLoop()
    QTimer.singleShot(1000,loop.quit)
    loop.exec_()

    trlayer = QgsProject.instance().mapLayersByName(trackLayer)[0]
    geom = feature.geometry()

    crsSrc = wplayer.crs()
    crsDest = trlayer.crs()
    xform = QgsCoordinateTransform(crsSrc, crsDest,QgsProject.instance())

    ret = ''
    for wpf in wplayer.getFeatures():
        wpgeom =  wpf.geometry()
        wpgeom.transform(xform)
        vtx = geom.closestVertex(wpgeom.asPoint())[1]
        elev = geom.vertexAt(vtx).z()
        d = geom.distanceToVertex(vtx)
        print(d)
        ret += "{point: { xAxis: 0, yAxis: 0, x: %s, y: %s}, text: '%s'}," % (d,elev, wpf["name"])

    ret = ret[0:-1]
    return ret
  1. Generare un Atlante con il livello "traccia" come "livello di copertura".
  2. Aggiungere una cornice HTML al layout:





Highcharts Demo






  1. A causa di problemi di temporizzazione, dobbiamo aggiungere la funzione di espressione Python wait1000() al riquadro HTML (cioè usare l'override definito dai dati per "Escludi elemento dalle esportazioni")
    enter image description here

valutazioni e recensioni

Se hai qualche incertezza e possibilità di correggere il nostro post, ti consigliamo di prendere nota e lo leggeremo con grande piacere.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.