[{"data":1,"prerenderedAt":1746},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fgenerate-slope-aspect-hillshade-pyqgis":3},{"id":4,"title":5,"body":6,"description":1735,"extension":1736,"meta":1737,"navigation":274,"path":1742,"seo":1743,"stem":1744,"__hash__":1745},"docs\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fgenerate-slope-aspect-hillshade-pyqgis\u002Findex.md","Generate Slope, Aspect and Hillshade in PyQGIS",{"type":7,"value":8,"toc":1722},"minimark",[9,13,17,26,199,204,235,239,242,402,412,415,419,422,587,615,619,622,667,682,690,909,927,1031,1035,1038,1108,1120,1123,1296,1308,1312,1315,1322,1468,1478,1482,1488,1578,1582,1634,1638,1644,1648,1659,1673,1679,1685,1689,1718],[10,11,5],"h1",{"id":12},"generate-slope-aspect-and-hillshade-in-pyqgis",[14,15,16],"p",{},"Slope, aspect and hillshade come from the same three-by-three neighbourhood calculation, and all three are a single algorithm call. What separates a usable result from a convincing-looking wrong one is entirely in the setup: the CRS the DEM sits in, the vertical unit it stores, and — for hillshade only — a couple of cartographic conventions that readers' eyes depend on.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002F","Terrain & Interpolation Analysis in PyQGIS",". It covers producing each raster from Python, choosing between the native and GDAL implementations, handling flat ground in an aspect raster, and building a multi-directional hillshade that shows features a single light source hides.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,97,101,105,110,116,124,130,134,138,142,146,150,154,157,161,164,167,173,178,181,183,187,190,192,196],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 320","img","One elevation raster producing three derivative rasters: slope as a magnitude, aspect as a direction, and hillshade as a shaded picture, with the checks that must pass before any of them are computed","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Three derivatives, one set of prerequisites",[40,41,42],"desc",{},"Before deriving anything the DEM must be in a projected CRS with vertical units matching, and its nodata must be declared. From there slope produces degrees or percent, aspect produces a bearing that is undefined on flat ground, and hillshade produces an eight bit image intended for display rather than measurement.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","320","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"sahArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Two of these are measurements; one is a picture",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"24","62","200","122","10","#fffdf7","#b91c1c","stroke-width:2.5",[68,87,91],{"x":88,"y":89,"style":90,"fill":84,"textAnchor":74},"124","88","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","check first",[68,93,96],{"x":88,"y":94,"style":95,"fill":66,"textAnchor":74},"112","text-anchor:middle;font-size:10.5px;font-family:sans-serif","projected CRS?",[68,98,100],{"x":88,"y":99,"style":95,"fill":66,"textAnchor":74},"134","vertical unit = horizontal?",[68,102,104],{"x":88,"y":103,"style":95,"fill":66,"textAnchor":74},"156","nodata declared?",[68,106,109],{"x":88,"y":107,"style":108,"fill":84,"textAnchor":74},"176","text-anchor:middle;font-size:10px;font-family:sans-serif","skip these and everything below is wrong",[111,112],"line",{"x1":113,"y1":81,"x2":114,"y2":81,"stroke":66,"style":115},"228","266","stroke-width:2;marker-end:url(#sahArrow)",[44,117],{"x":118,"y":119,"width":120,"height":121,"rx":58,"fill":122,"stroke":123,"style":85},"276","52","216","72","#edf8e9","#15803d",[68,125,129],{"x":126,"y":127,"style":128,"fill":123,"textAnchor":74},"384","78","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","slope",[68,131,133],{"x":126,"y":132,"style":95,"fill":66,"textAnchor":74},"102","Float32 · degrees or percent",[44,135],{"x":118,"y":99,"width":120,"height":121,"rx":58,"fill":136,"stroke":137,"style":85},"#eff3ff","#2563eb",[68,139,141],{"x":126,"y":140,"style":128,"fill":137,"textAnchor":74},"160","aspect",[68,143,145],{"x":126,"y":144,"style":95,"fill":66,"textAnchor":74},"184","Float32 · 0–360, circular",[44,147],{"x":118,"y":120,"width":120,"height":121,"rx":58,"fill":148,"stroke":149,"style":85},"#fdf2e2","#b45309",[68,151,153],{"x":126,"y":152,"style":128,"fill":149,"textAnchor":74},"242","hillshade",[68,155,156],{"x":126,"y":114,"style":95,"fill":66,"textAnchor":74},"Byte · 0–255, for display",[111,158],{"x1":159,"y1":89,"x2":160,"y2":89,"stroke":66,"style":115},"496","530",[111,162],{"x1":159,"y1":163,"x2":160,"y2":163,"stroke":66,"style":115},"170",[111,165],{"x1":159,"y1":166,"x2":160,"y2":166,"stroke":66,"style":115},"252",[44,168],{"x":169,"y":119,"width":170,"height":121,"rx":58,"fill":83,"stroke":171,"style":172},"540","196","#0f766e","stroke-width:2",[68,174,177],{"x":175,"y":176,"style":95,"fill":66,"textAnchor":74},"638","82","thresholds, suitability,",[68,179,180],{"x":175,"y":132,"style":95,"fill":66,"textAnchor":74},"zonal summaries",[44,182],{"x":169,"y":99,"width":170,"height":121,"rx":58,"fill":83,"stroke":171,"style":172},[68,184,186],{"x":175,"y":185,"style":95,"fill":66,"textAnchor":74},"164","solar exposure, habitat,",[68,188,189],{"x":175,"y":144,"style":95,"fill":66,"textAnchor":74},"reclassified to compass points",[44,191],{"x":169,"y":120,"width":170,"height":121,"rx":58,"fill":83,"stroke":171,"style":172},[68,193,195],{"x":175,"y":194,"style":95,"fill":66,"textAnchor":74},"246","underneath a thematic fill",[68,197,198],{"x":175,"y":114,"style":95,"fill":66,"textAnchor":74},"set to multiply",[200,201,203],"h2",{"id":202},"prerequisites","Prerequisites",[205,206,207,215,227],"ul",{},[208,209,210,214],"li",{},[211,212,213],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer, with the GDAL provider available (it is, in every standard install).",[208,216,217,218,221,222,226],{},"A DEM in a ",[211,219,220],{},"projected"," CRS whose horizontal units match its vertical units. Reproject first if not — see ",[21,223,225],{"href":224},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fbatch-reprojecting-raster-datasets\u002F","batch reprojecting raster datasets",".",[208,228,229,230,234],{},"Nodata declared on the input. An undeclared fill value of ",[231,232,233],"code",{},"-9999"," becomes a cliff face several kilometres high at every gap.",[200,236,238],{"id":237},"validate-before-you-derive","Validate before you derive",[14,240,241],{},"Two lines of checking save an afternoon of confused output.",[243,244,249],"pre",{"className":245,"code":246,"language":247,"meta":248,"style":248},"language-python shiki shiki-themes github-dark","from qgis.core import QgsRasterLayer\n\ndem = QgsRasterLayer(\"\u002Fdata\u002Fdem_27700.tif\", \"dem\")\nif not dem.isValid():\n    raise RuntimeError(\"DEM failed to load — check the path and driver\")\nif dem.crs().isGeographic():\n    raise RuntimeError(f\"{dem.crs().authid()} is geographic; reproject before deriving\")\nif not dem.dataProvider().sourceHasNoDataValue(1):\n    dem.dataProvider().setNoDataValue(1, -9999)\n","python","",[231,250,251,269,276,301,313,331,339,368,384],{"__ignoreMap":248},[252,253,255,259,263,266],"span",{"class":111,"line":254},1,[252,256,258],{"class":257},"snl16","from",[252,260,262],{"class":261},"s95oV"," qgis.core ",[252,264,265],{"class":257},"import",[252,267,268],{"class":261}," QgsRasterLayer\n",[252,270,272],{"class":111,"line":271},2,[252,273,275],{"emptyLinePlaceholder":274},true,"\n",[252,277,279,282,285,288,292,295,298],{"class":111,"line":278},3,[252,280,281],{"class":261},"dem ",[252,283,284],{"class":257},"=",[252,286,287],{"class":261}," QgsRasterLayer(",[252,289,291],{"class":290},"sU2Wk","\"\u002Fdata\u002Fdem_27700.tif\"",[252,293,294],{"class":261},", ",[252,296,297],{"class":290},"\"dem\"",[252,299,300],{"class":261},")\n",[252,302,304,307,310],{"class":111,"line":303},4,[252,305,306],{"class":257},"if",[252,308,309],{"class":257}," not",[252,311,312],{"class":261}," dem.isValid():\n",[252,314,316,319,323,326,329],{"class":111,"line":315},5,[252,317,318],{"class":257},"    raise",[252,320,322],{"class":321},"sDLfK"," RuntimeError",[252,324,325],{"class":261},"(",[252,327,328],{"class":290},"\"DEM failed to load — check the path and driver\"",[252,330,300],{"class":261},[252,332,334,336],{"class":111,"line":333},6,[252,335,306],{"class":257},[252,337,338],{"class":261}," dem.crs().isGeographic():\n",[252,340,342,344,346,348,351,354,357,360,363,366],{"class":111,"line":341},7,[252,343,318],{"class":257},[252,345,322],{"class":321},[252,347,325],{"class":261},[252,349,350],{"class":257},"f",[252,352,353],{"class":290},"\"",[252,355,356],{"class":321},"{",[252,358,359],{"class":261},"dem.crs().authid()",[252,361,362],{"class":321},"}",[252,364,365],{"class":290}," is geographic; reproject before deriving\"",[252,367,300],{"class":261},[252,369,371,373,375,378,381],{"class":111,"line":370},8,[252,372,306],{"class":257},[252,374,309],{"class":257},[252,376,377],{"class":261}," dem.dataProvider().sourceHasNoDataValue(",[252,379,380],{"class":321},"1",[252,382,383],{"class":261},"):\n",[252,385,387,390,392,394,397,400],{"class":111,"line":386},9,[252,388,389],{"class":261},"    dem.dataProvider().setNoDataValue(",[252,391,380],{"class":321},[252,393,294],{"class":261},[252,395,396],{"class":257},"-",[252,398,399],{"class":321},"9999",[252,401,300],{"class":261},[14,403,404,407,408,411],{},[211,405,406],{},"Breakdown:"," ",[231,409,410],{},"isValid()"," catches the case where the path is wrong or the driver is missing, which otherwise surfaces much later as an empty output rather than an error. The geographic check is the one that matters most: in EPSG:4326 the cell size is in degrees and the heights are in metres, so slope comes out around five orders of magnitude too small — a hillside reported as 0.0003 degrees. Declaring nodata is done on the layer rather than the file, which is enough for the Processing algorithms that receive the layer object.",[14,413,414],{},"Passing the layer object rather than a path is generally the better habit here, because the nodata declaration and any other layer-level settings travel with it.",[200,416,418],{"id":417},"slope-in-degrees-or-percent","Slope, in degrees or percent",[14,420,421],{},"The native algorithm returns degrees; GDAL's returns degrees or percent.",[243,423,425],{"className":245,"code":424,"language":247,"meta":248,"style":248},"import processing\n\nprocessing.run(\"native:slope\", {\n    \"INPUT\": dem,\n    \"Z_FACTOR\": 1.0,\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fslope_deg.tif\",\n})\n\nprocessing.run(\"gdal:slope\", {\n    \"INPUT\": dem,\n    \"BAND\": 1,\n    \"SCALE\": 1.0,\n    \"AS_PERCENT\": True,\n    \"COMPUTE_EDGES\": False,\n    \"ZEVENBERGEN\": False,\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fslope_pct.tif\",\n})\n",[231,426,427,434,438,449,457,471,483,488,492,501,508,520,532,545,558,570,582],{"__ignoreMap":248},[252,428,429,431],{"class":111,"line":254},[252,430,265],{"class":257},[252,432,433],{"class":261}," processing\n",[252,435,436],{"class":111,"line":271},[252,437,275],{"emptyLinePlaceholder":274},[252,439,440,443,446],{"class":111,"line":278},[252,441,442],{"class":261},"processing.run(",[252,444,445],{"class":290},"\"native:slope\"",[252,447,448],{"class":261},", {\n",[252,450,451,454],{"class":111,"line":303},[252,452,453],{"class":290},"    \"INPUT\"",[252,455,456],{"class":261},": dem,\n",[252,458,459,462,465,468],{"class":111,"line":315},[252,460,461],{"class":290},"    \"Z_FACTOR\"",[252,463,464],{"class":261},": ",[252,466,467],{"class":321},"1.0",[252,469,470],{"class":261},",\n",[252,472,473,476,478,481],{"class":111,"line":333},[252,474,475],{"class":290},"    \"OUTPUT\"",[252,477,464],{"class":261},[252,479,480],{"class":290},"\"\u002Fdata\u002Foutput\u002Fslope_deg.tif\"",[252,482,470],{"class":261},[252,484,485],{"class":111,"line":341},[252,486,487],{"class":261},"})\n",[252,489,490],{"class":111,"line":370},[252,491,275],{"emptyLinePlaceholder":274},[252,493,494,496,499],{"class":111,"line":386},[252,495,442],{"class":261},[252,497,498],{"class":290},"\"gdal:slope\"",[252,500,448],{"class":261},[252,502,504,506],{"class":111,"line":503},10,[252,505,453],{"class":290},[252,507,456],{"class":261},[252,509,511,514,516,518],{"class":111,"line":510},11,[252,512,513],{"class":290},"    \"BAND\"",[252,515,464],{"class":261},[252,517,380],{"class":321},[252,519,470],{"class":261},[252,521,523,526,528,530],{"class":111,"line":522},12,[252,524,525],{"class":290},"    \"SCALE\"",[252,527,464],{"class":261},[252,529,467],{"class":321},[252,531,470],{"class":261},[252,533,535,538,540,543],{"class":111,"line":534},13,[252,536,537],{"class":290},"    \"AS_PERCENT\"",[252,539,464],{"class":261},[252,541,542],{"class":321},"True",[252,544,470],{"class":261},[252,546,548,551,553,556],{"class":111,"line":547},14,[252,549,550],{"class":290},"    \"COMPUTE_EDGES\"",[252,552,464],{"class":261},[252,554,555],{"class":321},"False",[252,557,470],{"class":261},[252,559,561,564,566,568],{"class":111,"line":560},15,[252,562,563],{"class":290},"    \"ZEVENBERGEN\"",[252,565,464],{"class":261},[252,567,555],{"class":321},[252,569,470],{"class":261},[252,571,573,575,577,580],{"class":111,"line":572},16,[252,574,475],{"class":290},[252,576,464],{"class":261},[252,578,579],{"class":290},"\"\u002Fdata\u002Foutput\u002Fslope_pct.tif\"",[252,581,470],{"class":261},[252,583,585],{"class":111,"line":584},17,[252,586,487],{"class":261},[14,588,589,591,592,595,596,599,600,602,603,606,607,610,611,614],{},[211,590,406],{}," GDAL calls the vertical scaling ",[231,593,594],{},"SCALE"," where the native algorithm calls it ",[231,597,598],{},"Z_FACTOR","; both mean \"multiply the heights by this before differentiating\", and both should be ",[231,601,467],{}," unless the DEM's vertical unit differs from the CRS's horizontal one — ",[231,604,605],{},"0.3048"," for a DEM in feet on a metric grid. ",[231,608,609],{},"ZEVENBERGEN"," switches from the default Horn algorithm to Zevenbergen-Thorne, which weights the diagonal neighbours differently and gives slightly sharper results on smooth surfaces; Horn is more robust on noisy DEMs and is the right default. ",[231,612,613],{},"AS_PERCENT"," matters because engineering criteria are usually written as percentages: a 1-in-4 gradient is 25 percent and 14 degrees, and confusing the two is a real and expensive mistake.",[200,616,618],{"id":617},"aspect-and-the-flat-ground-problem","Aspect, and the flat-ground problem",[14,620,621],{},"Aspect is the compass bearing of the downhill direction, and on perfectly flat ground there is no such direction.",[243,623,625],{"className":245,"code":624,"language":247,"meta":248,"style":248},"processing.run(\"native:aspect\", {\n    \"INPUT\": dem,\n    \"Z_FACTOR\": 1.0,\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Faspect.tif\",\n})\n",[231,626,627,636,642,652,663],{"__ignoreMap":248},[252,628,629,631,634],{"class":111,"line":254},[252,630,442],{"class":261},[252,632,633],{"class":290},"\"native:aspect\"",[252,635,448],{"class":261},[252,637,638,640],{"class":111,"line":271},[252,639,453],{"class":290},[252,641,456],{"class":261},[252,643,644,646,648,650],{"class":111,"line":278},[252,645,461],{"class":290},[252,647,464],{"class":261},[252,649,467],{"class":321},[252,651,470],{"class":261},[252,653,654,656,658,661],{"class":111,"line":303},[252,655,475],{"class":290},[252,657,464],{"class":261},[252,659,660],{"class":290},"\"\u002Fdata\u002Foutput\u002Faspect.tif\"",[252,662,470],{"class":261},[252,664,665],{"class":111,"line":315},[252,666,487],{"class":261},[14,668,669,671,672,675,676,678,679,681],{},[211,670,406],{}," Flat cells receive ",[231,673,674],{},"-1"," in GDAL's convention and ",[231,677,233],{}," in some others, so any reclassification must handle the flag explicitly rather than letting it fall into the \"north\" bin — where it will, since a naive classification of 0–360 into eight sectors puts everything below 22.5 degrees into north and a ",[231,680,674],{}," sorts below that.",[14,683,684,685,689],{},"The second trap is that aspect is ",[686,687,688],"em",{},"circular",". A mean of 350 and 10 degrees is 0, not 180, so averaging an aspect raster with zonal statistics produces meaningless numbers. When a summary is needed, reclassify into compass sectors first and take the majority:",[243,691,693],{"className":245,"code":692,"language":247,"meta":248,"style":248},"processing.run(\"native:reclassifybytable\", {\n    \"INPUT_RASTER\": \"\u002Fdata\u002Foutput\u002Faspect.tif\",\n    \"RASTER_BAND\": 1,\n    \"TABLE\": [\n        0, 22.5, 1,  22.5, 67.5, 2,  67.5, 112.5, 3,  112.5, 157.5, 4,\n        157.5, 202.5, 5,  202.5, 247.5, 6,  247.5, 292.5, 7,\n        292.5, 337.5, 8,  337.5, 360, 1,\n    ],\n    \"NO_DATA\": -9999,\n    \"RANGE_BOUNDARIES\": 0,\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Faspect_class.tif\",\n})\n",[231,694,695,704,715,726,734,793,836,865,870,883,894,905],{"__ignoreMap":248},[252,696,697,699,702],{"class":111,"line":254},[252,698,442],{"class":261},[252,700,701],{"class":290},"\"native:reclassifybytable\"",[252,703,448],{"class":261},[252,705,706,709,711,713],{"class":111,"line":271},[252,707,708],{"class":290},"    \"INPUT_RASTER\"",[252,710,464],{"class":261},[252,712,660],{"class":290},[252,714,470],{"class":261},[252,716,717,720,722,724],{"class":111,"line":278},[252,718,719],{"class":290},"    \"RASTER_BAND\"",[252,721,464],{"class":261},[252,723,380],{"class":321},[252,725,470],{"class":261},[252,727,728,731],{"class":111,"line":303},[252,729,730],{"class":290},"    \"TABLE\"",[252,732,733],{"class":261},": [\n",[252,735,736,739,741,744,746,748,751,753,755,758,760,763,765,767,769,772,774,777,779,781,783,786,788,791],{"class":111,"line":315},[252,737,738],{"class":321},"        0",[252,740,294],{"class":261},[252,742,743],{"class":321},"22.5",[252,745,294],{"class":261},[252,747,380],{"class":321},[252,749,750],{"class":261},",  ",[252,752,743],{"class":321},[252,754,294],{"class":261},[252,756,757],{"class":321},"67.5",[252,759,294],{"class":261},[252,761,762],{"class":321},"2",[252,764,750],{"class":261},[252,766,757],{"class":321},[252,768,294],{"class":261},[252,770,771],{"class":321},"112.5",[252,773,294],{"class":261},[252,775,776],{"class":321},"3",[252,778,750],{"class":261},[252,780,771],{"class":321},[252,782,294],{"class":261},[252,784,785],{"class":321},"157.5",[252,787,294],{"class":261},[252,789,790],{"class":321},"4",[252,792,470],{"class":261},[252,794,795,798,800,803,805,807,809,811,813,816,818,821,823,825,827,830,832,834],{"class":111,"line":333},[252,796,797],{"class":321},"        157.5",[252,799,294],{"class":261},[252,801,802],{"class":321},"202.5",[252,804,294],{"class":261},[252,806,59],{"class":321},[252,808,750],{"class":261},[252,810,802],{"class":321},[252,812,294],{"class":261},[252,814,815],{"class":321},"247.5",[252,817,294],{"class":261},[252,819,820],{"class":321},"6",[252,822,750],{"class":261},[252,824,815],{"class":321},[252,826,294],{"class":261},[252,828,829],{"class":321},"292.5",[252,831,294],{"class":261},[252,833,60],{"class":321},[252,835,470],{"class":261},[252,837,838,841,843,846,848,850,852,854,856,859,861,863],{"class":111,"line":341},[252,839,840],{"class":321},"        292.5",[252,842,294],{"class":261},[252,844,845],{"class":321},"337.5",[252,847,294],{"class":261},[252,849,58],{"class":321},[252,851,750],{"class":261},[252,853,845],{"class":321},[252,855,294],{"class":261},[252,857,858],{"class":321},"360",[252,860,294],{"class":261},[252,862,380],{"class":321},[252,864,470],{"class":261},[252,866,867],{"class":111,"line":370},[252,868,869],{"class":261},"    ],\n",[252,871,872,875,877,879,881],{"class":111,"line":386},[252,873,874],{"class":290},"    \"NO_DATA\"",[252,876,464],{"class":261},[252,878,396],{"class":257},[252,880,399],{"class":321},[252,882,470],{"class":261},[252,884,885,888,890,892],{"class":111,"line":503},[252,886,887],{"class":290},"    \"RANGE_BOUNDARIES\"",[252,889,464],{"class":261},[252,891,46],{"class":321},[252,893,470],{"class":261},[252,895,896,898,900,903],{"class":111,"line":510},[252,897,475],{"class":290},[252,899,464],{"class":261},[252,901,902],{"class":290},"\"\u002Fdata\u002Foutput\u002Faspect_class.tif\"",[252,904,470],{"class":261},[252,906,907],{"class":111,"line":522},[252,908,487],{"class":261},[14,910,911,913,914,917,918,921,922,926],{},[211,912,406],{}," The table is a flat list of ",[231,915,916],{},"min, max, value"," triples, and north appears twice because it wraps around zero — that wrap is the whole reason this reclassification exists. ",[231,919,920],{},"RANGE_BOUNDARIES: 0"," makes each range include its minimum and exclude its maximum, which avoids a cell exactly on a boundary landing in two classes. With classes in hand, the majority statistic in ",[21,923,925],{"href":924},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fzonal-statistics-pyqgis\u002F","zonal statistics"," gives a defensible \"this catchment mostly faces south-west\".",[14,928,929],{},[29,930,933,936,939,942,945,951,970,974,979,985,990,993,996,1000,1005,1010,1014,1018,1020,1024,1027],{"viewBox":931,"role":32,"ariaLabel":932,"xmlns":34},"0 0 760 300","An aspect compass rose showing eight sectors, with north spanning the wrap point at zero and three hundred and sixty degrees, and flat cells shown as a separate category outside the rose",[36,934,935],{},"Aspect wraps, and flat ground is not a direction",[40,937,938],{},"The eight compass sectors each span forty five degrees. North is split across the wrap at zero, so it must appear twice in any reclassification table. Cells on flat ground carry a sentinel value rather than a bearing and belong in their own class, not in the north sector.",[44,940],{"x":46,"y":46,"width":47,"height":941,"fill":49},"300",[68,943,944],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"North is one class written as two ranges",[946,947],"circle",{"cx":948,"cy":185,"r":949,"fill":83,"stroke":950,"style":172},"240","96","#59645f",[952,953,955,959,963,968],"g",{"stroke":950,"style":954},"stroke-width:1.2",[111,956],{"x1":948,"y1":957,"x2":948,"y2":958},"68","260",[111,960],{"x1":961,"y1":185,"x2":962,"y2":185},"144","336",[111,964],{"x1":965,"y1":949,"x2":966,"y2":967},"172","308","232",[111,969],{"x1":966,"y1":949,"x2":965,"y2":967},[63,971],{"d":972,"fill":973,"stroke":137,"style":172},"M240 164 L204 74 A96 96 0 0 1 276 74 Z","#dbeafe",[68,975,978],{"x":948,"y":976,"style":977,"fill":137,"textAnchor":74},"58","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","N",[68,980,984],{"x":981,"y":982,"style":983,"fill":66},"342","168","font-size:11px;font-family:sans-serif","E",[68,986,989],{"x":948,"y":987,"style":988,"fill":66,"textAnchor":74},"278","text-anchor:middle;font-size:11px;font-family:sans-serif","S",[68,991,992],{"x":81,"y":982,"style":983,"fill":66},"W",[68,994,995],{"x":948,"y":81,"style":108,"fill":73,"textAnchor":74},"337.5–360",[68,997,999],{"x":948,"y":998,"style":108,"fill":73,"textAnchor":74},"140","plus 0–22.5",[44,1001],{"x":1002,"y":1003,"width":941,"height":1004,"rx":82,"fill":136,"stroke":137,"style":85},"420","76","80",[68,1006,1009],{"x":1007,"y":1008,"style":128,"fill":137,"textAnchor":74},"570","104","eight sectors, nine ranges",[68,1011,1013],{"x":1007,"y":1012,"style":95,"fill":66,"textAnchor":74},"128","the extra range is north, past the wrap",[68,1015,1017],{"x":1007,"y":1016,"style":95,"fill":66,"textAnchor":74},"146","both map to class 1",[44,1019],{"x":1002,"y":107,"width":941,"height":89,"rx":82,"fill":148,"stroke":149,"style":85},[68,1021,1023],{"x":1007,"y":1022,"style":128,"fill":149,"textAnchor":74},"204","flat cells",[68,1025,1026],{"x":1007,"y":113,"style":95,"fill":66,"textAnchor":74},"value −1, not a bearing",[68,1028,1030],{"x":1007,"y":1029,"style":95,"fill":66,"textAnchor":74},"250","exclude, or they all become north",[200,1032,1034],{"id":1033},"hillshade-and-why-azimuth-315-is-not-negotiable","Hillshade, and why azimuth 315 is not negotiable",[14,1036,1037],{},"Hillshade is a rendering of a light source over the surface. Two of its parameters are cartographic conventions rather than choices.",[243,1039,1041],{"className":245,"code":1040,"language":247,"meta":248,"style":248},"processing.run(\"native:hillshade\", {\n    \"INPUT\": dem,\n    \"Z_FACTOR\": 2.0,\n    \"AZIMUTH\": 315,\n    \"V_ANGLE\": 45,\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fhillshade.tif\",\n})\n",[231,1042,1043,1052,1058,1069,1081,1093,1104],{"__ignoreMap":248},[252,1044,1045,1047,1050],{"class":111,"line":254},[252,1046,442],{"class":261},[252,1048,1049],{"class":290},"\"native:hillshade\"",[252,1051,448],{"class":261},[252,1053,1054,1056],{"class":111,"line":271},[252,1055,453],{"class":290},[252,1057,456],{"class":261},[252,1059,1060,1062,1064,1067],{"class":111,"line":278},[252,1061,461],{"class":290},[252,1063,464],{"class":261},[252,1065,1066],{"class":321},"2.0",[252,1068,470],{"class":261},[252,1070,1071,1074,1076,1079],{"class":111,"line":303},[252,1072,1073],{"class":290},"    \"AZIMUTH\"",[252,1075,464],{"class":261},[252,1077,1078],{"class":321},"315",[252,1080,470],{"class":261},[252,1082,1083,1086,1088,1091],{"class":111,"line":315},[252,1084,1085],{"class":290},"    \"V_ANGLE\"",[252,1087,464],{"class":261},[252,1089,1090],{"class":321},"45",[252,1092,470],{"class":261},[252,1094,1095,1097,1099,1102],{"class":111,"line":333},[252,1096,475],{"class":290},[252,1098,464],{"class":261},[252,1100,1101],{"class":290},"\"\u002Fdata\u002Foutput\u002Fhillshade.tif\"",[252,1103,470],{"class":261},[252,1105,1106],{"class":111,"line":341},[252,1107,487],{"class":261},[14,1109,1110,1112,1113,1116,1117,1119],{},[211,1111,406],{}," An azimuth of 315 degrees puts the light in the north-west. Human perception assumes light comes from above and to the left; lighting from the south-east makes most readers see ridges as valleys and valleys as ridges, an illusion strong enough that it does not go away once you know about it. ",[231,1114,1115],{},"V_ANGLE"," of 45 degrees is a balanced default — lower angles lengthen shadows and reveal subtle features at the cost of saturating steep ground to black. A ",[231,1118,598],{}," above 1 exaggerates relief, which is legitimate here precisely because a hillshade is not a measurement.",[14,1121,1122],{},"Where the terrain has strong linear structure — glacial valleys, dune fields, fault scarps — a single light direction hides everything parallel to it. Combining several directions fixes that.",[243,1124,1126],{"className":245,"code":1125,"language":247,"meta":248,"style":248},"import processing\n\nshades = []\nfor azimuth in (315, 15, 75, 135):\n    shades.append(processing.run(\"native:hillshade\", {\n        \"INPUT\": dem, \"Z_FACTOR\": 2.0, \"AZIMUTH\": azimuth, \"V_ANGLE\": 45,\n        \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n    })[\"OUTPUT\"])\n\nprocessing.run(\"native:cellstatistics\", {\n    \"INPUT\": shades,\n    \"STATISTIC\": 2,                    # mean\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fhillshade_multi.tif\",\n})\n",[231,1127,1128,1134,1138,1148,1181,1190,1222,1234,1245,1249,1258,1265,1281,1292],{"__ignoreMap":248},[252,1129,1130,1132],{"class":111,"line":254},[252,1131,265],{"class":257},[252,1133,433],{"class":261},[252,1135,1136],{"class":111,"line":271},[252,1137,275],{"emptyLinePlaceholder":274},[252,1139,1140,1143,1145],{"class":111,"line":278},[252,1141,1142],{"class":261},"shades ",[252,1144,284],{"class":257},[252,1146,1147],{"class":261}," []\n",[252,1149,1150,1153,1156,1159,1162,1164,1166,1169,1171,1174,1176,1179],{"class":111,"line":303},[252,1151,1152],{"class":257},"for",[252,1154,1155],{"class":261}," azimuth ",[252,1157,1158],{"class":257},"in",[252,1160,1161],{"class":261}," (",[252,1163,1078],{"class":321},[252,1165,294],{"class":261},[252,1167,1168],{"class":321},"15",[252,1170,294],{"class":261},[252,1172,1173],{"class":321},"75",[252,1175,294],{"class":261},[252,1177,1178],{"class":321},"135",[252,1180,383],{"class":261},[252,1182,1183,1186,1188],{"class":111,"line":315},[252,1184,1185],{"class":261},"    shades.append(processing.run(",[252,1187,1049],{"class":290},[252,1189,448],{"class":261},[252,1191,1192,1195,1198,1201,1203,1205,1207,1210,1213,1216,1218,1220],{"class":111,"line":333},[252,1193,1194],{"class":290},"        \"INPUT\"",[252,1196,1197],{"class":261},": dem, ",[252,1199,1200],{"class":290},"\"Z_FACTOR\"",[252,1202,464],{"class":261},[252,1204,1066],{"class":321},[252,1206,294],{"class":261},[252,1208,1209],{"class":290},"\"AZIMUTH\"",[252,1211,1212],{"class":261},": azimuth, ",[252,1214,1215],{"class":290},"\"V_ANGLE\"",[252,1217,464],{"class":261},[252,1219,1090],{"class":321},[252,1221,470],{"class":261},[252,1223,1224,1227,1229,1232],{"class":111,"line":341},[252,1225,1226],{"class":290},"        \"OUTPUT\"",[252,1228,464],{"class":261},[252,1230,1231],{"class":290},"\"TEMPORARY_OUTPUT\"",[252,1233,470],{"class":261},[252,1235,1236,1239,1242],{"class":111,"line":370},[252,1237,1238],{"class":261},"    })[",[252,1240,1241],{"class":290},"\"OUTPUT\"",[252,1243,1244],{"class":261},"])\n",[252,1246,1247],{"class":111,"line":386},[252,1248,275],{"emptyLinePlaceholder":274},[252,1250,1251,1253,1256],{"class":111,"line":503},[252,1252,442],{"class":261},[252,1254,1255],{"class":290},"\"native:cellstatistics\"",[252,1257,448],{"class":261},[252,1259,1260,1262],{"class":111,"line":510},[252,1261,453],{"class":290},[252,1263,1264],{"class":261},": shades,\n",[252,1266,1267,1270,1272,1274,1277],{"class":111,"line":522},[252,1268,1269],{"class":290},"    \"STATISTIC\"",[252,1271,464],{"class":261},[252,1273,762],{"class":321},[252,1275,1276],{"class":261},",                    ",[252,1278,1280],{"class":1279},"sjoCn","# mean\n",[252,1282,1283,1285,1287,1290],{"class":111,"line":534},[252,1284,475],{"class":290},[252,1286,464],{"class":261},[252,1288,1289],{"class":290},"\"\u002Fdata\u002Foutput\u002Fhillshade_multi.tif\"",[252,1291,470],{"class":261},[252,1293,1294],{"class":111,"line":547},[252,1295,487],{"class":261},[14,1297,1298,407,1300,1303,1304,1307],{},[211,1299,406],{},[231,1301,1302],{},"native:cellstatistics"," combines aligned rasters cell by cell, and the mean of four hillshades is the standard multi-directional recipe — softer than any single one, with no direction of structure invisible. The inputs are temporary outputs, so nothing but the final file is written to disk. Because all four derive from the same DEM they are automatically aligned, which is the condition ",[231,1305,1306],{},"cellstatistics"," requires and does not check.",[200,1309,1311],{"id":1310},"styling-the-outputs-so-they-read-correctly","Styling the outputs so they read correctly",[14,1313,1314],{},"Each of the three derivatives wants a different treatment, and the defaults QGIS applies are wrong for two of them.",[14,1316,1317,1318,1321],{},"Slope suits a sequential ramp with class breaks at the thresholds that matter to the work rather than at equal intervals — 0–2, 2–5, 5–10, 10–20, above 20 degrees says something about buildability, while five equal classes says only that the algorithm ran. Aspect needs a ",[686,1319,1320],{},"cyclic"," ramp, because a sequential one puts a hard colour break at north where the values wrap and invents a boundary that is not in the terrain. Hillshade wants greyscale with no ramp at all, sitting underneath everything else.",[243,1323,1325],{"className":245,"code":1324,"language":247,"meta":248,"style":248},"from qgis.core import (\n    QgsRasterLayer, QgsProject, QgsSingleBandGrayRenderer, QgsContrastEnhancement,\n)\nfrom qgis.PyQt.QtGui import QPainter\n\nshade = QgsRasterLayer(\"\u002Fdata\u002Foutput\u002Fhillshade_multi.tif\", \"hillshade\")\nQgsProject.instance().addMapLayer(shade)\n\nrenderer = QgsSingleBandGrayRenderer(shade.dataProvider(), 1)\nenhancement = QgsContrastEnhancement(shade.dataProvider().dataType(1))\nenhancement.setContrastEnhancementAlgorithm(\n    QgsContrastEnhancement.StretchToMinimumMaximum\n)\nenhancement.setMinimumValue(0)\nenhancement.setMaximumValue(255)\nrenderer.setContrastEnhancement(enhancement)\nshade.setRenderer(renderer)\nshade.triggerRepaint()\n",[231,1326,1327,1338,1343,1347,1359,1363,1381,1386,1390,1404,1419,1424,1429,1433,1442,1452,1457,1462],{"__ignoreMap":248},[252,1328,1329,1331,1333,1335],{"class":111,"line":254},[252,1330,258],{"class":257},[252,1332,262],{"class":261},[252,1334,265],{"class":257},[252,1336,1337],{"class":261}," (\n",[252,1339,1340],{"class":111,"line":271},[252,1341,1342],{"class":261},"    QgsRasterLayer, QgsProject, QgsSingleBandGrayRenderer, QgsContrastEnhancement,\n",[252,1344,1345],{"class":111,"line":278},[252,1346,300],{"class":261},[252,1348,1349,1351,1354,1356],{"class":111,"line":303},[252,1350,258],{"class":257},[252,1352,1353],{"class":261}," qgis.PyQt.QtGui ",[252,1355,265],{"class":257},[252,1357,1358],{"class":261}," QPainter\n",[252,1360,1361],{"class":111,"line":315},[252,1362,275],{"emptyLinePlaceholder":274},[252,1364,1365,1368,1370,1372,1374,1376,1379],{"class":111,"line":333},[252,1366,1367],{"class":261},"shade ",[252,1369,284],{"class":257},[252,1371,287],{"class":261},[252,1373,1289],{"class":290},[252,1375,294],{"class":261},[252,1377,1378],{"class":290},"\"hillshade\"",[252,1380,300],{"class":261},[252,1382,1383],{"class":111,"line":341},[252,1384,1385],{"class":261},"QgsProject.instance().addMapLayer(shade)\n",[252,1387,1388],{"class":111,"line":370},[252,1389,275],{"emptyLinePlaceholder":274},[252,1391,1392,1395,1397,1400,1402],{"class":111,"line":386},[252,1393,1394],{"class":261},"renderer ",[252,1396,284],{"class":257},[252,1398,1399],{"class":261}," QgsSingleBandGrayRenderer(shade.dataProvider(), ",[252,1401,380],{"class":321},[252,1403,300],{"class":261},[252,1405,1406,1409,1411,1414,1416],{"class":111,"line":503},[252,1407,1408],{"class":261},"enhancement ",[252,1410,284],{"class":257},[252,1412,1413],{"class":261}," QgsContrastEnhancement(shade.dataProvider().dataType(",[252,1415,380],{"class":321},[252,1417,1418],{"class":261},"))\n",[252,1420,1421],{"class":111,"line":510},[252,1422,1423],{"class":261},"enhancement.setContrastEnhancementAlgorithm(\n",[252,1425,1426],{"class":111,"line":522},[252,1427,1428],{"class":261},"    QgsContrastEnhancement.StretchToMinimumMaximum\n",[252,1430,1431],{"class":111,"line":534},[252,1432,300],{"class":261},[252,1434,1435,1438,1440],{"class":111,"line":547},[252,1436,1437],{"class":261},"enhancement.setMinimumValue(",[252,1439,46],{"class":321},[252,1441,300],{"class":261},[252,1443,1444,1447,1450],{"class":111,"line":560},[252,1445,1446],{"class":261},"enhancement.setMaximumValue(",[252,1448,1449],{"class":321},"255",[252,1451,300],{"class":261},[252,1453,1454],{"class":111,"line":572},[252,1455,1456],{"class":261},"renderer.setContrastEnhancement(enhancement)\n",[252,1458,1459],{"class":111,"line":584},[252,1460,1461],{"class":261},"shade.setRenderer(renderer)\n",[252,1463,1465],{"class":111,"line":1464},18,[252,1466,1467],{"class":261},"shade.triggerRepaint()\n",[14,1469,1470,1472,1473,1477],{},[211,1471,406],{}," Pinning the stretch to the full 0–255 range rather than letting QGIS compute it from the visible extent is what stops the hillshade re-contrasting itself as you pan, which otherwise makes flat regions look dramatic and mountains look washed out. The layer belongs at the bottom of the tree with the thematic layers above it set to multiply, as covered in ",[21,1474,1476],{"href":1475},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fset-layer-opacity-and-blend-mode-pyqgis\u002F","opacity and blend modes"," — that arrangement, rather than any transparency setting, is what produces shaded thematic mapping.",[200,1479,1481],{"id":1480},"qgis-version-compatibility","QGIS version compatibility",[14,1483,1484,1485,1487],{},"The examples target ",[211,1486,213],{}," (Python 3.12).",[1489,1490,1491,1507],"table",{},[1492,1493,1494],"thead",{},[1495,1496,1497,1501,1504],"tr",{},[1498,1499,1500],"th",{},"QGIS version",[1498,1502,1503],{},"Python",[1498,1505,1506],{},"Notes",[1508,1509,1510,1531,1544,1557,1568],"tbody",{},[1495,1511,1512,1516,1519],{},[1513,1514,1515],"td",{},"3.16 LTR",[1513,1517,1518],{},"3.7",[1513,1520,1521,294,1524,294,1527,1530],{},[231,1522,1523],{},"native:slope",[231,1525,1526],{},"native:aspect",[231,1528,1529],{},"native:hillshade"," and the GDAL equivalents present.",[1495,1532,1533,1536,1539],{},[1513,1534,1535],{},"3.22 LTR",[1513,1537,1538],{},"3.9",[1513,1540,1541,1543],{},[231,1542,1302],{}," available for combining aligned rasters.",[1495,1545,1546,1549,1551],{},[1513,1547,1548],{},"3.28 LTR",[1513,1550,1538],{},[1513,1552,1553,1556],{},[231,1554,1555],{},"native:reclassifybytable"," boundary handling stable.",[1495,1558,1559,1562,1565],{},[1513,1560,1561],{},"3.34 LTR",[1513,1563,1564],{},"3.12",[1513,1566,1567],{},"Baseline for this page.",[1495,1569,1570,1573,1575],{},[1513,1571,1572],{},"3.40+",[1513,1574,1564],{},[1513,1576,1577],{},"Additional terrain algorithms exposed through the native provider.",[200,1579,1581],{"id":1580},"troubleshooting","Troubleshooting",[205,1583,1584,1590,1599,1605,1611,1617,1626],{},[208,1585,1586,1589],{},[211,1587,1588],{},"Slope is almost all zero."," The DEM is in a geographic CRS. Reproject with bilinear resampling.",[208,1591,1592,1595,1596,1598],{},[211,1593,1594],{},"Slope values are ten times too large."," Vertical and horizontal units differ. Set ",[231,1597,598],{}," to the conversion factor, not to taste.",[208,1600,1601,1604],{},[211,1602,1603],{},"A cliff appears around every gap."," Nodata was never declared, so the fill value is being treated as elevation.",[208,1606,1607,1610],{},[211,1608,1609],{},"The relief looks inverted."," The azimuth is in the southern half. Return it to 315.",[208,1612,1613,1616],{},[211,1614,1615],{},"Averaging aspect gives nonsense."," Aspect is circular. Reclassify into sectors and take the majority instead.",[208,1618,1619,1622,1623,1625],{},[211,1620,1621],{},"Everything flat is classified as north."," The ",[231,1624,674],{}," flat sentinel fell into the first range. Exclude it explicitly in the reclassification table.",[208,1627,1628,1633],{},[211,1629,1630,1632],{},[231,1631,1306],{}," refuses the inputs."," The rasters are not aligned. Derive them all from one DEM, or warp to a common grid first.",[200,1635,1637],{"id":1636},"conclusion","Conclusion",[14,1639,1640,1641,1643],{},"Check the CRS and the nodata before anything else, keep ",[231,1642,598],{}," as a unit conversion rather than a style control, treat aspect as circular data with a separate flat class, and reserve exaggeration and multi-directional tricks for the hillshade, which is the only one of the three that is a picture rather than a measurement.",[200,1645,1647],{"id":1646},"frequently-asked-questions","Frequently Asked Questions",[14,1649,1650,1653,1654,1658],{},[211,1651,1652],{},"Which is better, the native or GDAL slope?","\nThey agree to within rounding. GDAL is faster on large files and offers percent output, edge computation and the Zevenbergen-Thorne algorithm; native is simpler and returns a layer that slots into a ",[21,1655,1657],{"href":1656},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002F","chained workflow"," with less ceremony.",[14,1660,1661,1664,1665,464,1669,1672],{},[211,1662,1663],{},"How do I get slope in a ratio like 1 in 20?","\nCompute percent slope and take the reciprocal in the ",[21,1666,1668],{"href":1667},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fraster-calculator-pyqgis\u002F","raster calculator",[231,1670,1671],{},"100 \u002F \"slope_pct@1\""," gives the run for one unit of rise, with a guard needed where slope is zero.",[14,1674,1675,1678],{},[211,1676,1677],{},"Should I smooth the DEM before deriving slope?","\nOnly if the DEM is noisy and the noise is not real terrain. A light low-pass filter removes stepping artefacts from integer-valued DEMs; smoothing a good DEM discards real detail and flatters the result.",[14,1680,1681,1684],{},[211,1682,1683],{},"Can I compute these on a virtual raster?","\nYes. A VRT mosaic behaves as an ordinary input, which is the usual way to derive across a directory of tiles without building a physical mosaic first.",[200,1686,1688],{"id":1687},"related","Related",[205,1690,1691,1696,1702,1708,1713],{},[208,1692,1693,1695],{},[21,1694,24],{"href":23}," — the guide this recipe belongs to",[208,1697,1698],{},[21,1699,1701],{"href":1700},"\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fcreate-contours-from-dem-pyqgis\u002F","Create Contours from a DEM in PyQGIS",[208,1703,1704],{},[21,1705,1707],{"href":1706},"\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fextract-elevation-profile-along-line-pyqgis\u002F","Extract an Elevation Profile Along a Line in PyQGIS",[208,1709,1710],{},[21,1711,1712],{"href":924},"Zonal Statistics in PyQGIS",[208,1714,1715],{},[21,1716,1717],{"href":1667},"Use the Raster Calculator in PyQGIS",[1719,1720,1721],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":248,"searchDepth":271,"depth":271,"links":1723},[1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734],{"id":202,"depth":271,"text":203},{"id":237,"depth":271,"text":238},{"id":417,"depth":271,"text":418},{"id":617,"depth":271,"text":618},{"id":1033,"depth":271,"text":1034},{"id":1310,"depth":271,"text":1311},{"id":1480,"depth":271,"text":1481},{"id":1580,"depth":271,"text":1581},{"id":1636,"depth":271,"text":1637},{"id":1646,"depth":271,"text":1647},{"id":1687,"depth":271,"text":1688},"Derive terrain rasters from a DEM with native and GDAL algorithms — degrees versus percent slope, the z-factor, aspect on flat ground, hillshade azimuth, and multi-directional shading.","md",{"slug":1738,"type":1739,"breadcrumb":1740,"datePublished":1741,"dateModified":1741},"generate-slope-aspect-hillshade-pyqgis","article","Slope, Aspect & Hillshade","2026-08-27","\u002Fspatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fgenerate-slope-aspect-hillshade-pyqgis",{"title":5,"description":1735},"spatial-data-processing-automation\u002Fterrain-and-interpolation-analysis\u002Fgenerate-slope-aspect-hillshade-pyqgis\u002Findex","giuS9UHaaYTCGN3Fdrne1Pjvjrbawoq8NvTEM_Dcieg",1787823363921]