I have used QGIS to batch import and visualise tiled elevation data. This is illuminated greyscale, with identical parameters used each time, so I would have expected the result to be seamless and the tile boundaries to be essentially invisible.

However, the result is a 'patchwork quilt' effect. The shading is not consistent from one tile to the next. I do not understand why not. This is the code used to apply the hillshading:

  def ShadeRaster(raster, pth):

    import processing   

    parameters = {'INPUT': "", 
      'BAND': 1, 
      'COMPUTE_EDGES': False,
      'ZEVENBERGEN': False,
      'Z_FACTOR': 1.0,
      'SCALE': 1.0,
      'AZIMUTH': 315,
      'COMBINED': False,
      'ALTITUDE': 45,
      'OUTPUT': ""}

    parameters['INPUT'] =raster.name()
    parameters['OUTPUT'] = pth + '/' + raster.name() + '.tif'

    return QgsProject.instance().mapLayersByName('Hillshade')[0]

As you can see, the elevation and azimuth of the light source are always the same. But this is how it looks:

enter image description here


  • What software are you viewing this in? Is each tile a unique file or is the hillshade a mosaic? It looks to me like the tiles are single images which have been stretched by default, if you mosaic or build a VRT of the hillshade tiles the contrast stretch should be applied over all the tiles the same. – Michael Stimson Nov 9 '18 at 0:08
  • It is viewed in QGIS. The code shown above generates a TIFF image for each tile, and it is at this point that the inconsistent shading occurs. Agreed, it does look as if the contrast is being stretched, but I am not sure why or how to prevent it. Once generated I dragged and dropped the images into QGIS with the result as shown. – wotnot Nov 9 '18 at 1:27
  • 1
    ArcMap does the same thing, I suggest creating a VRT with GDALBuildVRT (from the command line). A VRT is an XML file that points to your existing raster so is fast to create and takes up only a little space then add the VRT to your QGIS project. Your other option is to go to each hillshade tile layer and turn the stretching off in the layer properties, I think it's MIN-MAX or 'standard deviations' by default; because each tile has different statistics the stretched values do not match. – Michael Stimson Nov 9 '18 at 1:33
  • Thanks for the info ; I will investigate. First option wouldn't necessarily work since the hillshading is done within a plugin and should be fully automatic. Turning off stretching for each raster layer (before generating images) sounds like a better bet. I could presumably automate that. – wotnot Nov 9 '18 at 1:45
  • 1
    Seems I need to set the contrast enhancement for the raster layer to 'no enhancement' before running the hillshading. – wotnot Nov 9 '18 at 2:38

OK... Got it. I changed approach a little bit, having realised that I don't have to run the Hillshade algorithm and generate a TIFF. I can just apply hillshade rendering to the memory raster layer.

I have now got a three stage process (repeated for each grid tile):

  1. Create raster layer from xyz ASCII file
  2. Set contrast enhancement to 'none'
  3. Set render to 'hillshade'

Stages 2 and 3 can actually be done via the QGIS GUI quite easily and applied to multiple layers, but I have done it programmatically.

def ImportRaster(self, raster):

  from PyQt5.QtCore import QFileInfo

  fileInfo = QFileInfo(raster)
  path = fileInfo.filePath()
  baseName = fileInfo.baseName()

  layer = QgsRasterLayer(path, baseName)

  return layer if layer.isValid() else None

def setNoEnhancement(self, layer): # set contrast enhancement to 'no enhancement'
  ContrastEnhancement = QgsContrastEnhancement.NoEnhancement
  myBand = layer.renderer().grayBand()
  myType = layer.renderer().dataType(myBand)
  myEnhancement = QgsContrastEnhancement(myType)
  myEnhancement.setContrastEnhancementAlgorithm(ContrastEnhancement, True)

def setRenderHillshade(self, layer): # set render type to 'hillshade'
  r = QgsHillshadeRenderer(layer.dataProvider(), 1, 315, 45)

def XYZToShadedTIFF(self, fname):

  l = self.ImportRaster(fname) # import file, return layer 'l'

  if not l is None:
    self.setNoEnhancement(l) # ContrastEnhancement to 'none'
    self.setRenderHillshade(l) # hillshade

This works.

Thanks Michael for the assistance.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.