Select Page

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:

  1. # Purpose:
  2. # – Generate 100 random points over the DEM extent (EPSG:32128), sample elevation from DEM, and store as a shapefile
  3. # – Compute 5 nearest neighbors for each point using qgis:distancematrix
  4. # – Build origin–neighbor connection lines (one line per pair) using native:pointstopath
  5. # – Create an elevation histogram (HTML) for the 100 points using qgis:vectorlayerhistogram
  6. # – All outputs are saved to the specified workspace, loaded into QGIS (except the HTML, whose path is printed)
  7. #
  8. # Notes:
  9. # – Distances are computed in a projected CRS (EPSG:32128), so no reprojection is required
  10. # – Implements stepwise processing with short waits to ensure files are ready before the next step
  11. # – Uses selected tools: qgis:distancematrix, native:pointstopath, qgis:vectorlayerhistogram
  12. # – DEM sampling is performed with rasterio
  13. # – Field names are kept <= 10 characters
  14. #
  15. # Inputs:
  16. # – DEM: C:/Users/17036/Downloads/PA_DEM.tif
  17. # – Output directory:
  18. #   C:\Users\17036\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins\SpatialAnalysisAgent-master\Default_workspace
  19. import os
  20. import time
  21. import random
  22. import numpy as np
  23. import pandas as pd
  24. import geopandas as gpd
  25. from shapely.geometry import Point
  26. import rasterio
  27. from qgis.core import QgsVectorLayer, QgsProject
  28. import processing
  29. def create_elevation_knn_visualization():
  30.     # Paths
  31.    dem_path = r“C:/Users/17036/Downloads/PA_DEM.tif”
  32.     out_dir = r“C:\Users\17036\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins\SpatialAnalysisAgent-master\Default_workspace”
  33.     # Helpers
  34.    def unique_path(directory, base_name, ext):
  35.         base = os.path.join(directory, f“{base_name}{ext}”)
  36.         if not os.path.exists(base):
  37.             return base
  38.         i = 1
  39.         while True:
  40.             cand = os.path.join(directory, f“{base_name}_{i}{ext}”)
  41.             if not os.path.exists(cand):
  42.                 return cand
  43.             i += 1
  44.     # 1) Create 100 random points within DEM bounds and sample elevation
  45.    with rasterio.open(dem_path) as src:
  46.         bounds = src.bounds  # (left, bottom, right, top)
  47.        dem_crs_epsg = 32128  # From provided overview
  48.        rng = np.random.default_rng(42)
  49.         xs = rng.uniform(bounds.left, bounds.right, 100)
  50.         ys = rng.uniform(bounds.bottom, bounds.top, 100)
  51.         coords = list(zip(xs, ys))
  52.         # Sample the DEM at point coordinates
  53.        elev_vals = list(src.sample(coords))
  54.         elev_vals = [float(v[0]) if v.size > 0 else np.nan for v in elev_vals]
  55.     points = [Point(xy) for xy in coords]
  56.     df = pd.DataFrame({
  57.         “id”: np.arange(1, 101, dtype=int),
  58.         “elev”: elev_vals
  59.     })
  60.     gdf = gpd.GeoDataFrame(df, geometry=points, crs=f“EPSG:{dem_crs_epsg}”)
  61.     # Save points shapefile with elevation
  62.    points_path = unique_path(out_dir, “points_100”, “.shp”)
  63.     gdf.to_file(points_path, driver=“ESRI Shapefile”)
  64.     # Load points into QGIS
  65.    points_vl = QgsVectorLayer(points_path, “Points_100”, “ogr”)
  66.     QgsProject.instance().addMapLayer(points_vl)
  67.     time.sleep(1.5)
  68.     # 2) Reproject for distance calculations – Not needed (already in projected CRS EPSG:32128)
  69.     # 3) k-Nearest Neighbors: compute 5 nearest neighbors using qgis:distancematrix
  70.    # Ensure we use a valid field name from layer
  71.    pts_fields = [f.name() for f in points_vl.fields()]
  72.     input_id_field = “id” if “id” in pts_fields else pts_fields[0]
  73.     dist_matrix_path = unique_path(out_dir, “knn5_matrix”, “.shp”)
  74.     dm_params = {
  75.         “INPUT”: points_vl,
  76.         “INPUT_FIELD”: input_id_field,
  77.         “TARGET”: points_vl,
  78.         “TARGET_FIELD”: input_id_field,
  79.         “MATRIX_TYPE”: 0,  # Linear (N*k x 3)
  80.        “NEAREST_POINTS”: 5,
  81.         “OUTPUT”: dist_matrix_path
  82.     }
  83.     dm_result = processing.run(“qgis:distancematrix”, dm_params)
  84.     dm_layer = QgsVectorLayer(dm_result[“OUTPUT”], “KNN5_Matrix”, “ogr”)
  85.     QgsProject.instance().addMapLayer(dm_layer)
  86.     time.sleep(1.5)
  87.     # 4) Create connection lines from origin–neighbor pairs using native:pointstopath
  88.    #    We will build a 2-point-per-pair point layer with fields: pairid (int), ord (int), dist (float)
  89.    #    GROUP_EXPRESSION = ‘pairid’, ORDER_EXPRESSION = ‘ord’
  90.    # Read back distance matrix rows
  91.    dm_fields = [f.name() for f in dm_layer.fields()]
  92.     # Find likely field names (case-insensitive fallback)
  93.    def find_field(candidates, fallback=None):
  94.         for cand in candidates:
  95.             for name in dm_fields:
  96.                 if name.lower() == cand.lower():
  97.                     return name
  98.         return fallback
  99.     in_field = find_field([“InputID”, “Input”, “INPUTID”, “INPUT”, “in_id”, “id1”], None)
  100.     tg_field = find_field([“TargetID”, “Target”, “TARGETID”, “TARGET”, “tg_id”, “id2”], None)
  101.     dist_field = find_field([“Distance”, “DISTANCE”, “dist”, “Dist”], None)
  102.     # Build id -> shapely Point mapping from gdf
  103.    id_to_geom = dict(zip(gdf[“id”].astype(int).tolist(), gdf.geometry.tolist()))
  104.     pair_rows = []
  105.     pair_counter = 1
  106.     for feat in dm_layer.getFeatures():
  107.         try:
  108.             oid = int(feat[in_field])
  109.             tid = int(feat[tg_field])
  110.             dval = float(feat[dist_field])
  111.             # Origin and target geometries
  112.            p_o = id_to_geom.get(oid, None)
  113.             p_t = id_to_geom.get(tid, None)
  114.             if (p_o is None) or (p_t is None):
  115.                 continue
  116.             # Two rows for this pair
  117.            pair_rows.append({“pairid”: pair_counter, “ord”: 0, “dist”: dval, “o_id”: oid, “t_id”: tid, “geometry”: p_o})
  118.             pair_rows.append({“pairid”: pair_counter, “ord”: 1, “dist”: dval, “o_id”: oid, “t_id”: tid, “geometry”: p_t})
  119.             pair_counter += 1
  120.         except:
  121.             # Skip problematic rows silently
  122.            continue
  123.     pairs_gdf = gpd.GeoDataFrame(pair_rows, geometry=“geometry”, crs=gdf.crs)
  124.     pairs_points_path = unique_path(out_dir, “pairs_pts”, “.shp”)
  125.     pairs_gdf.to_file(pairs_points_path, driver=“ESRI Shapefile”)
  126.     pairs_vl = QgsVectorLayer(pairs_points_path, “Pairs_Points”, “ogr”)
  127.     QgsProject.instance().addMapLayer(pairs_vl)
  128.     time.sleep(1.5)
  129.     # Run native:pointstopath to create a line per pairid
  130.    lines_path = unique_path(out_dir, “knn5_lines”, “.shp”)
  131.     p2l_params = {
  132.         “INPUT”: pairs_points_path,
  133.         “CLOSE_PATH”: False,
  134.         “ORDER_EXPRESSION”: “ord”,
  135.         “NATURAL_SORT”: False,
  136.         “GROUP_EXPRESSION”: “pairid”,
  137.         “OUTPUT”: lines_path,
  138.         “OUTPUT_TEXT_DIR”: None
  139.     }
  140.     lines_result = processing.run(“native:pointstopath”, p2l_params)
  141.     lines_vl = QgsVectorLayer(lines_result[“OUTPUT”], “KNN5_Lines”, “ogr”)
  142.     QgsProject.instance().addMapLayer(lines_vl)
  143.     time.sleep(1.5)
  144.     # 5) Histogram: Generate a histogram of the elevation attribute for the 100 points
  145.    hist_out = unique_path(out_dir, “elev_histogram”, “.html”)
  146.     # Ensure the elevation field name is correct
  147.    pts_fields = [f.name() for f in points_vl.fields()]
  148.     elev_field = “elev” if “elev” in pts_fields else pts_fields[0]
  149.     hist_params = {
  150.         “INPUT”: points_vl,
  151.         “FIELD”: elev_field,
  152.         “BINS”: 12,
  153.         “OUTPUT”: hist_out
  154.     }
  155.     hist_result = processing.run(“qgis:vectorlayerhistogram”, hist_params)
  156.     # Print only the HTML file path per requirements
  157.    print(hist_result[“OUTPUT”])
  158. # Execute
  159. create_elevation_knn_visualization()