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()
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:
Tutto ciò che si deve fare è:
- 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".
- 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)
- 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
- Generare un Atlante con il livello "traccia" come "livello di copertura".
- Aggiungere una cornice HTML al layout:
Highcharts Demo
- 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")
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.