9

I would like to shift a raster by a specified distance in x and y.

I tried to create a world file, as is suggested here, but neither Raster: Conversion: Translate in QGIS nor GDAL Conversion Translate in the Processing Toolbox created a world file when saved in the suggested formats. How can I shift raster layer for making cloud-shadow mask?

I tried from the Python console, as is suggested below, but I get an error: AttributeError: 'NoneType' object has no attribute 'SetGeoTransform'. I am not familiar with the Python console, so I may be doing something wrong. How to translate (reposition) a .tif raster layer?

The plugin rasmover, which is suggested in several threads, does not appear to exist anymore?

2
  • 2
    Open world file with notepad and shift coordinates by x and y.
    – Frodo
    Commented Oct 22, 2019 at 19:59
  • Error in newest version of QGIS
    – Jim Garner
    Commented Jun 4, 2020 at 6:29

5 Answers 5

4

I think you can give a try to Freehand raster georeferencer. It is QGIS plugin. You must add raster to QGIS from this plugins toolbar to start working with it and even it's already georeferenced, you can make changes to it.

Sample gif: enter image description here

2

If you have a lot of rasters, this would be a better way. This script shifts all the selected rasters a specified x and y distance and saves them to a specified folder.

"""
Use in the QGIS Console Script Editor
Shifts selected rasters a specified amount in an X, Y direction,
and then saves the new rasters in the SHIFTED_FOLDER.
"""

shift_x = -50
shift_y = 50
SHIFTED_FOLDER = r"B:\Satellite Processing B\General GIS\Test"

for layer in iface.layerTreeView().selectedLayers():
    path = layer.source()
    new_path = os.path.join(SHIFTED_FOLDER,os.path.basename(path))

    min_x = layer.extent().xMinimum() + shift_x
    min_y = layer.extent().yMinimum() + shift_y
    max_x = layer.extent().xMaximum() + shift_x
    max_y = layer.extent().yMaximum() + shift_y

    processing.run("gdal:translate", 
            {'INPUT':layer.source()
            ,'TARGET_CRS':None
            ,'NODATA':None
            ,'COPY_SUBDATASETS':False
            ,'OPTIONS':''
            ,'EXTRA':'-a_ullr '+str(min_x)+' '+str(max_y)+' '+str(max_x)+' '+str(min_y)
            ,'DATA_TYPE':0
            ,'OUTPUT':new_path}
        )
    
    iface.addRasterLayer(new_path)

This can be done without python by opening the ReProject tool, and in "additional arguments" use the -a_ullr tag. See GDAL Translate

1

If you need the move the raster by a very specific amount, you can overwrite the corner coordinates with gdal_edit.py -a_ullr on the command line.
Look up the current upper left and lower right corner coordinates with gdalinfo (first pair of numbers).
Add distance to shift east and north to both corners and call gdal_edit.py -a_ullr ulx uly lrx lry datasetname using the new numbers. Make copy of your file before running this command, as it will modify your file.

1

If this is a one-off (and so not worth a PyQGIS solution) but you need a precise, numerically specified shift (vs freehand with Freehand Georeferencer, then Virtual Rasters provide a quick'n'dirty solution.

  1. Choose Raster / Miscellaneous / Create virtual raster

  2. Use your raster-to-be-moved as the single input, and don't much around with any of the other advanced parameters. Save to disk (not [Save to temporary file]) and uncheck "Open output file after...".

  3. The above will have created a .vrt file, which is an XML set of directions how to display the original raster; in this case, so far unchanged. Open the .vrt file in a text editor (i.e. outside QGIS). Find the <GeoTransform> tag, which has 6 numbers.

  4. The 1st and 4th numbers of the GeoTransform are the easting and northing of the upper left corner, in projected coordinates. Manually add the desired translation to these numbers. Save and close the vrt file.

  5. Open the vrt file as a layer in QGIS. It should be shifted as needed.

  6. Optionally, export the layer into a new raster to save it as a flat file in whatever format you need (i.e. not a vrt file referencing the original raster)

More information on virtual rasters and the GeoTransform parameters at https://gdal.org/drivers/raster/vrt.html. As an example built on the one there, suppose the GeoTransform reads:

<GeoTransform>440720.0, 60, 0.0, 3751320.0, 0.0, -60.0</GeoTransform>

If you want to shift x by +2 metres and y by -1 metre, you would change that to:

<GeoTransform>440722.0, 60, 0.0, 3751319.0, 0.0, -60.0</GeoTransform>

By the way, if the numbers are large (e.g. MTM coordinates, they may be written in the vrt file in exponential notation, e.g. the y coordinate could be 3.751319e6

Also, a GeoTransform represents a general affine transformation, so the same approach could be used to not only shift but also rotate and even shear a raster.

1
  • I absolutely love this hack :D
    – Honeybear
    Commented Apr 11 at 13:46
1

To be run from the QGIS Python Console.

The new raster will be in the same folder and with the same extension (tested only with tif), to the name shift by x and by y will be added, e.g. in.tif -> in_10_20.tif.

After the first run, later just call shift_layer(100, -3050), by default the active layer will be used.

Edit 2023.12.19: change default value of layer parameter of shift_layer function from iface.activeLayer() to iface.activeLayer, so active layer woulb be recalculated each time the function is called with the default parameter

import pathlib
from pathlib import Path
from typing import Union, Callable

def shift_layer(
        shift_x: float = 0, shift_y: float = 0,
        layer: Union[
            QgsMapLayer, Callable[[None], QgsMapLayer]] = iface.activeLayer,
        add_layer: bool = True) -> None:
    
    # if iface.activeLayer() is used as default value for layer,
    # the active layer at the moment of declaration will be used
    if isinstance(layer, QgsMapLayer):
        _layer = layer
    else:
        _layer = layer()

    src = pathlib.Path(_layer.source())
    stem_suffix = f'{shift_x}_{shift_y}'
    target: Path = src.with_stem(src.stem + stem_suffix)
    
    shifted_extent_gdal_order: tuple[float, float, float, float] = (
        get_shifted_extent_ggal_order(_layer, shift_x, shift_y)
    )
    shifted_raster: str = shift_raster(
        _layer, shifted_extent_gdal_order, target
    )['OUTPUT']

    if not add_layer:
        return None
    iface.addRasterLayer(shifted_raster, _layer.name() + stem_suffix)

def shift_raster(
        layer: QgsRasterLayer,
        new_extent_gdal_order: tuple[float, float, float, float],
        target: Path) -> dict[str, str]:
    
    assert isinstance(layer, QgsRasterLayer), 'not raster layer'
    
    return processing.run('gdal:translate', {
        'INPUT': layer.source(),
        'EXTRA': f"-a_ullr {' '.join(map(str, new_extent_gdal_order))}",
        'OUTPUT': str(target)
    })
    

def get_shifted_extent_gdal_order(
        layer: QgsMapLayer,
        shift_x: float = 0,
        shift_y: float = 0) -> tuple[float, float, float, float]:
            
    return (
        layer.extent().xMinimum() + shift_x,
        layer.extent().yMaximum() + shift_y,
        layer.extent().xMaximum() + shift_x,
        layer.extent().yMinimum() + shift_y
    )
        
if __name__ == '__console__':
    shift_layer(100, -3050)
    

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