DEM management#

DEM are used in two ways in EOReader:

  • for computing DEM bands (DEM, SLOPE, HILLSHADE)

  • for orthorectification (SAR, with SNAP or VHR data)

There are several things you need to know to handle DEMs properly.

# Imports
import os
import tempenv

import matplotlib.pyplot as plt


from eoreader.reader import Reader
from eoreader.bands import DEM, SLOPE, HILLSHADE, VV, RED
from eoreader.env_vars import DEM_PATH, SNAP_DEM_NAME
from eoreader.keywords import DEM_KW, SLOPE_KW, HILLSHADE_KW
from eoreader.products import SnapDems
/home/docs/checkouts/readthedocs.org/user_builds/eoreader/envs/stable/lib/python3.9/site-packages/dask/dataframe/__init__.py:42: FutureWarning: 
Dask dataframe query planning is disabled because dask-expr is not installed.

You can install it with `pip install dask[dataframe]` or `conda install dask`.
This will raise in a future version.

  warnings.warn(msg, FutureWarning)
# Create logger
import logging
from sertit import logs

logger = logging.getLogger("eoreader")
logs.init_logger(logger)
# Open the product
path = os.path.join("/home", "prods", "S2", "PB 02.07+", "S2B_MSIL2A_20200114T065229_N0213_R020_T40REQ_20200114T094749.SAFE")
prod = Reader().open(path)
2025-04-18 15:55:31,831 - [WARNING] - There is no existing products in EOReader corresponding to /home/prods/S2/PB 02.07+/S2B_MSIL2A_20200114T065229_N0213_R020_T40REQ_20200114T094749.SAFE.
2025-04-18 15:55:31,831 - [INFO] - Your given path may not be a satellite image. If it is, maybe the product isn't handled by EOReader. If you are sure this product is handled, it is either corrupted or you may need to go deeper in the filetree to find the correct path to give.
2025-04-18 15:55:31,832 - [DEBUG] - Please look at what folder you should give to EOReader by accessing the documentation: https://eoreader.readthedocs.io/latest/main_features.html#recognized-paths
# DEM paths
dem_folder = os.path.join("/home", "ds2_db2", "BASES_DE_DONNEES", "GLOBAL")
dtm = os.path.join(dem_folder, "MERIT_Hydrologically_Adjusted_Elevations", "MERIT_DEM.vrt")
dem = os.path.join(dem_folder, "COPDEM_30m", "COPDEM_30m.vrt")
dem_tif = os.path.join(dem_folder, "EUDEM_v2", "eudem_wgs84.tif")

DEM bands#

DEM bands are computed using the DEM stored in the EOREADER_DEM_PATH environment variable.
Without anything specified, DEM bands cannot be computed and an error will be thrown.

Note: SAR products cannot load a HILLSHADE band !

# Error when computing the DEM band: Iy hasn't been specified it
try:
    prod.load(DEM)[DEM]
except ValueError as msg:
   logger.error(msg)     
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[5], line 3
      1 # Error when computing the DEM band: Iy hasn't been specified it
      2 try:
----> 3     prod.load(DEM)[DEM]
      4 except ValueError as msg:
      5    logger.error(msg)     

AttributeError: 'NoneType' object has no attribute 'load'
# This works now
with tempenv.TemporaryEnvironment({DEM_PATH: dem}):
    prod.load(DEM)[DEM]
2023-05-31 11:46:35,485 - [DEBUG] - Loading DEM bands ['DEM']
2023-05-31 11:46:35,486 - [DEBUG] - Warping DEM for 20200114T065229_S2_T40REQ_L2A_094749
2023-05-31 11:46:35,487 - [DEBUG] - Using DEM: /home/data/DS2/BASES_DE_DONNEES/GLOBAL/COPDEM_30m/COPDEM_30m.vrt

However, in some cases, you may want to specify a specific DEM to load a band from it.

The most common use of this is:

  • I want to compute the hillshade from a DEM

  • I want to compute the slope of a DTM as I don’t want the slopes of buildings or trees !

  • I want those two bands coregistered by default

When specifying a DEM in the load or stack function, you don’t need to set the EOREADER_DEM_PATH environment variable

dem_bands = prod.load(
    [DEM, SLOPE, HILLSHADE], 
    **{
        DEM_KW: dem,
        SLOPE_KW: dtm, # We want a DTM here
        HILLSHADE_KW: dem,        
    }
)
2023-05-31 11:49:08,486 - [DEBUG] - Loading DEM bands ['DEM', 'SLOPE', 'HILLSHADE']
2023-05-31 11:49:08,489 - [DEBUG] - Already existing DEM for 20200114T065229_S2_T40REQ_L2A_094749. Skipping process.
2023-05-31 11:49:10,335 - [DEBUG] - Warping DEM for 20200114T065229_S2_T40REQ_L2A_094749
2023-05-31 11:49:10,338 - [DEBUG] - Using DEM: /home/data/DS2/BASES_DE_DONNEES/GLOBAL/MERIT_Hydrologically_Adjusted_Elevations/MERIT_DEM.vrt
2023-05-31 11:49:10,599 - [DEBUG] - Computing slope for 20200114T065229_S2_T40REQ_L2A_094749
2023-05-31 11:56:15,503 - [DEBUG] - Already existing DEM for 20200114T065229_S2_T40REQ_L2A_094749. Skipping process.
2023-05-31 11:56:15,513 - [DEBUG] - Computing hillshade DEM for S2B_MSIL2A_20200114T065229_N0213_R020_T40REQ_20200114T094749
# Plot DEM bands
ncols = len(dem_bands)
plt.figure(figsize=(6, 6 * ncols))
i = 0
for key in dem_bands.keys():
    axes = plt.subplot(3, 1, i+1)
    dem_bands[key][0, ::10, ::10].plot.imshow(robust=True)
    i += 1
../_images/6dc9d9b82a6c2701276fec5b92ba15b3c54088582627a60ec974e6b474415281.png

Orthorectification#

SAR orthorectification (with SNAP)#

SNAP uses its own DEM mecanics: it downloads DEM tiles on its own and everything is managed without any intervention of the user.

By default, EOReader forces SNAP to use the COPDEM-30 (GLO-30) as its a global DTM.

However, this can be modified by setting the environment variable overriding the default DEM name used in SNAP. The user can choose GETASSE30, SRTM 3Sec, External DEM

If EOREADER_SNAP_DEM_NAME is set to External DEM, SNAP will use the DEM stored in EOREADER_DEM_PATH as an external DEM.

Warning: It seems that SNAP can only DEM with TIF format and not VRT.
Using such DEM format would result with the following error message: Error: No product reader found for /path/to/dem.vrt

# Open a SAR product
path = os.path.join("/home", "prods", "S1", "S1B_IW_GRDH_1SDV_20191215T180300_20191215T180325_019379_0249B2_C99C.SAFE")
prod = Reader().open(path)
# Orthorectifying with COPDEM-30
vv = prod.load(VV, pixel_size=prod.pixel_size*100)[VV]
vv.plot()
SNAP Release version 9.0.0
SNAP home: /opt/snap/bin/..
SNAP debug: null
SNAP log level: WARNING
Java home: /opt/snap/jre/jre
Java version: 1.8.0_242
Processors: 16
Max memory: 40.9 GB
Cache size: 23.0 GB
Tile parallelism: 14
Tile size: 512 x 512 pixels

To configure your gpt memory usage:
Edit snap/bin/gpt.vmoptions

To configure your gpt cache size and parallelism:
Edit .snap/etc/snap.properties or gpt -c ${cachesize-in-GB}G -q ${parallelism} 
Executing processing graph
OpenSearch: https://scihub.copernicus.eu/gnss/search?q=platformname:Sentinel-1 AND platformnumber:B AND producttype:AUX_POEORB AND beginposition:[2019-12-01T00:00:000Z TO 2019-12-31T24:00:000Z]
OpenSearch: 31 total results on 1 pages.
OpenSearch: https://scihub.copernicus.eu/gnss/search?q=platformname:Sentinel-1 AND platformnumber:B AND producttype:AUX_POEORB AND beginposition:[2019-12-01T00:00:000Z TO 2019-12-31T24:00:000Z]
version = 3.1
10%20%30%40%50%60%70%.80%90%Copernicus_DSM_COG_10_N44_00_W005_00_DEM.tif
Copernicus_DSM_COG_10_N44_00_W004_00_DEM.tif
Copernicus_DSM_COG_10_N44_00_W003_00_DEM.tif
Copernicus_DSM_COG_10_N44_00_W002_00_DEM.tif
Copernicus_DSM_COG_10_N44_00_W001_00_DEM.tif
Copernicus_DSM_COG_10_N43_00_W001_00_DEM.tif
Copernicus_DSM_COG_10_N43_00_W005_00_DEM.tif
Copernicus_DSM_COG_10_N43_00_W004_00_DEM.tif
Copernicus_DSM_COG_10_N43_00_W003_00_DEM.tif
Copernicus_DSM_COG_10_N43_00_W002_00_DEM.tif
Copernicus_DSM_COG_10_N42_00_W001_00_DEM.tif
Copernicus_DSM_COG_10_N42_00_W002_00_DEM.tif
Copernicus_DSM_COG_10_N42_00_W005_00_DEM.tif
Copernicus_DSM_COG_10_N42_00_W004_00_DEM.tif
Copernicus_DSM_COG_10_N42_00_W003_00_DEM.tif
2023-05-31 11:58:26,155 - [DEBUG] - Loading bands ['VV']
2023-05-31 11:58:29,995 - [DEBUG] - Pre-process SAR image
Killed
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /home/data/eoreader/eoreader/products/sar/sar_product.py:432, in SarProduct.get_band_paths(self, band_list, pixel_size, **kwargs)
    431     band_id = self.bands[band].id
--> 432     band_paths[band] = files.get_file_in_dir(
    433         self._get_band_folder(),
    434         f"*{self.condensed_name}_{band_id}.tif",
    435         exact_name=True,
    436     )
    437 except FileNotFoundError:

File /opt/conda/lib/python3.10/site-packages/sertit/files.py:1146, in get_file_in_dir(directory, pattern_str, extension, filename_only, get_list, exact_name)
   1145 if len(file_list) == 0:
-> 1146     raise FileNotFoundError(
   1147         f"File with pattern {glob_pattern} not found in {directory}"
   1148     )
   1150 # Return list, file path or file name

FileNotFoundError: File with pattern *20191215T180300_S1_VV_VH_IW_GRD_VV.tif not found in /tmp/tmp4i911y3p/tmp_20191215T180300_S1_VV_VH_IW_GRD

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
File /home/data/eoreader/eoreader/products/sar/sar_product.py:817, in SarProduct._pre_process_sar(self, band, pixel_size, **kwargs)
    816 try:
--> 817     misc.run_cli(cmd_list)
    818 except RuntimeError as ex:

File /opt/conda/lib/python3.10/site-packages/sertit/misc.py:370, in run_cli(cmd, timeout, check_return_value, in_background, cwd)
    369 if check_return_value and retval != 0:
--> 370     raise RuntimeError(f"Exe {cmd[0]} has failed.")
    372 return retval, output

RuntimeError: Exe gpt has failed.

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
Cell In[10], line 2
      1 # Orthorectifying with COPDEM-30
----> 2 vv = prod.load(VV, pixel_size=prod.pixel_size*100)[VV]
      3 vv.plot()

File /home/data/eoreader/eoreader/products/product.py:876, in Product.load(self, bands, pixel_size, size, **kwargs)
    874 # Load bands (only once ! and convert the bands to be loaded to correct format)
    875 unique_bands = misc.unique(bands)
--> 876 band_xds = self._load(unique_bands, pixel_size, size, **kwargs)
    878 # Rename all bands and add attributes
    879 for key, val in band_xds.items():

File /home/data/eoreader/eoreader/products/product.py:974, in Product._load(self, bands, pixel_size, size, **kwargs)
    972 if unique_bands:
    973     LOGGER.debug(f"Loading bands {to_str(unique_bands)}")
--> 974     loaded_bands = self._load_bands(
    975         unique_bands, pixel_size=pixel_size, size=size, **kwargs
    976     )
    978     # Compute index (they conserve the nodata)
    979     if index_list:
    980         # Collocate bands before indices to ensure the same size to perform operations between bands

File /home/data/eoreader/eoreader/products/sar/sar_product.py:642, in SarProduct._load_bands(self, bands, pixel_size, size, **kwargs)
    640 if pixel_size is None and size is not None:
    641     pixel_size = self._pixel_size_from_img_size(size)
--> 642 band_paths = self.get_band_paths(bands, pixel_size, **kwargs)
    644 # Open bands and get array (resampled if needed)
    645 band_arrays = {}

File /home/data/eoreader/eoreader/products/sar/sar_product.py:454, in SarProduct.get_band_paths(self, band_list, pixel_size, **kwargs)
    452                 band_paths[band] = self._despeckle_sar(speckle_band, **kwargs)
    453             else:
--> 454                 band_paths[band] = self._pre_process_sar(
    455                     band, pixel_size, **kwargs
    456                 )
    458 return band_paths

File /home/data/eoreader/eoreader/products/sar/sar_product.py:819, in SarProduct._pre_process_sar(self, band, pixel_size, **kwargs)
    817         misc.run_cli(cmd_list)
    818     except RuntimeError as ex:
--> 819         raise RuntimeError("Something went wrong with SNAP!") from ex
    821 # Convert DIMAP images to GeoTiff
    822 LOGGER.debug("Converting DIMAP to GeoTiff")

RuntimeError: Something went wrong with SNAP!
# Orthorectifying with GETASSE
prod.clean_tmp()
with tempenv.TemporaryEnvironment(
    {
        SNAP_DEM_NAME: SnapDems.GETASSE30.value
    }
):
    vv2 = prod.load(VV, pixel_size=prod.pixel_size*100)[VV]
    vv2.plot()
# Orthorectifying with custom TIF DEM (EUDEM)
with tempenv.TemporaryEnvironment(
    {
        SNAP_DEM_NAME: SnapDems.EXT_DEM.value,
        DEM_PATH: dem_tif
    }
):
    vv = prod.load(VV, pixel_size=prod.pixel_size*100)[VV]

VHR orthorectification#

Some products are not orthorectified by default (according what product type you bought).
It may be wanted, in order to control yourself the orthorectification.
EOReader can orthorectify VHR products: DIMAP (Pleiades and SPOT) and Maxar (GeoEye and WorldView) products.

Warning: The orthorectification may take a while !

# Open a VHR product in sensor geometry
path = os.path.join("/home", "prods", "PLEIADES", "3302499201", "IMG_PHR1A_MS_004")
prod = Reader().open(path)
# Error when orthorectifying the RED band: Iy hasn't been specified it
try:
    prod.load(RED)[RED]
except ValueError as msg:
   logger.error(msg)
# With environment variable
with tempenv.TemporaryEnvironment({DEM_PATH: dem}):
    red = prod.load(RED)[RED]

red.plot()
# With keyword
red = prod.load(RED, **{DEM_KW: dem})[RED]