Task
Create a histogram to show elevation of 100 points, and also connect each point to its nearest 5 points.
Dataset
DEM of Pennsylvania.
Result
Geoprocessing workflow


Generated code:
-
# Purpose:
-
# – Generate 100 random points over the DEM extent (EPSG:32128), sample elevation from DEM, and store as a shapefile
-
# – Compute 5 nearest neighbors for each point using qgis:distancematrix
-
# – Build origin–neighbor connection lines (one line per pair) using native:pointstopath
-
# – Create an elevation histogram (HTML) for the 100 points using qgis:vectorlayerhistogram
-
# – All outputs are saved to the specified workspace, loaded into QGIS (except the HTML, whose path is printed)
-
#
-
# Notes:
-
# – Distances are computed in a projected CRS (EPSG:32128), so no reprojection is required
-
# – Implements stepwise processing with short waits to ensure files are ready before the next step
-
# – Uses selected tools: qgis:distancematrix, native:pointstopath, qgis:vectorlayerhistogram
-
# – DEM sampling is performed with rasterio
-
# – Field names are kept <= 10 characters
-
#
-
# Inputs:
-
# – DEM: C:/Users/17036/Downloads/PA_DEM.tif
-
# – Output directory:
-
# C:\Users\17036\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins\SpatialAnalysisAgent-master\Default_workspace
-
import os
-
import time
-
import random
-
import numpy as np
-
import pandas as pd
-
import geopandas as gpd
-
from shapely.geometry import Point
-
import rasterio
-
from qgis.core import QgsVectorLayer, QgsProject
-
import processing
-
def create_elevation_knn_visualization():
-
# Paths
-
dem_path = r“C:/Users/17036/Downloads/PA_DEM.tif”
-
out_dir = r“C:\Users\17036\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins\SpatialAnalysisAgent-master\Default_workspace”
-
# Helpers
-
def unique_path(directory, base_name, ext):
-
if not os.path.exists(base):
-
return base
-
i = 1
-
while True:
-
if not os.path.exists(cand):
-
return cand
-
i += 1
-
# 1) Create 100 random points within DEM bounds and sample elevation
-
with rasterio.open(dem_path) as src:
-
bounds = src.bounds # (left, bottom, right, top)
-
dem_crs_epsg = 32128 # From provided overview
-
rng = np.random.default_rng(42)
-
xs = rng.uniform(bounds.left, bounds.right, 100)
-
ys = rng.uniform(bounds.bottom, bounds.top, 100)
-
# Sample the DEM at point coordinates
-
elev_vals = [float(v[0]) if v.size > 0 else np.nan for v in elev_vals]
-
points = [Point(xy) for xy in coords]
-
df = pd.DataFrame({
-
“id”: np.arange(1, 101, dtype=int),
-
“elev”: elev_vals
-
})
-
gdf = gpd.GeoDataFrame(df, geometry=points, crs=f“EPSG:{dem_crs_epsg}”)
-
# Save points shapefile with elevation
-
points_path = unique_path(out_dir, “points_100”, “.shp”)
-
gdf.to_file(points_path, driver=“ESRI Shapefile”)
-
# Load points into QGIS
-
points_vl = QgsVectorLayer(points_path, “Points_100”, “ogr”)
-
QgsProject.instance().addMapLayer(points_vl)
-
# 2) Reproject for distance calculations – Not needed (already in projected CRS EPSG:32128)
-
# 3) k-Nearest Neighbors: compute 5 nearest neighbors using qgis:distancematrix
-
# Ensure we use a valid field name from layer
-
pts_fields = [f.name() for f in points_vl.fields()]
-
input_id_field = “id” if “id” in pts_fields else pts_fields[0]
-
dist_matrix_path = unique_path(out_dir, “knn5_matrix”, “.shp”)
-
dm_params = {
-
“INPUT”: points_vl,
-
“INPUT_FIELD”: input_id_field,
-
“TARGET”: points_vl,
-
“TARGET_FIELD”: input_id_field,
-
“MATRIX_TYPE”: 0, # Linear (N*k x 3)
-
“NEAREST_POINTS”: 5,
-
“OUTPUT”: dist_matrix_path
-
}
-
dm_result = processing.run(“qgis:distancematrix”, dm_params)
-
dm_layer = QgsVectorLayer(dm_result[“OUTPUT”], “KNN5_Matrix”, “ogr”)
-
QgsProject.instance().addMapLayer(dm_layer)
-
# 4) Create connection lines from origin–neighbor pairs using native:pointstopath
-
# We will build a 2-point-per-pair point layer with fields: pairid (int), ord (int), dist (float)
-
# GROUP_EXPRESSION = ‘pairid’, ORDER_EXPRESSION = ‘ord’
-
# Read back distance matrix rows
-
dm_fields = [f.name() for f in dm_layer.fields()]
-
# Find likely field names (case-insensitive fallback)
-
def find_field(candidates, fallback=None):
-
for cand in candidates:
-
for name in dm_fields:
-
if name.lower() == cand.lower():
-
return name
-
return fallback
-
in_field = find_field([“InputID”, “Input”, “INPUTID”, “INPUT”, “in_id”, “id1”], None)
-
tg_field = find_field([“TargetID”, “Target”, “TARGETID”, “TARGET”, “tg_id”, “id2”], None)
-
dist_field = find_field([“Distance”, “DISTANCE”, “dist”, “Dist”], None)
-
# Build id -> shapely Point mapping from gdf
-
id_to_geom = dict(zip(gdf[“id”].astype(int).tolist(), gdf.geometry.tolist()))
-
pair_rows = []
-
pair_counter = 1
-
for feat in dm_layer.getFeatures():
-
try:
-
oid = int(feat[in_field])
-
tid = int(feat[tg_field])
-
dval = float(feat[dist_field])
-
# Origin and target geometries
-
p_o = id_to_geom.get(oid, None)
-
p_t = id_to_geom.get(tid, None)
-
if (p_o is None) or (p_t is None):
-
continue
-
# Two rows for this pair
-
pair_rows.append({“pairid”: pair_counter, “ord”: 0, “dist”: dval, “o_id”: oid, “t_id”: tid, “geometry”: p_o})
-
pair_rows.append({“pairid”: pair_counter, “ord”: 1, “dist”: dval, “o_id”: oid, “t_id”: tid, “geometry”: p_t})
-
pair_counter += 1
-
except:
-
# Skip problematic rows silently
-
continue
-
pairs_gdf = gpd.GeoDataFrame(pair_rows, geometry=“geometry”, crs=gdf.crs)
-
pairs_points_path = unique_path(out_dir, “pairs_pts”, “.shp”)
-
pairs_gdf.to_file(pairs_points_path, driver=“ESRI Shapefile”)
-
pairs_vl = QgsVectorLayer(pairs_points_path, “Pairs_Points”, “ogr”)
-
QgsProject.instance().addMapLayer(pairs_vl)
-
# Run native:pointstopath to create a line per pairid
-
lines_path = unique_path(out_dir, “knn5_lines”, “.shp”)
-
p2l_params = {
-
“INPUT”: pairs_points_path,
-
“CLOSE_PATH”: False,
-
“ORDER_EXPRESSION”: “ord”,
-
“NATURAL_SORT”: False,
-
“GROUP_EXPRESSION”: “pairid”,
-
“OUTPUT”: lines_path,
-
“OUTPUT_TEXT_DIR”: None
-
}
-
lines_result = processing.run(“native:pointstopath”, p2l_params)
-
lines_vl = QgsVectorLayer(lines_result[“OUTPUT”], “KNN5_Lines”, “ogr”)
-
QgsProject.instance().addMapLayer(lines_vl)
-
# 5) Histogram: Generate a histogram of the elevation attribute for the 100 points
-
hist_out = unique_path(out_dir, “elev_histogram”, “.html”)
-
# Ensure the elevation field name is correct
-
pts_fields = [f.name() for f in points_vl.fields()]
-
elev_field = “elev” if “elev” in pts_fields else pts_fields[0]
-
hist_params = {
-
“INPUT”: points_vl,
-
“FIELD”: elev_field,
-
“BINS”: 12,
-
“OUTPUT”: hist_out
-
}
-
hist_result = processing.run(“qgis:vectorlayerhistogram”, hist_params)
-
# Print only the HTML file path per requirements
-
print(hist_result[“OUTPUT”])
-
# Execute
-
create_elevation_knn_visualization()