In Python, qual è il modo migliore per replicare il plugin QGIS Heatmap?
Vorrei creare una mappa termica ponderata spaziale in Python in cui ho il controllo sul riquadro di delimitazione, le dimensioni della griglia e la larghezza di banda. Ad esempio, se voglio creare una mappa di calore della popolazione su una griglia di 200 * 200 metri con una larghezza di banda di 500 metri:
Utilizzando il plugin QGIS Heatmap:
kde (posizioni = xy, peso = popolazione, boundingbox, gridsize = 200, bandwidth = 500, kernel = "gaussian")
Non ho trovato un pacchetto in grado di fare proprio questo.
Seguendo l'esempio: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.gaussian_kde.htmlDa quello che ho capito è che si dovrebbe prima creare una meshgrid e rimodellare il kde sulla griglia. Questo è quello che ho provato finora, ma ci sono 3 problemi:
- Questa parte richiede molto tempo: zz2 = kernel ((np.ravel (x_mesh), np.ravel (y_mesh))) - 470,44 secondi su 19150 punti dove in Qgis sono pochi secondi.
- L'asse y è ancora invertito.
- Non sei sicuro di come impostare la larghezza di banda su un valore costante di 500
Codice:
from scipy import stats
from shapely.geometry import Point
import geopandas as gpd
import numpy as np
from osgeo import gdal
from osgeo import osr
import time
import pandas as pd
#Input variables
grid_size=200
h=500
#Data
x = np.array([-285815.24600105, -285905.88928823, -285596.62853068,
-285376.49911475, -284530.02007635, -285976.25971212,
-285079.67702268, -286188.5497945 , -284810.5502149 ,
-285707.6207015 , -285072.46928953, -284872.60260027,
-285567.26057971, -284593.23417313, -285318.32010344,
-285767.26258091, -284600.84807157, -285185.11331713,
-284727.6299865 , -284982.16195329, -284983.76372273,
-284753.27862336, -284688.7406417 , -284963.14336973,
-285102.43887492, -284610.34171822, -284710.3065015 ,
-284501.4211114 , -286246.95919243, -284923.81296141,
-285880.14147568, -285099.55526278, -284611.19426662,
-286108.759291 , -285358.72069313, -284906.19046438,
-286177.47753297, -284571.34168874, -285519.67954529,
-285162.43056364, -285915.21656255, -285599.37350284,
-284494.30220736, -284577.61017269, -284793.26653895,
-285115.45608425, -285915.80558585])
y = np.array([2906143.2521925 , 2906369.43984717, 2906356.32381486,
2906525.61255684, 2906540.60531809, 2906586.42258352,
2906450.18112564, 2906707.0534267 , 2906492.11113259,
2906725.89380165, 2906763.87804504, 2906779.45155159,
2906947.06861677, 2906866.70425729, 2906864.30448599,
2907483.92004085, 2907011.01133657, 2907183.5411114 ,
2907125.55907197, 2907137.32092455, 2907403.91453819,
2907417.71324586, 2907309.79221579, 2907636.60663656,
2907754.1172582 , 2907559.26299843, 2907747.3226264 ,
2907750.19855555, 2907966.22491989, 2907996.07814694,
2908213.91807075, 2908003.55703708, 2908039.44317742,
2908125.88796091, 2908214.69867858, 2908353.7416716 ,
2908438.61892689, 2908267.34531307, 2908373.17285713,
2908369.37610769, 2908494.01196971, 2908602.17039364,
2908507.03090379, 2908737.87072884, 2908685.12160762,
2908645.49069608, 2908723.21635992])
weight_value = np.array([7985585., 7985084., 7985237., 7984908., 7985446., 7985504.,
7984242., 7984369., 7984735., 7985019., 7984076., 7984041.,
7983581., 7984401., 7985564., 7983173., 7984675., 7984697.,
7984507., 7984368., 7984972., 7984348., 7985082., 7983377.,
7984336., 7984319., 7984419., 7984460., 7984684., 7984942.,
7984028., 7985162., 7984346., 7983969., 7984232., 7985258.,
7984913., 7985284., 7984889., 7984567., 7984341., 7984907.,
7984793., 7982612., 7983755., 7984752., 7983938.])
#create geodataframe
df_geometry = [Point(xy) for xy in zip(x, y)]
gdf_centroid = gpd.GeoDataFrame(df_geometry, geometry=df_geometry)
gdf_centroid['weigth'] = weight_value
#Create GRID
gdf_centroidg_bb = gdf_centroid.total_bounds
xmin = gdf_centroidg_bb[0]
xmax = gdf_centroidg_bb[2]
ymin = gdf_centroidg_bb[1]
ymax = gdf_centroidg_bb[3]
x_grid = np.arange(xmin-h, xmax+h, grid_size)
y_grid = np.arange(ymin-h, ymax+h, grid_size)
x_mesh, y_mesh = np.meshgrid(x_grid, y_grid)
#Create Kernel Density Estimation
positions = np.vstack([x_mesh.ravel(), y_mesh.ravel()])
values = np.vstack([x, y])
kernel = stats.gaussian_kde(values, weights = weight_value)
kernel.set_bandwidth(bw_method=kernel.factor / 3.)
#This takes too long. (470.44 seconds on 19150 points)
start = time.time()
zz2 = kernel((np.ravel(x_mesh), np.ravel(y_mesh)))
end = time.time()
print(end - start)
#Reshape the kde
zz2 = np.reshape(zz2.T, x_mesh.shape)
#Setup the raster metadata
nrows,ncols = np.shape(y_mesh)
xres = (xmax-xmin)/float(ncols)
yres = (ymax-ymin)/float(nrows)
geotransform=(xmin,xres,0,ymax,0, -yres)
#Export kernel density to geotiff
output_raster = gdal.GetDriverByName('GTiff').Create('population_heatmap2.tif',ncols, nrows, 1 ,gdal.GDT_Float32)
output_raster.SetGeoTransform(geotransform)
srs = osr.SpatialReference()
srs.ImportFromEPSG(2051)
output_raster.SetProjection( srs.ExportToWkt() )
output_raster.GetRasterBand(1).WriteArray(zz2)
output_raster.FlushCache()
C'è un motivo migliore per farlo in uno script Python senza usare QGIS?
Risposte
Puoi chiamare moduli / plugin di QGIS ecc. Dall'esterno di QGIS. https://docs.qgis.org/3.4/fi/docs/pyqgis_developer_cookbook/intro.html#using-pyqgis-in-standalone-scripts Anche questo link parla dell'utilizzo di algoritmi di elaborazione dalla console, che possono anche essere di aiuto. https://docs.qgis.org/3.10/en/docs/user_manual/processing/console.html#processing-console
Forse potresti provare a capire come chiamare il plugin Heatmap tramite pyQGIS, essenzialmente replicando lo strumento GUI al di fuori di QGIS. Controlla questohttps://docs.qgis.org/3.10/en/docs/user_manual/processing_algs/qgis/interpolation.html#python-code
Sembra che tu possa chiamare l'algoritmo da Python, quindi forse leggi quanto sopra e guarda come vai!
Alla fine sono riuscito a eseguire una mappa di calore utilizzando i moduli Qgis. Per un utente non tecnico è stata un po 'difficile, ma ne è valsa la pena. Ho eseguito una mappa di calore su punti sparsi in tutta l'Africa su una griglia di 200 metri in poco più di 3 minuti.
Software:
- Anacondo / Python 3.7.4
- Pycharm
- Qgis 3.14
Processo seguito:
- Per importare qgis.core ho seguito il consiglio qui: inserisci la descrizione del link qui In pratica dicendo di aggiungere prima quanto segue alle tue variabili d'ambiente PATH:
C: \ Programmi \ QGIS 3.14 \ bin; C: \ Programmi \ QGIS 3.14 \ apps \ qgis \ bin
E in secondo luogo per creare un file .pth nel tuo ambiente virtuale in Lib \ site-packages, che contiene le righe:
- C: \ Programmi \ QGIS 3.14 \ apps \ qgis \ python
- C: \ Programmi \ QGIS 3.14 \ apps \ Python37 \
- C: \ Programmi \ QGIS 3.14 \ apps \ Python37 \ lib \
- C: \ Programmi \ QGIS 3.14 \ apps \ Python37 \ lib \ site-packages
- C: \ Programmi \ QGIS 3.14 \ bin
- C: \ Programmi \ QGIS 3.14 \ include
- C: \ Programmi \ QGIS 3.14 \ apps \ qgis \ bin
Il mio screenshot del percorso:
Il problema successivo era impostare QgsApplication che presentava un errore: "Questa applicazione non si è avviata perché non è riuscita a trovare o caricare il plugin della piattaforma Qt" Questo è stato risolto aggiungendo la seguente variabile d'ambiente allo script:
os.environ ['QT_QPA_PLATFORM_PLUGIN_PATH'] = 'C: \ Programmi \ QGIS 3.14 \ apps \ Qt5 \ plugins'
vedere:
QGIS 3.14 vs "nessun plugin della piattaforma Qt potrebbe essere inizializzato" .
Ora QgsApplication può essere inizializzato:
from qgis.core import (
QgsApplication,
QgsProcessingFeedback,
QgsVectorLayer
)
QgsApplication.setPrefixPath('C:\\Program Files\\QGIS 3.14\\apps\\qgis\\', True)
from PyQt5 import QtGui, QtCore
qgs = QgsApplication([], False)
qgs.initQgis()
Il problema successivo era che nella mia elaborazione mancavano tutti gli algoritmi nativi. Per risolvere questo problema ho dovuto aggiungere il seguente codice:
import sys sys.path.append ('C: \ Program Files \ QGIS 3.14 \ apps \ qgis \ python \ plugins')
importazione elaborazione da processing.core.Processing importazione elaborazione Processing.initialize ()
da qgis.analysis import QgsNativeAlgorithms QgsApplication.processingRegistry (). addProvider (QgsNativeAlgorithms ())
vedere:> Utilizzo degli algoritmi di elaborazione QGIS3 da script PyQGIS standalone (al di fuori della GUI)
Nota: potresti ricevere un messaggio di errore indicante che non è possibile trovare il file proj.db. Anche con questo errore il processo è terminato come previsto, ma questo può anche essere risolto impostando la variabile d'ambiente PROJ_LIB in modo che punti alla directory dei dati PROJ.4 (dove risiede proj.db). vedi:> Ogr2ogr: ERRORE 1: PROJ: pj_obj_create: Impossibile trovare proj.db
Questo però non ha funzionato per me. Ho aggiunto PROJ_DEBUG = 3 alle mie variabili d'ambiente e ho visto che cerca proj.db in C: / Users / nome_utente / AppData / Roaming / python \ profiles \ default / proj \ proj.db
Quindi ho appena copiato il file proj.db in quella posizione.
Finalmente ho potuto eseguire l'algoritmo della mappa di calore:
params = {'INPUT': 'path to shapefile', 'RADIUS': 500, 'RADIUS_FIELD': '', 'PIXEL_SIZE': 200, 'WEIGHT_FIELD': 'SAL_ID', 'KERNEL': 0, 'DECAY': 0, 'OUTPUT_VALUE': 0, 'OUTPUT': 'output \ test6.tif'}
processing.run ("qgis: heatmapkerneldensityestimation", params)
Script completo:
import os
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = 'C:\\Program Files\\QGIS 3.14\\apps\\Qt5\\plugins'
#Tried to import the proj.db but did not work
# os.environ['GDAL_DATA'] = '/home/server/anaconda3/share/gdal'
# os.environ['PROJ_LIB'] = '/home/server/anaconda3/share/proj'
from qgis.core import (
QgsApplication,
QgsProcessingFeedback,
QgsVectorLayer
)
QgsApplication.setPrefixPath('C:\\Program Files\\QGIS 3.14\\apps\\qgis\\', True)
from PyQt5 import QtGui, QtCore
qgs = QgsApplication([], False)
qgs.initQgis()
import sys
sys.path.append('C:\\Program Files\\QGIS 3.14\\apps\\qgis\\python\\plugins')
import processing
from processing.core.Processing import Processing
Processing.initialize()
from qgis.analysis import QgsNativeAlgorithms
QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())
params = {'INPUT':'Path to Shape',
'RADIUS':500,
'RADIUS_FIELD':'',
'PIXEL_SIZE':200,
'WEIGHT_FIELD':'SAL_ID',
'KERNEL':0,
'DECAY':0,
'OUTPUT_VALUE':0,
'OUTPUT':'output\\test6.tif'}
processing.run("qgis:heatmapkerneldensityestimation", params)