[Git][debian-gis-team/otb][upstream] New upstream version 8.1.1+dfsg

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Fri Jan 13 20:29:28 GMT 2023



Bas Couwenberg pushed to branch upstream at Debian GIS Project / otb


Commits:
f53878cd by Bas Couwenberg at 2023-01-13T16:23:46+01:00
New upstream version 8.1.1+dfsg
- - - - -


14 changed files:

- .gitlab-ci.yml
- CI/headers_check.py
- CMakeLists.txt
- + Docker/Dockerfile
- + Docker/system-dependencies.txt
- Modules/Applications/AppSegmentation/app/otbSegmentation.cxx
- Modules/Filtering/Projection/include/otbPhysicalToRPCSensorModelImageFilter.hxx
- Modules/Filtering/VectorDataManipulation/include/otbVectorDataExtractROI.h
- Modules/IO/IOGDAL/include/otbDEMHandler.h
- Modules/IO/IOGDAL/src/otbDEMHandler.cxx
- Modules/IO/IOGDAL/src/otbGDALRPCTransformer.cxx
- Modules/IO/IOGDAL/test/otbGDALRPCTransformerTest.cxx
- Modules/Registration/Stereo/include/otbStereorectificationDisplacementFieldSource.hxx
- RELEASE_NOTES.txt


Changes:

=====================================
.gitlab-ci.yml
=====================================
@@ -51,6 +51,7 @@ stages:
   - build
   - report
   - deploy
+  - docker
 
 .general:
   retry:
@@ -424,3 +425,18 @@ release-container:
            --form ref=master
            --form variables[OTB_TAG]=$CI_COMMIT_TAG
            https://gitlab.orfeo-toolbox.org/api/v4/projects/126/trigger/pipeline
+
+release-docker:
+  image:
+      name: gcr.io/kaniko-project/executor:debug
+      entrypoint: [""]
+  stage: docker
+  only:
+    refs:
+      - /^release-[0-9]+\.[0-9]+$/
+  script:
+      - /kaniko/executor --cleanup
+                       --verbosity warn
+                       --context $CI_PROJECT_DIR/Docker
+                       --dockerfile $CI_PROJECT_DIR/Docker/Dockerfile
+                       --destination $CI_REGISTRY_IMAGE/orfeotoolbox/otb:$CI_COMMIT_BRANCH


=====================================
CI/headers_check.py
=====================================
@@ -129,6 +129,8 @@ excludedfiles = set([
     './CMake/exportheader.cmake.in',
     './CMake/pre-commit',
     './CMake/qt.conf.in',
+    './Docker/Dockerfile',
+    './Docker/system-dependencies.txt',
     './Documentation/Cookbook/Art/residual_registration-figure.tex',
     './Documentation/Cookbook/rst/Makefile.in',
     './Documentation/Cookbook/rst/conf.py.in',


=====================================
CMakeLists.txt
=====================================
@@ -153,7 +153,7 @@ set(main_project_name ${_OTBModuleMacros_DEFAULT_LABEL})
 # OTB version number.
 set(OTB_VERSION_MAJOR "8")
 set(OTB_VERSION_MINOR "1")
-set(OTB_VERSION_PATCH "0")
+set(OTB_VERSION_PATCH "1")
 set(OTB_VERSION_STRING "${OTB_VERSION_MAJOR}.${OTB_VERSION_MINOR}.${OTB_VERSION_PATCH}")
 
 get_package_name(${OTB_SOURCE_DIR} ${PROJECT_NAME} OTB_VERSION_STRING2)


=====================================
Docker/Dockerfile
=====================================
@@ -0,0 +1,51 @@
+#
+# Copyright (C) 2005-2022 Centre National d'Etudes Spatiales (CNES)
+#
+# This file is part of Orfeo Toolbox
+#
+#     https://www.orfeo-toolbox.org/
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+FROM ubuntu:20.04
+ARG OTB_RELEASE=8.0.0
+
+# Install system dependencies
+COPY system-dependencies.txt .
+RUN apt-get update -y \
+ && apt-get upgrade -y \
+ && cat system-dependencies.txt | DEBIAN_FRONTEND=noninteractive xargs apt-get install -y --no-install-recommends \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* system-dependencies.txt
+
+# Install pre-compiled OTB binaries
+RUN curl https://www.orfeo-toolbox.org/packages/archives/OTB/OTB-$OTB_RELEASE-Linux64.run -o OTB-$OTB_RELEASE-Linux64.run \
+ && chmod +x OTB-$OTB_RELEASE-Linux64.run \
+ && ./OTB-$OTB_RELEASE-Linux64.run --target /opt/otb \
+ && rm OTB-$OTB_RELEASE-Linux64.run
+
+# Symlink Python library to a known path for OTB
+RUN ln -s /usr/lib/x86_64-linux-gnu/libpython3.8.so /opt/otb/lib/libpython3.8.so.rh-python38-1.0
+
+# Persistant env
+ENV CMAKE_PREFIX_PATH=/opt/otb
+ENV PYTHONPATH=/opt/otb/lib/python
+ENV GDAL_DRIVER_PATH=disable
+ENV GDAL_DATA=/opt/otb/share/gdal
+ENV PROJ_LIB=/opt/otb/share/proj
+ENV PATH=/opt/otb/bin:$PATH
+ENV OTB_APPLICATION_PATH=/opt/otb/lib/otb/applications
+ENV LC_NUMERIC=C
+
+# Default command when using 'docker run' or 'docker create'
+CMD /bin/bash


=====================================
Docker/system-dependencies.txt
=====================================
@@ -0,0 +1,30 @@
+ca-certificates
+curl
+libxcb1
+libxcb-composite0
+libxcb-glx0
+libxcb-icccm4
+libxcb-image0
+libxcb-keysyms1
+libxcb-randr0
+libxcb-render0
+libxcb-render-util0
+libxcb-util1
+libxcb-shm0
+libxcb-xfixes0
+libxcb-xinerama0
+libxcb-xinput0
+libxcb-xkb1
+libxcb-shape0
+libx11-xcb1
+libglu1-mesa
+libxrender1
+libxi6
+libxkbcommon0
+libxkbcommon-x11-0
+libxinerama1
+python3 
+python3-dev
+python3-numpy
+libtool
+libopengl0


=====================================
Modules/Applications/AppSegmentation/app/otbSegmentation.cxx
=====================================
@@ -193,8 +193,10 @@ private:
 
     AddParameter(ParameterType_String, "filter.cc.expr", "Condition");
     SetParameterDescription("filter.cc.expr",
-                            "User defined connection condition, written as a mathematical expression. Available variables are p(i)b(i), intensity_p(i) and "
-                            "distance (example of expression: distance < 10 )");
+                            "User defined connection condition, written as a mathematical expression. "
+                            "Available variables are 'p(i)b(i)', 'intensity_p(i)' and "
+                            "'distance'. Substitute (i) by the desired value. "
+                            "Example of expression: intensity_p2 > 0.5.");
 
     // Watershed
     AddChoice("filter.watershed", "Watershed");


=====================================
Modules/Filtering/Projection/include/otbPhysicalToRPCSensorModelImageFilter.hxx
=====================================
@@ -22,7 +22,6 @@
 #define otbPhysicalToRPCSensorModelImageFilter_hxx
 
 #include "otbPhysicalToRPCSensorModelImageFilter.h"
-#include "otbDEMHandler.h"
 
 namespace otb
 {


=====================================
Modules/Filtering/VectorDataManipulation/include/otbVectorDataExtractROI.h
=====================================
@@ -23,7 +23,6 @@
 
 #include "otbVectorDataToVectorDataFilter.h"
 #include "otbRemoteSensingRegion.h"
-#include "otbDEMHandler.h"
 #include "itkMacro.h"
 #include "itkPreOrderTreeIterator.h"
 


=====================================
Modules/IO/IOGDAL/include/otbDEMHandler.h
=====================================
@@ -22,7 +22,11 @@
 #define otbDEMHandler_h
 
 #include "otbImage.h"
-#include "otbGDALDriverManagerWrapper.h"
+#include "otbGDALDatasetWrapper.h"
+#include <string>
+#include <list>
+#include <vector>
+#include <memory>
 
 namespace otb
 {
@@ -32,10 +36,15 @@ namespace otb
  * \brief Observer design pattern to keep track of DEM configuration changes
  * \ingroup OTBIOGDAL
  */
-class DEMObserverInterface {
- public:
-  virtual ~DEMObserverInterface() = default;
+class DEMObserverInterface
+{
+public:
   virtual void Update() = 0;
+protected:
+  DEMObserverInterface() = default;
+  ~DEMObserverInterface() = default;
+  DEMObserverInterface(DEMObserverInterface const&) = delete;
+  DEMObserverInterface& operator=(DEMObserverInterface const&) = delete;
 };
 
 /** \class DEMSubjectInterface
@@ -43,17 +52,26 @@ class DEMObserverInterface {
  * \brief Observer design pattern to keep track of DEM configuration changes
  * \ingroup OTBIOGDAL
  */
-class DEMSubjectInterface {
- public:
-  virtual ~DEMSubjectInterface() = default;
+class DEMSubjectInterface
+{
+public:
   virtual void AttachObserver(DEMObserverInterface *observer) = 0;
   virtual void DetachObserver(DEMObserverInterface *observer) = 0;
   virtual void Notify() const = 0;
+protected:
+  DEMSubjectInterface() = default;
+  ~DEMSubjectInterface() = default;
+  DEMSubjectInterface(DEMSubjectInterface const&) = delete;
+  DEMSubjectInterface& operator=(DEMSubjectInterface const&) = delete;
 };
 
+// Forward declaration of the class at this point. No need to expose the
+// full class definition.
+class DEMHandlerTLS;
+
 /** \class DEMHandler
  *
- * \brief Single access point for DEM data retrieval
+ * \brief Single access point for DEM data retrieval.
  *
  * This class is the single configuration and access point for
  * elevation handling in images projections and localization
@@ -65,15 +83,15 @@ class DEMSubjectInterface {
  * functionalities.
  *
  * The class allows configuring a directory containing DEM tiles
- * (raster tiles will be opened by GDAL drivers) using the OpenDEMDirectory() 
- * method. the OpenGeoidFile() method allows inputting a geoid file as well. 
+ * (raster tiles will be opened by GDAL drivers) using the OpenDEMDirectory()
+ * method. the OpenGeoidFile() method allows inputting a geoid file as well.
  * Last, a default height above ellipsoid can be set using the
  * SetDefaultHeightAboveEllipsoid() method.
  *
  * The class allows retrieving either height above ellipsoid or
  * height above Mean Sea Level (MSL).
  *
- * Here is the complete description of both methods output depending
+ * Here is the complete description of both functions output depending
  * on the class configuration for the SRTM DEM (in the following, no
  * SRTM means DEMDirectory not set, or no coverage for point, or
  * srtm_value is no_data).
@@ -95,130 +113,203 @@ class DEMSubjectInterface {
 class DEMHandler : public DEMSubjectInterface
 {
 public:
-  using Self =          DEMHandler;
-  using PointType =     itk::Point<double, 2>;
-
-  using DatasetUPtr = std::unique_ptr<GDALDataset, void(*)(GDALDataset*)>;
+  using Self      = DEMHandler;
+  using PointType = itk::Point<double, 2>;
 
   /** Retrieve the singleton instance */
   static DEMHandler & GetInstance();
 
-  /** Try to open the DEM directory. 
-  * \param path input path
-  */
-  void OpenDEMFile(const std::string & path);
+  /** Open all raster in the directory.
+   * \param[in] DEMDirectory input directory
+   */
+  void OpenDEMDirectory(std::string DEMDirectory);
 
-  /** Open all raster in the directory. 
-  * \param DEMDirectory input directory
-  */
-  void OpenDEMDirectory(const std::string& DEMDirectory);
+  /** Try to open the DEM directory.
+   * \param[in] path input path
+   */
+  void OpenDEMFile(std::string path);
 
   /** Tells whether the directory contains a raster
-  * \param DEMDirectory input directory
-  */
+   * \param[in] DEMDirectory input directory
+   */
   bool IsValidDEMDirectory(const std::string& DEMDirectory) const;
 
-  /** Try to open a geoid file 
-  * \param geoidFile input geoid path
-  */
-  bool OpenGeoidFile(const std::string& geoidFile);
+  /** Try to open a geoid file
+   * \param[in] geoidFile input geoid path
+   */
+  bool OpenGeoidFile(std::string geoidFile);
 
   /** Return the height above the ellipsoid :
    * - SRTM and geoid both available: srtm_value + geoid_offset
    * - No SRTM but geoid available: geoid_offset
    * - SRTM available, but no geoid: srtm_value
    * - No SRTM and no geoid available: default height above ellipsoid
-   * \param lon input longitude
-   * \param lat input latitude
+   * \param[in] lon input longitude
+   * \param[in] lat input latitude
    * \return height above ellipsoid
   */
   double GetHeightAboveEllipsoid(double lon, double lat) const;
 
   double GetHeightAboveEllipsoid(const PointType& geoPoint) const;
- 
+
   /** Return the height above the mean sea level :
    * - SRTM and geoid both available: srtm_value
    * - No SRTM but geoid available: 0
    * - SRTM available, but no geoid: srtm_value
    * - No SRTM and no geoid available: 0
-   * \param lon input longitude
-   * \param lat input latitude
+   * \param[in] lon input longitude
+   * \param[in] lat input latitude
    * \return height above mean sea level
   */
   double GetHeightAboveMSL(double lon, double lat) const;
-
   double GetHeightAboveMSL(const PointType& geoPoint) const;
 
+  /** Return the offset information from the Geoid.
+   * \param[in] lon input longitude
+   * \param[in] lat input latitude
+   * \return Geoid offset
+  */
+  double GetGeoidHeight(double lon, double lat) const;
+  double GetGeoidHeight(const PointType& geoPoint) const;
+
   /** Return the number of DEM opened */
-  unsigned int GetDEMCount() const;
-  
-  double GetDefaultHeightAboveEllipsoid() const;
+  std::size_t GetDEMCount() const noexcept;
+
+  double GetDefaultHeightAboveEllipsoid() const noexcept;
 
   void SetDefaultHeightAboveEllipsoid(double height);
 
-  /** Get DEM directory 
-   * \param idx directory index
+  /** Get n-th DEM directory name.
+   * \param[in] idx directory index
    * \return the DEM directory corresponding to index idx
+   * \throw std::out_of_range if `idx >= GetNumberOfDEMDirectories()`
    */
-  std::string GetDEMDirectory(unsigned int idx = 0) const;
+  std::string const& GetDEMDirectory(std::size_t idx = 0) const;
+
+  std::size_t GetNumberOfDEMDirectories() const noexcept
+  { return m_DEMDirectories.size(); }
 
   /** Get Geoid file */
-  std::string GetGeoidFile() const;
+  std::string const& GetGeoidFile() const noexcept;
 
-  /** Clear the DEM list and geoid filename, close all elevation datasets 
-   * and reset the default height above ellipsoid */
+  /** Clear the DEM list and geoid filename, close all elevation datasets
+   * and reset the default height above ellipsoid.
+   */
   void ClearElevationParameters();
-  
-  /** Add an element to the current list of observers. The obsever will be updated whenever the DEM configuration
-  is modified*/
+
+  /** Add an element to the current list of observers.
+   * The obsever will be updated whenever the DEM configuration is
+   * modified
+   */
   void AttachObserver(DEMObserverInterface *observer) override {m_ObserverList.push_back(observer);};
-  
+
   /** Remove an element of the current list of observers. */
   void DetachObserver(DEMObserverInterface *observer) override {m_ObserverList.remove(observer);};
-  
+
   /** Update all observers */
   void Notify() const override;
 
   /** Path to the in-memory vrt */
-  const std::string DEM_DATASET_PATH = "/vsimem/otb_dem_dataset.vrt";
-  const std::string DEM_WARPED_DATASET_PATH = "/vsimem/otb_dem_warped_dataset.vrt";
-  const std::string DEM_SHIFTED_DATASET_PATH = "/vsimem/otb_dem_shifted_dataset.vrt";
+  static constexpr char const* DEM_DATASET_PATH         = "/vsimem/otb_dem_dataset.vrt";
+  static constexpr char const* DEM_WARPED_DATASET_PATH  = "/vsimem/otb_dem_warped_dataset.vrt";
+  static constexpr char const* DEM_SHIFTED_DATASET_PATH = "/vsimem/otb_dem_shifted_dataset.vrt";
 
-protected: 
-  DEMHandler(); 
+  /** Accessor to the current (thread-wise) DEM handler.
+   *
+   * Returns the instance of `DEMHandlerTLS` that is meant to be used
+   * exclusively from the current thread.
+   *
+   * As a microoptimisation, this function is public and can be used
+   * from places like `ThreadedGenerateData()` to extract a local
+   * reference to the instance of the handler, which could then be used
+   * with `GetHeightAboveEllipsoid(DEMHandlerTLS const&, double lon,
+   * double lat)` and so on.
+   *
+   * \internal
+   * If no handler has been attributed to the current thread, one will be
+   * picked from the `m_tlses` pool, or created on the fly thanks to
+   * `DoFetchOrCreateHandler`.
+   *
+   * This function is just a facade that caches the static thread local
+   * `DEMHandlerTLS` attributed to the current thread.
+   */
+  DEMHandlerTLS const& GetHandlerForCurrentThread() const;
 
+protected:
+  /**
+   * Default constructor.
+   * Takes care of calling `GDALAllRegister()`.
+   */
+  DEMHandler();
+  /** Destructor.
+   * \post `DEM_DATASET_PATH`, `DEM_WARPED_DATASET_PATH` and
+   * `DEM_SHIFTED_DATASET_PATH` files are deleted.
+   * \post release shared ownership of the `DEMHandlerTLS` instances.
+   */
   ~DEMHandler();
 
 private:
-  DEMHandler(const Self&) = delete;
-  void operator=(const Self&) = delete;  
 
-  void CreateShiftedDataset();
+  /** Internal function to fetch from the pool or create a new
+   * `DEMHandlerTLS.
+   */
+  std::shared_ptr<DEMHandlerTLS> DoFetchOrCreateHandler() const;
+
+  /** (Re)sets GEOID and DEM directory information in the handler.
+   * This function is meant to be called from `DoFetchOrCreateHandler` only:
+   * when a thread needs a new `DEMHandlerTLS` which is created on the fly.
+   * It's not thread safe!
+   *
+   * @warning DO NOT change the GEOID filename or the list of DEM directories
+   * while this function is being executed. These information should only be
+   * changed from a main thread, not from processing threads!
+   */
+  void RegisterConfigurationInHandler(DEMHandlerTLS & handler) const;
 
-  /** Clear the DEM list and close all DEM datasets */
-  void ClearDEMs();
-  
-  /** List of RAII capsules on all opened DEM datasets for memory management */
-  std::vector<otb::GDALDatasetWrapper::Pointer> m_DatasetList;
-  
-  /** Pointer to the DEM dataset */
-  DatasetUPtr m_Dataset = DatasetUPtr(nullptr, [](GDALDataset* DS){if(DS){GDALClose(DS);}});
+  DEMHandler(const Self&) = delete;
+  void operator=(const Self&) = delete;
 
-  /** Pointer to the geoid dataset */
-  DatasetUPtr m_GeoidDS = DatasetUPtr(nullptr, [](GDALDataset* DS){if(DS){GDALClose(DS);}});
-  
   /** Default height above elliposid, used when no DEM or geoid height is available. */
   double m_DefaultHeightAboveEllipsoid;
 
   /** List of the DEM directories currently opened */
   std::vector<std::string> m_DEMDirectories;
 
+  /** List of RAII capsules on all opened DEM datasets for memory management */
+  std::vector<otb::GDALDatasetWrapper::Pointer> m_DatasetList;
+
   /** Filename of the current geoid */
   std::string m_GeoidFilename;
 
   /** Observers on the DEM */
   std::list<DEMObserverInterface *> m_ObserverList;
+
+  /** Pool of actual thread local DEM handlers. */
+  mutable std::vector<std::shared_ptr<DEMHandlerTLS>> m_tlses;
+
 };
 
+
+/**\name Fast conviennce access functions
+ * \ingroup OTBIOGDAL
+ *
+ * DEM fast conviennce access functions.
+ *
+ * These functions are provided to be used with a `DEMHandlerTLS`
+ * instance that could (and should) be obtained in any
+ * `ThreadedGenerateData()` function where `DEMHandler` would be used.
+ *
+ * \see the eponym functions from `DEMHandler` for what they actually
+ * return
+ */
+/// @{
+double GetHeightAboveEllipsoid(DEMHandlerTLS const&, double lon, double lat);
+double GetHeightAboveMSL      (DEMHandlerTLS const&, double lon, double lat);
+double GetGeoidHeight         (DEMHandlerTLS const&, double lon, double lat);
+double GetHeightAboveEllipsoid(DEMHandlerTLS const&, itk::Point<double, 2> geoPoint);
+double GetHeightAboveMSL      (DEMHandlerTLS const&, itk::Point<double, 2> geoPoint);
+double GetGeoidHeight         (DEMHandlerTLS const&, itk::Point<double, 2> geoPoint);
+/// @}
+
 }
 #endif


=====================================
Modules/IO/IOGDAL/src/otbDEMHandler.cxx
=====================================
@@ -20,7 +20,7 @@
 
 #include "otbDEMHandler.h"
 #include "otbGDALDriverManagerWrapper.h"
-#include "boost/filesystem.hpp"
+#include <boost/filesystem.hpp>
 #include <boost/range/iterator_range.hpp>
 #include "gdal_utils.h"
 
@@ -28,22 +28,31 @@
 #include "gdalwarper.h"
 #include "vrtdataset.h"
 
+#include "ogr_spatialref.h"
+
 //TODO C++ 17 : use std::optional instead
 #include <boost/optional.hpp>
 
 #include <mutex>
+#include <thread>
+#include <memory>
+#include <sstream>
 
-#include "ogr_spatialref.h"
+namespace
+{ // Anonymous namespace
+std::mutex demMutex;
+} // Anonymous namespace
 
 namespace otb {
 
 namespace DEMDetails {
 
-// Adapted from boost filesystem documentation : https://www.boost.org/doc/libs/1_53_0/libs/filesystem/doc/reference.html
+// Adapted from boost filesystem documentation:
+// https://www.boost.org/doc/libs/1_53_0/libs/filesystem/doc/reference.html
 std::vector<std::string> GetFilesInDirectory(const std::string & directoryPath)
 {
   std::vector<std::string> fileList;
-  
+
   if ( !boost::filesystem::exists( directoryPath) )
   {
     return fileList;
@@ -53,23 +62,23 @@ std::vector<std::string> GetFilesInDirectory(const std::string & directoryPath)
     fileList.push_back(directoryPath);
     return fileList;
   }
-  
-  // End iterator : default construction yields past-the-end
+
+  // End iterator: default construction yields past-the-end
   for ( const auto & item : boost::make_iterator_range(boost::filesystem::directory_iterator(directoryPath), {}) )
   {
     try
     {
       if ( boost::filesystem::is_directory(item.status()) )
       {
-	auto subDirList = GetFilesInDirectory(item.path().string());
-	fileList.insert(fileList.end(), subDirList.begin(), subDirList.end());
+        auto subDirList = GetFilesInDirectory(item.path().string());
+        fileList.insert(fileList.end(), subDirList.begin(), subDirList.end());
       }
       else
       {
-	fileList.push_back(item.path().string());
+        fileList.push_back(item.path().string());
       }
     }
-    catch (boost::filesystem::filesystem_error& e)
+    catch (boost::filesystem::filesystem_error const& e)
     {
       otbLogMacro(Warning, << e.what())
     }
@@ -78,277 +87,689 @@ std::vector<std::string> GetFilesInDirectory(const std::string & directoryPath)
   return fileList;
 }
 
-std::mutex demMutex;
-
-boost::optional<double> GetDEMValue(double lon, double lat, GDALDataset& ds)
+/**
+ * Internal RAII wrapper for providing access to `GDALDataset` and
+ * caching related information (projection for WGS84 case and geo
+ * transformation).
+ *
+ * \invariant if WGS84 then `(m_Dataset != nullptr) == (m_poCT != nullptr)`
+ * \internal
+ */
+struct DatasetCache
 {
-  const std::lock_guard<std::mutex> lock(demMutex);
+  /**@name `GDALDataset` accessors */
+  /** @{ */
+  GDALDataset * get          () const { return m_Dataset.get();}
+  GDALDataset * operator->   () const { return m_Dataset.get();}
+  GDALDataset & operator*    () const { assert(m_Dataset); return *m_Dataset;}
+  /** @} */
+
+  /// Tells whether a valid dataset is associated.
+  explicit      operator bool() const { return bool(m_Dataset); }
+
+  DatasetCache() = default;
+  ~DatasetCache() = default;
+
+  /** Constructor that takes over a `GDALDataset` ownership.
+   * \post Projection information will have been fetched (if WGS84).
+   * \post Geo Transform information will have been fetched
+   */
+  explicit DatasetCache(GDALDataset *ds)
+  { reset(ds); }
+
+  /** Constructor from a filename.
+   * \post Projection information will have been fetched (if WGS84).
+   * \post Geo Transform information will have been fetched
+   */
+  explicit DatasetCache(std::string const& file)
+    : DatasetCache(static_cast<GDALDataset*>(GDALOpen(file.c_str(), GA_ReadOnly)))
+    {}
+
+  /** Release ownership of the dataset.
+   * And clear projection related information.
+   */
+  void release()
+  {
+    m_Dataset.release();
+    m_poCT.release();
+    m_isWGS84 = false;
+  }
 
+  /** Takes over a `GDALDataset` ownership.
+   * \post Projection information will have been fetched (if WGS84).
+   * \post Geo Transform information will have been fetched
+   */
+  void reset(GDALDataset* ds)
+  {
+    m_Dataset.reset(ds);
+    if (m_Dataset)
+    {
 #if GDAL_VERSION_NUM >= 3000000
-  auto srs = ds.GetSpatialRef();
+      auto srs = ds->GetSpatialRef();
 #else
-  auto projRef = ds.GetProjectionRef();
-  
-  std::unique_ptr<OGRSpatialReference> srsUniquePtr;
-  OGRSpatialReference* srs = nullptr;
-  // GetProjectionRef() returns an empty non null string if no projection is available
-  if (strlen(projRef) != 0 )
-  {
-    srsUniquePtr = std::make_unique<OGRSpatialReference>(ds.GetProjectionRef());
-    srs = srsUniquePtr.get();
-  }
+      auto projRef = ds->GetProjectionRef();
+
+      std::unique_ptr<OGRSpatialReference> srsUniquePtr;
+      OGRSpatialReference* srs = nullptr;
+      // GetProjectionRef() returns an empty non null string if no projection is available
+      if (strlen(projRef) != 0 )
+      {
+        srsUniquePtr = std::make_unique<OGRSpatialReference>(ds->GetProjectionRef());
+        srs = srsUniquePtr.get();
+      }
 #endif
 
-  auto wgs84Srs = OGRSpatialReference::GetWGS84SRS();
+      static auto const& wgs84Srs = OGRSpatialReference::GetWGS84SRS(); // 1%
+      m_isWGS84 = false;
 
-  // Convert input lon lat into the coordinates defined by the dataset if needed.
-  if (srs && !srs->IsSame(wgs84Srs))
-  {
-    auto poCT = std::unique_ptr<OGRCoordinateTransformation>
-                    (OGRCreateCoordinateTransformation(wgs84Srs, srs));
+      // Convert input lon lat into the coordinates defined by the dataset if needed.
+      if (srs && !srs->IsSame(wgs84Srs)) // 10% of the time
+      {
+        m_isWGS84 = true;
+        m_poCT = std::unique_ptr<OGRCoordinateTransformation>
+          (OGRCreateCoordinateTransformation(wgs84Srs, srs));
+      }
+
+      m_Dataset->GetGeoTransform(m_geoTransform);
 
-    if (poCT && !poCT->Transform( 1, &lon, &lat ) )
+      m_NoDataValue = m_Dataset->GetRasterBand(1)->GetNoDataValue();
+    }
+    else
     {
-      return boost::none;
+      m_poCT.release();
     }
   }
 
-  double geoTransform[6];
-  
-  ds.GetGeoTransform(geoTransform);
+  /** Convert {lon, lat} coordinates if WGS84 and projection is
+   * defined.
+   *
+   * \todo what about `isWGS84() == true` and projection is not defined?
+   * Is it even even possible?
+   */
+  bool convert_lon_lat(double & lon, double & lat) const
+  {
+    assert((m_poCT || !isWGS84()) && "Expected: isWGS84() => projection is defined"); // => no need to test isWGS84
+    if (/*isWGS84() &&*/ m_poCT) {
+      return m_poCT->Transform( 1, &lon, &lat );
+    }
+    return true;
+  }
 
-  auto x = (lon - geoTransform[0]) / geoTransform[1] - 0.5;
-  auto y = (lat - geoTransform[3]) / geoTransform[5] - 0.5;
+  /// Apply geo transformation to return pixels associated to {lon, * lat}.
+  std::pair<double, double> transform(double lon, double lat) const
+  {
+    auto const x = (lon - m_geoTransform[0]) / m_geoTransform[1] - 0.5;
+    auto const y = (lat - m_geoTransform[3]) / m_geoTransform[5] - 0.5;
+    return std::make_pair(x, y);
+  }
+
+  bool isWGS84() const noexcept { return m_isWGS84; }
+
+  /// Accessors to Geo Transformation.
+  double      * getGeoTransform()       noexcept { return m_geoTransform;}
+  double const* getGeoTransform() const noexcept { return m_geoTransform;}
+
+  /// Accessor to No Data value
+  double GetNoDataValue() const noexcept { return m_NoDataValue;}
+
+
+  DatasetCache & operator=(DatasetCache const&) = delete;
+  DatasetCache & operator=(DatasetCache &&    ) = default;
 
-  if (x < 0 || y < 0 || x > ds.GetRasterXSize() || y > ds.GetRasterYSize())
+private:
+  using DatasetUPtr = std::unique_ptr<GDALDataset, void(*)(GDALDataset*)>;
+  using OCT_ptr     = std::unique_ptr<OGRCoordinateTransformation>;
+  DatasetUPtr m_Dataset = DatasetUPtr(nullptr, [](GDALDataset* DS){if(DS){GDALClose(DS);}});
+  OCT_ptr     m_poCT;
+  bool        m_isWGS84 = false;
+  double      m_geoTransform[6] = {};
+  double      m_NoDataValue     = {};
+};
+
+/**
+ * Obtain DEM value from dataset at specified position.
+ *
+ * General operation sequence:
+ * 1. Convert {lon, lat} in WGS84 case
+ * 2. Computed associated pixel coordinates (in floatting point unit)
+ *    thanks to geo transformation
+ * 3. Extract 4 pixels around the desired position
+ * 4. Interpolate the final elevation
+ *
+ * \return `boost::none` if {lon, lat} conversion cannot be achieved
+ * \return `boost::none` if {lon, lat} conversion cannot be achieved
+ * \return `boost::none` if pixel extractions from file fails
+ * \return elevation according to DEM at {lot, lat} position
+ * \internal
+ */
+boost::optional<double> GetDEMValue(double lon, double lat, DatasetCache const& dsc)
+{
+  if (!dsc.convert_lon_lat(lon, lat))
   {
     return boost::none;
   }
 
-  auto x_int = static_cast<int>(x);
-  auto y_int = static_cast<int>(y);
+  // C++17 use: `auto const [x,y] = dsc.transform(lon, lat);`
+  auto const xy = dsc.transform(lon, lat);
+  auto const x = xy.first;
+  auto const y = xy.second;
 
-  auto deltaX = x - x_int;
-  auto deltaY = y - y_int;
+  auto is_out_raster = [&](auto x, auto y, GDALDataset & ds) {
+    return x < 0
+      ||   y < 0
+      || 1+x > ds.GetRasterXSize()   // no need to test x > size
+      || 1+y > ds.GetRasterYSize();  // no need to test Y > size
+  };
 
-  if (x < 0 || y < 0 || x+1 > ds.GetRasterXSize() || y+1 > ds.GetRasterYSize())
+  if (is_out_raster(x, y, *dsc))
   {
     return boost::none;
   }
-  
+
+  auto const x_int = static_cast<int>(x);
+  auto const y_int = static_cast<int>(y);
+
+  auto const deltaX = x - x_int;
+  auto const deltaY = y - y_int;
+
   // Bilinear interpolation.
   double elevData[4];
-  
-  auto err = ds.GetRasterBand(1)->RasterIO( GF_Read, x_int, y_int, 2, 2,
-              elevData, 2, 2, GDT_Float64,
-              0, 0, nullptr);
+
+  auto const err = dsc->GetRasterBand(1)->RasterIO( GF_Read, x_int, y_int, 2, 2,
+      elevData, 2, 2, GDT_Float64,
+      0, 0, nullptr);
 
   if (err)
   {
     return boost::none;
   }
 
-  // Test for no data. Don't return a value if one pixel 
-  // of the interpolation is no data.
-  for (int i =0; i<4; i++)
+  // Test for no data. Don't return a value if any pixel of the
+  // interpolation has no data.
+  auto const no_data = dsc.GetNoDataValue();
+  auto const has_no_data = [=](auto const v){ return  v == no_data; };
+  if(std::any_of(std::begin(elevData), std::end(elevData), has_no_data))
   {
-    if (elevData[i] == ds.GetRasterBand(1)->GetNoDataValue())
-    {
-      return boost::none;
-    }
+    return boost::none;
   }
 
-  auto xBil1 = elevData[0] * (1-deltaX) + elevData[1] * deltaX;
-  auto xBil2 = elevData[2] * (1-deltaX) + elevData[3] * deltaX;
+  // C++20: use std::lerp for better precision (expected to be slower)
+  auto const xBil1 = elevData[0] * (1.0 - deltaX) + elevData[1] * deltaX;
+  auto const xBil2 = elevData[2] * (1.0 - deltaX) + elevData[3] * deltaX;
 
-  auto yBil = xBil1 * (1.0 - deltaY) + xBil2 * deltaY;
+  auto const yBil = xBil1 * (1.0 - deltaY) + xBil2 * deltaY;
   return yBil;
 }
 
+/**
+ * Tells whether the dataset has a Geo Transformation.
+ * \internal
+ */
+bool HasGeoTransform(GDALDataset & gdalds)
+{
+  std::array<double, 6> a;
+  return gdalds.GetGeoTransform(a.data()) == CE_None;
+}
+
 }  // namespace DEMDetails
 
+
+/** Internal actual instance of DEM handling wrapper for current thread.
+ * \ingroup OTBIOGDAL
+ *
+ * `GDALDataset` isn't thread safe. However, several datasets can be
+ * opened to the same image. Instances of this class are meant to
+ * provide the final access to `GDALDataset` and live as singletons
+ * in each thread.
+ *
+ * \internal This class is not exposed to other code. It's meant to be
+ * used through `DEMHandler` singleton.
+ *
+ * The current design has one `DEMHandlerTLS` per actual thread. In case
+ * threads are terminated and restarted multiple time (this is the case
+ * with default old ITK 4 architecture that will terminate and respawn
+ * thread for new OTB stream), `DEMHandlerTLS` instance will be
+ * destroyed and re-recreated instead of being reused.
+ *
+ * To reuse the instances of `DEMHandlerTLS` we could:
+ * - have a kind of pool of `DEMHandlerTLS` instances, and a
+ *   thread_local pointer of the instance attributed to the current
+ *   thread. Attributing an instance the first time would require a lock
+ *   to search a free instance in the pool. The TLS object could be a
+ *   RAII capsule that returns the `DEMHandlerTLS` instance to the pool.
+ */
+class DEMHandlerTLS
+{
+public:
+  double GetHeightAboveEllipsoid(double lon, double lat, double defaultHeight) const;
+  boost::optional<double> GetHeightAboveMSL(double lon, double lat) const;
+  boost::optional<double> GetGeoidHeight(double lon, double lat) const;
+
+  DEMHandlerTLS() {
+    otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandlerTLS::DEMHandlerTLS() --> " << this);
+  }
+  ~DEMHandlerTLS() {
+    otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandlerTLS::~DEMHandlerTLS() --> " << this);
+  }
+
+  bool OpenDEMVRTFile();
+  void CloseDEMVRTFile();
+
+  /** Try to open a geoid file
+   * \param geoidFile input geoid path
+   */
+  bool OpenGeoidFile(std::string const& geoidFile);
+
+  /** Clear the DEM list and geoid filename, close all elevation datasets
+   * and reset the default height above ellipsoid */
+  void ClearElevationParameters();
+
+  void CreateShiftedDatasetOnce() const;
+
+private:
+  DEMHandlerTLS(DEMHandlerTLS const&) = delete;
+  void operator=(DEMHandlerTLS const&) = delete;
+
+  using DatasetUPtr = std::unique_ptr<GDALDataset, void(*)(GDALDataset*)>;
+
+  /** Pointer to the DEM dataset */
+  DEMDetails::DatasetCache m_DEMDS;
+
+  /** Pointer to the geoid dataset */
+  DEMDetails::DatasetCache m_GeoidDS;
+};
+
+DEMHandlerTLS const& DEMHandler::GetHandlerForCurrentThread() const
+{
+  static thread_local auto tls = DoFetchOrCreateHandler(); // no need to lock as this is a static TLS
+  return *tls;
+}
+
+std::shared_ptr<DEMHandlerTLS> DEMHandler::DoFetchOrCreateHandler() const
+{
+  // DEMHandlerTLS instances are shared between the global DEMHandler
+  // singleton and the (unique) thread they are associated to.
+  // When a thread terminates, its TLS are destroyed. And as such the
+  // shared_ptr DEMHandlerTLS are destroyed, which decrements the owning count
+  // by 1.
+  // When the DEMHandler singleton is destroyed, each DEMHandlerTLS shared_ptr
+  // is destroyed, and the count is decreased by 1.
+  // When the count reaches 0, the DEMHandlerTLS is actually destroyed.
+  // Infortunately we cannot be sure TLS variables are destroyed before the
+  // DEMHandler singleton. Hence this convoluted approach with
+  // shared_ptr.
+
+  // With the chosen (shared_ptr based) approach, when the owning count is 1,
+  // this means only the DEMHandler singleton owns the TLS handler. And as
+  // such, it could be attributed to another thread.
+  auto handler_is_not_attributed_to_a_thread = [](auto const& h) { return h.use_count() == 1; };
+
+  std::unique_lock<std::mutex> lock(demMutex); // protecting m_tlses
+  for (auto & tls : m_tlses) {
+    if (handler_is_not_attributed_to_a_thread(tls)) {
+      return tls;
+    }
+  }
+  m_tlses.emplace_back(std::make_shared<DEMHandlerTLS>());
+  auto res = m_tlses.back();
+  lock.unlock(); // No need to keep the lock any more thanks to the copy
+                 // of the shared_ptr `res` from the vector that could
+                 // be resized... (which would invalidate any reference
+                 // to back())
+  RegisterConfigurationInHandler(*res);
+  return res;
+}
+
+void DEMHandler::RegisterConfigurationInHandler(DEMHandlerTLS & tls) const
+{
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandler::RegisterConfigurationInHandler(" << &tls <<")");
+  if (! m_DatasetList.empty()) {
+    if (! tls.OpenDEMVRTFile())
+    {
+      otbLogMacro(Warning, << "An unexpected situation has occured: trying to open a VRT created from incompatible DEM files");
+      assert(false);
+      // The following should not be necessary. In the impossible case
+      // the VRT file could not be opened, we just clear everything in
+      // Release mode (i.e. without assertions)
+      // Yes, the clearing has an extremely dirty use of const_cast...
+      const_cast<DEMHandler*>(this)->m_DatasetList.clear();
+      const_cast<DEMHandler*>(this)->m_DEMDirectories.clear();
+    }
+  }
+  if (!m_GeoidFilename.empty()) {
+    tls.OpenGeoidFile(m_GeoidFilename);
+  }
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandler::RegisterConfigurationInHandler(" << &tls <<") END");
+}
+
 // Meyer singleton design pattern
-DEMHandler & DEMHandler::GetInstance() 
+DEMHandler & DEMHandler::GetInstance()
 {
   static DEMHandler s_instance;
   return s_instance;
 }
 
-DEMHandler::DEMHandler() : m_DefaultHeightAboveEllipsoid(0.0)
+DEMHandler::DEMHandler()
+: m_DefaultHeightAboveEllipsoid(0.0)
 {
   GDALAllRegister();
 }
 
 DEMHandler::~DEMHandler()
 {
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandler::destructor()");
+
   // Close all elevation datasets
   ClearElevationParameters();
 
-  VSIUnlink(DEM_DATASET_PATH.c_str());
-  VSIUnlink(DEM_WARPED_DATASET_PATH.c_str());
-  VSIUnlink(DEM_SHIFTED_DATASET_PATH.c_str());
+  // As elevation dataset have been closed, we can remove these files
+  VSIUnlink(DEMHandler::DEM_DATASET_PATH);
+  VSIUnlink(DEMHandler::DEM_WARPED_DATASET_PATH);
+  VSIUnlink(DEMHandler::DEM_SHIFTED_DATASET_PATH);
+
+  { // {} makes sure clearing m_tlses is synchronized
+    const std::lock_guard<std::mutex> lock(demMutex);
+    m_tlses.clear();
+  }
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandler::destructor() END");
+}
+
+void DEMHandlerTLS::CloseDEMVRTFile()
+{
+  m_DEMDS.release();
+}
+
+bool DEMHandlerTLS::OpenDEMVRTFile()
+{
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandlerTLS::OpenDEMVRTFile() --> " << this);
+  // As we are reopening the same file: we should close it first!
+  assert(! m_DEMDS);
+  m_DEMDS = DEMDetails::DatasetCache(DEMHandler::DEM_DATASET_PATH);
+
+  // Note: we don't try to call CreateShiftedDatasetOnce() as the
+  // current implementation of DEMHandler::OpenGeoidFile() indirectly
+  // makes sure to do so by enforcing the existence of a DEMHandlerTLS
+  // instance. This will then trigger the call to
+  // CreateShiftedDatasetOnce() either from DEMHandler::OpenGeoidFile()
+  // and DEMHandler::OpenDEMDirectory()
+  // So... we can just exit
+  return !! m_DEMDS;
 }
 
-void DEMHandler::OpenDEMFile(const std::string& path)
+void DEMHandler::OpenDEMFile(std::string path)
 {
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandler::OpenDEMFile("<<path<<")");
+
+  std::unique_lock<std::mutex> lock(demMutex); // protecting m_tlses
   m_DatasetList.push_back(otb::GDALDriverManagerWrapper::GetInstance().Open(path));
-  std::vector<GDALDatasetH> vrtDatasetList(1);
-  vrtDatasetList[0] = m_DatasetList[0]->GetDataSet();
-  auto close_me = GDALBuildVRT(DEM_DATASET_PATH.c_str(), 1, vrtDatasetList.data(),
-                               nullptr, nullptr, nullptr);
+  for (auto tls : m_tlses) {
+    tls->CloseDEMVRTFile();
+  }
+  // TODO: why [0] and not back()???
+  std::array<GDALDatasetH, 1> vrtDatasetList { m_DatasetList[0]->GetDataSet() };
+  auto close_me = GDALBuildVRT(DEMHandler::DEM_DATASET_PATH, 1, vrtDatasetList.data(),
+      nullptr, nullptr, nullptr);
   // Need to close the dataset, so it is flushed into memory.
   GDALClose(close_me);
-  m_Dataset.reset(static_cast<GDALDataset*>(GDALOpen(DEM_DATASET_PATH.c_str(), GA_ReadOnly)));
-  m_DEMDirectories.push_back(path);
-
-  if(m_GeoidDS)
+  for (auto tls : m_tlses)
   {
-    CreateShiftedDataset();
+    if (! tls->OpenDEMVRTFile())
+    {
+      // Sometimes ungeoreferenced images are pushed into the VRT files,
+      // but as they are not supported, no VRT is produced
+      m_DatasetList.clear();
+      return ; // no push_back, no notification
+    }
   }
-
+  m_DEMDirectories.push_back(move(path));
+  lock.unlock();
   Notify();
 }
 
-void DEMHandler::OpenDEMDirectory(const std::string& DEMDirectory)
+void DEMHandler::OpenDEMDirectory(std::string DEMDirectory)
 {
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandler::OpenDEMDirectory("<<DEMDirectory<<")");
   auto isSameDirectory = [&DEMDirectory](std::string const& s)
-			 {
-			   return s == DEMDirectory;
-			 };
+  {
+    return s == DEMDirectory;
+  };
   if(std::any_of(std::begin(m_DEMDirectories), std::end(m_DEMDirectories), isSameDirectory))
   {
-    otbLogMacro(Info, << "Directory '"<< DEMDirectory << "' already opened.")
+    otbLogMacro(Info, << "DEM Directory '"<< DEMDirectory << "' is already opened.");
     return;
   }
-  
-  // Free the previous in-memory dataset (if any)
-  if (!m_DatasetList.empty())
-    VSIUnlink(DEM_DATASET_PATH.c_str());
 
   auto demFiles = DEMDetails::GetFilesInDirectory(DEMDirectory);
+  std::size_t nb_new_DEM_opened = 0;
+  // Check and open every dem files found in DEMDirectory
   for (const auto & file : demFiles)
   {
+    otbMsgDevMacro("Try to open DEM image: " << file);
     auto ds = otb::GDALDriverManagerWrapper::GetInstance().Open(file);
     if (ds)
     {
-      m_DatasetList.push_back(ds );
+      if (DEMDetails::HasGeoTransform(*ds->GetDataSet()))
+      {
+        m_DatasetList.push_back(ds);
+        ++nb_new_DEM_opened;
+      }
+      else
+      {
+        // GDAL VRT files cannot contain references to ungeoreferenced
+        // images, otherwise GDAL will issue messages like:
+        // > 662: Warning 1: gdalbuildvrt does not support ungeoreferenced image. Skipping BLA/BLA.TIF
+        // > ERROR 4: No such file or directory
+        otbLogMacro(Debug, << "Input image "<< file << " will not be used because it is ungeoreferenced.");
+      }
+    }
+    else
+    {
+      otbMsgDevMacro("Ignoring DEM image: " << file);
     }
   }
-  
-  int vrtSize = m_DatasetList.size();
- 
+
   // Don't build a vrt if there is no dataset
-  if (m_DatasetList.empty())
+  if (nb_new_DEM_opened == 0)
   {
-    m_Dataset.reset(nullptr);
     otbLogMacro(Warning, << "No DEM found in "<< DEMDirectory)
   }
   else
   {
+    otbLogMacro(Info, << nb_new_DEM_opened << " DEM found in "<< DEMDirectory)
+    m_DEMDirectories.push_back(move(DEMDirectory)); // => parameter voluntary taken by value
+
+    // Clean before anything else: Free the previous in-memory dataset (if any)
+    if (m_DatasetList.size() != nb_new_DEM_opened)
+    {
+      const std::lock_guard<std::mutex> lock(demMutex);
+      for (auto tls : m_tlses) {
+        tls->CloseDEMVRTFile();
+      }
+      VSIUnlink(DEMHandler::DEM_DATASET_PATH);
+    }
+
+    // (Re-)Create the VRT from a list of opened dataset
+    std::size_t const vrtSize = m_DatasetList.size();
     std::vector<GDALDatasetH> vrtDatasetList(vrtSize);
-    for (int i = 0; i < vrtSize; i++)
+    for (std::size_t i = 0; i < vrtSize; i++)
     {
       vrtDatasetList[i] = m_DatasetList[i]->GetDataSet();
     }
 
-    auto close_me = GDALBuildVRT(DEM_DATASET_PATH.c_str(), vrtSize, vrtDatasetList.data(),
-                                 nullptr, nullptr, nullptr);
+    otbMsgDevMacro("Building VRT from " << vrtSize << " datasets: " << DEMHandler::DEM_DATASET_PATH);
+    auto close_me = GDALBuildVRT(DEMHandler::DEM_DATASET_PATH, vrtSize, vrtDatasetList.data(),
+        nullptr, nullptr, nullptr);
     // Need to close the dataset, so it is flushed into memory.
     GDALClose(close_me);
-    m_Dataset.reset(static_cast<GDALDataset*>(GDALOpen(DEM_DATASET_PATH.c_str(), GA_ReadOnly)));
-    m_DEMDirectories.push_back(DEMDirectory);
-  }
 
-  if(m_GeoidDS)
-  {
-    CreateShiftedDataset();
+    const std::lock_guard<std::mutex> lock(demMutex);
+    {
+      // Check all requirements are satisfied to open de the VRT file.
+      // If not, clear everything.
+      DEMDetails::DatasetCache vrt(DEMHandler::DEM_DATASET_PATH);
+      // +--> TODO: DatasetCache will also try to check the
+      // projection... Can't we do better?
+      if (!vrt)
+      {
+        // Note: DEMDirectory has been moved into m_DEMDirectories
+        otbLogMacro(Warning, << "Input images from '"<< m_DEMDirectories.back() <<"' cannot be used to create a DEM VRT file, and as such they will be ignored.");
+        m_DatasetList.clear();
+        m_DEMDirectories.clear();
+        return ;
+      }
+    } // make sure to close the vrt (thanks to RAII)
+
+    for (auto tls : m_tlses)
+    {
+      if (! tls->OpenDEMVRTFile())
+      {
+        assert(false); // Should have been handled by the previous tests
+                       // (HasGeoTransform + VRT validity tests)
+        // Sometimes ungeoreferenced images are pushed into the VRT files,
+        // but as they are not supported, no VRT is produced
+        m_DatasetList.clear();
+        m_DEMDirectories.clear();
+        return ;
+      }
+    }
+
+    if(!m_GeoidFilename.empty()) // still locked
+    { // Should be done from only one thread, and ... once!!
+      assert(! m_tlses.empty()); // Given OpenGeoidFile() implementation
+                                 // (where an instance of DEMHandlerTLS
+                                 // is fetched locally (to be sure there
+                                 // is at least one), we know that
+                                 // !m_GeoidFilename.empty() =>
+                                 // !m_tlses.empty()
+      m_tlses.front()->CreateShiftedDatasetOnce();
+    }
   }
+
   Notify();
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandler::OpenDEMDirectory() END");
 }
 
-bool DEMHandler::OpenGeoidFile(const std::string& geoidFile)
+
+
+bool HasInputProjection(GDALDataset const& gdalds)
 {
-  auto gdalds = static_cast<GDALDataset*>(GDALOpen(geoidFile.c_str(), GA_ReadOnly));
+#if GDAL_VERSION_NUM >= 3000000
+    return gdalds.GetSpatialRef() && ! gdalds.GetSpatialRef()->IsEmpty()
+#else
+    GDALDataset& gdal_ds_for_v2_API=const_cast<GDALDataset&>(gdalds);
+    return gdal_ds_for_v2_API.GetProjectionRef()[0] != '\0'   // strlen(gdalds.GetProjectionRef()) != 0
+#endif
+      ;
+}
+
+bool DEMHandlerTLS::OpenGeoidFile(const std::string& geoidFile)
+{
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandlerTLS::OpenGeoidFile("<<geoidFile<<") --> " << this);
 
+  DEMDetails::DatasetCache gdalds(geoidFile);
   if (!gdalds)
   {
-    otbLogMacro(Warning, << "Cannot open geoid file "<< geoidFile)
+    otbLogMacro(Warning, << "Cannot open geoid file "<< geoidFile);
     return false;
   }
 
-#if GDAL_VERSION_NUM >= 3000000
-  if (!(gdalds->GetSpatialRef()) || gdalds->GetSpatialRef()->IsEmpty())
-#else
-  if (strlen(gdalds->GetProjectionRef()) == 0 )
-#endif
+  if (! HasInputProjection(*gdalds))
   {
-    otbLogMacro(Warning, << "input geoid file "<< geoidFile << " will not be used because it has no input projection.")
+    otbLogMacro(Warning, << "Input geoid file "<< geoidFile << " will not be used because it has no input projection.");
     return false;
   }
 
-  m_GeoidDS.reset(gdalds);
-  m_GeoidFilename = geoidFile;
+  m_GeoidDS = std::move(gdalds);
+
+  return true;
+}
+
+bool DEMHandler::OpenGeoidFile(std::string geoidFile)
+{
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandler::OpenGeoidFile("<<geoidFile<<")");
+
+  // In case the geoid is not valid, we still try to open it for real,
+  // even if the DEMHandlerTLS will not serve to anything...
+  // tls will also serve to call CreateShiftedDatasetOnce()
+  auto & tls = GetHandlerForCurrentThread();
+  (void) tls;
 
-  if(m_Dataset)
+  int nb_success = 0;
   {
-    CreateShiftedDataset();
+    const std::lock_guard<std::mutex> lock(demMutex);
+    for (auto tls : m_tlses)
+    {
+      nb_success += tls->OpenGeoidFile(geoidFile) ? 1 : 0;
+      // if any is true, all should be!
+    }
+  } // <- release lock
+
+  if (nb_success != 0)
+  {
+    m_GeoidFilename = move(geoidFile); // => parameter voluntary taken by value
+    if (! m_DatasetList.empty())
+    { // In that case, we could expect TLS.m_DEMDS to be non-null
+      const std::lock_guard<std::mutex> lock(demMutex);
+      assert(!m_tlses.empty()); //
+      if (! m_DatasetList.empty())
+      {
+        // We just need to create it once, in any thread. The one created
+        // locally being good enough...
+        tls.CreateShiftedDatasetOnce();
+      }
+    }
   }
-  
   Notify();
-  return true;
+  return nb_success != 0;
 }
 
-
-void DEMHandler::CreateShiftedDataset()
+void DEMHandlerTLS::CreateShiftedDatasetOnce() const
 {
+  otbMsgDevMacro(<<std::this_thread::get_id() << " § DEMHandlerTLS::CreateShiftedDatasetOnce() --> " << this);
 
-  // WIP : no data is not handed at the moment
-
-  double geoTransform[6];
-  m_Dataset->GetGeoTransform(geoTransform);
+  assert(m_DEMDS);
+  assert(m_GeoidDS);
+  // WIP : no data is not handled at the moment
 
   // Warp geoid dataset
   auto warpOptions = GDALCreateWarpOptions();;
-  warpOptions->hSrcDS = m_GeoidDS.get();
-  //warpOptions->hDstDS = m_Dataset;
-  warpOptions->eResampleAlg =  GRA_Bilinear;
+  warpOptions->hSrcDS           = m_GeoidDS.get();
+  //warpOptions->hDstDS         = m_Dataset;
+  warpOptions->eResampleAlg     = GRA_Bilinear;
   warpOptions->eWorkingDataType = GDT_Float64;
-  warpOptions->nBandCount = 1;
-    warpOptions->panSrcBands =
-        (int *) CPLMalloc(sizeof(int) * warpOptions->nBandCount );
-    warpOptions->panSrcBands[0] = 1;
-    warpOptions->panDstBands =
-        (int *) CPLMalloc(sizeof(int) * warpOptions->nBandCount );
-    warpOptions->panDstBands[0] = 1;
+  warpOptions->nBandCount       = 1;
+  warpOptions->panSrcBands      = (int *) CPLMalloc(sizeof(int) * warpOptions->nBandCount );
+  warpOptions->panSrcBands[0]   = 1;
+  warpOptions->panDstBands      = (int *) CPLMalloc(sizeof(int) * warpOptions->nBandCount );
+  warpOptions->panDstBands[0]   = 1;
 
   // Establish reprojection transformer.
   warpOptions->pTransformerArg =
-        GDALCreateGenImgProjTransformer( m_GeoidDS.get(),
-                                        GDALGetProjectionRef(m_GeoidDS.get()),
-                                        m_Dataset.get(),
-                                        GDALGetProjectionRef(m_Dataset.get()),
-                                        FALSE, 0.0, 1 );
+    GDALCreateGenImgProjTransformer(
+        m_GeoidDS.get(), GDALGetProjectionRef(m_GeoidDS.get()),
+        m_DEMDS.get(),   GDALGetProjectionRef(m_DEMDS.get()),
+        FALSE, 0.0, 1 );
   warpOptions->pfnTransformer = GDALGenImgProjTransform;
 
   auto ds = static_cast<GDALDataset *> (GDALCreateWarpedVRT(m_GeoidDS.get(),
-                      m_Dataset->GetRasterXSize(), 
-                      m_Dataset->GetRasterYSize(), 
-                      geoTransform, 
-                      warpOptions));
+        m_DEMDS->GetRasterXSize(),
+        m_DEMDS->GetRasterYSize(),
+        const_cast<double*>(m_DEMDS.getGeoTransform()), // not const-correct?
+        warpOptions));
 
-  ds->SetDescription(DEM_WARPED_DATASET_PATH.c_str());
+  ds->SetDescription(DEMHandler::DEM_WARPED_DATASET_PATH);
   GDALClose(ds);
 
   GDALDriver *poDriver = (GDALDriver *) GDALGetDriverByName( "VRT" );
   GDALDataset *poVRTDS;
 
-  poVRTDS = poDriver->Create( DEM_SHIFTED_DATASET_PATH.c_str(), m_Dataset->GetRasterXSize(), m_Dataset->GetRasterYSize(), 0, GDT_Float64, NULL );
+  poVRTDS = poDriver->Create( DEMHandler::DEM_SHIFTED_DATASET_PATH, m_DEMDS->GetRasterXSize(), m_DEMDS->GetRasterYSize(), 0, GDT_Float64, NULL );
 
-  poVRTDS->SetGeoTransform(geoTransform);
+  poVRTDS->SetGeoTransform(const_cast<double*>(m_DEMDS.getGeoTransform()));
 
-  poVRTDS->SetProjection(m_Dataset->GetProjectionRef());
+  poVRTDS->SetProjection(m_DEMDS->GetProjectionRef());
 
-  char** derivedBandOptions = NULL;
+  char** derivedBandOptions = nullptr;
 
   derivedBandOptions = CSLAddNameValue(derivedBandOptions, "subclass", "VRTDerivedRasterBand");
   derivedBandOptions = CSLAddNameValue(derivedBandOptions, "PixelFunctionType", "sum");
@@ -356,92 +777,102 @@ void DEMHandler::CreateShiftedDataset()
 
   GDALRasterBand *poBand = poVRTDS->GetRasterBand( 1 );
 
-// TODO use std string (and boost format ?) or stream
+  // TODO use std string (and boost format ?) or stream
   char demVrtXml[10000];
-
-  sprintf( demVrtXml,
-          "<SimpleSource>"
-          "  <SourceFilename>%s</SourceFilename>"
-          "  <SourceBand>1</SourceBand>"
-          "</SimpleSource>",
-          DEM_DATASET_PATH.c_str());
+  snprintf( demVrtXml, sizeof(demVrtXml),
+      "<SimpleSource>"
+      "  <SourceFilename>%s</SourceFilename>"
+      "  <SourceBand>1</SourceBand>"
+      "</SimpleSource>",
+      DEMHandler::DEM_DATASET_PATH);
 
   poBand->SetMetadataItem( "source_0", demVrtXml, "new_vrt_sources" );
-  
 
   char geoidVrtXml[10000];
-
-  sprintf( geoidVrtXml,
-          "<SimpleSource>"
-          "  <SourceFilename>%s</SourceFilename>"
-          "  <SourceBand>1</SourceBand>"
-          "</SimpleSource>",
-          DEM_WARPED_DATASET_PATH.c_str());
-
+  snprintf( geoidVrtXml, sizeof(geoidVrtXml),
+      "<SimpleSource>"
+      "  <SourceFilename>%s</SourceFilename>"
+      "  <SourceBand>1</SourceBand>"
+      "</SimpleSource>",
+      DEMHandler::DEM_WARPED_DATASET_PATH);
 
   poBand->SetMetadataItem( "source_1", geoidVrtXml, "new_vrt_sources" );
 
   GDALClose(poVRTDS);
 }
 
+boost::optional<double> DEMHandlerTLS::GetHeightAboveMSL(double lon, double lat) const
+{
+  if (m_DEMDS)
+  {
+    return DEMDetails::GetDEMValue(lon, lat, m_DEMDS);
+  }
+  return boost::none;
+}
 
-double DEMHandler::GetHeightAboveEllipsoid(double lon, double lat) const
+boost::optional<double> DEMHandlerTLS::GetGeoidHeight(double lon, double lat) const
 {
-  double result = 0.;
-  boost::optional<double> DEMresult;
-  boost::optional<double> geoidResult;
+  if (m_GeoidDS)
+  {
+    return DEMDetails::GetDEMValue(lon, lat, m_GeoidDS);
+  }
+  return boost::none;
+}
+
+double DEMHandlerTLS::GetHeightAboveEllipsoid(double lon, double lat, double defaultHeight) const
+{
+  boost::optional<double> DEMresult   = GetHeightAboveMSL(lon, lat);
+  boost::optional<double> geoidResult = GetGeoidHeight(lon, lat);
 
-  if (m_Dataset)
+  if (DEMresult || geoidResult)
   {
-    DEMresult = DEMDetails::GetDEMValue(lon, lat, *m_Dataset);
+    double result = 0.;
     if (DEMresult)
     {
       result += *DEMresult;
     }
-  }
 
-
-  if (m_GeoidDS)
-  {
-    geoidResult = DEMDetails::GetDEMValue(lon, lat, *m_GeoidDS);
     if (geoidResult)
     {
       result += *geoidResult;
     }
-  }
-
-  if (DEMresult || geoidResult)
     return result;
+  }
   else
-    return m_DefaultHeightAboveEllipsoid;
+    return defaultHeight;
 }
 
+double DEMHandler::GetHeightAboveEllipsoid(double lon, double lat) const
+{
+  auto & tls = GetHandlerForCurrentThread();
+  return tls.GetHeightAboveEllipsoid(lon, lat, m_DefaultHeightAboveEllipsoid);
+}
 double DEMHandler::GetHeightAboveEllipsoid(const PointType& geoPoint) const
 {
   return GetHeightAboveEllipsoid(geoPoint[0], geoPoint[1]);
 }
 
-double DEMHandler::GetHeightAboveMSL(double lon, double lat) const
+double DEMHandler::GetGeoidHeight(double lon, double lat) const
 {
-  if (m_Dataset)
-  { 
-    auto result = DEMDetails::GetDEMValue(lon, lat, *m_Dataset);
-    
-    if (result)
-    {
-      return *result;
-    }
-  }
-
-  return 0;
+  auto & tls = GetHandlerForCurrentThread();
+  return tls.GetGeoidHeight(lon, lat).value_or(0);
+}
+double DEMHandler::GetGeoidHeight(const PointType& geoPoint) const
+{
+  return GetGeoidHeight(geoPoint[0], geoPoint[1]);
 }
 
+double DEMHandler::GetHeightAboveMSL(double lon, double lat) const
+{
+  auto & tls = GetHandlerForCurrentThread();
+  return tls.GetHeightAboveMSL(lon, lat).value_or(0);
+}
 double DEMHandler::GetHeightAboveMSL(const PointType& geoPoint) const
 {
   return GetHeightAboveMSL(geoPoint[0], geoPoint[1]);
 }
 
-unsigned int DEMHandler::GetDEMCount() const
+std::size_t DEMHandler::GetDEMCount() const noexcept
 {
   return m_DatasetList.size();
 }
@@ -461,41 +892,49 @@ bool DEMHandler::IsValidDEMDirectory(const std::string& DEMDirectory) const
   return false;
 }
 
-std::string DEMHandler::GetDEMDirectory(unsigned int idx) const
+std::string const& DEMHandler::GetDEMDirectory(std::size_t idx) const
 {
   if (idx >= m_DEMDirectories.size())
   {
     std::ostringstream oss;
-    oss << "Requested DEM diectory " << idx
-        << ", but only "<< m_DEMDirectories.size() << "have been set.";
+    oss << "Requested DEM directory " << idx
+      << ", but only "<< m_DEMDirectories.size() << " have been set.";
+    // TODO: std::out_of_range is a logic (i.e. a programming) error =>
+    // use another exception type!
     throw std::out_of_range (oss.str());
   }
-  
+
   return m_DEMDirectories[idx];
-  
 }
 
-std::string DEMHandler::GetGeoidFile() const
+std::string const& DEMHandler::GetGeoidFile() const noexcept
 {
   return m_GeoidFilename;
 }
 
-void DEMHandler::ClearDEMs()
+void DEMHandlerTLS::ClearElevationParameters()
 {
-  m_DEMDirectories.clear();
-
-  // This will call GDALClose on all datasets
-  m_DatasetList.clear();
-  Notify();
+  m_GeoidDS.release();
+  m_DEMDS.release();
 }
 
 void DEMHandler::ClearElevationParameters()
 {
   m_DefaultHeightAboveEllipsoid = 0.;
   m_GeoidFilename.clear();
-  m_GeoidDS.reset(nullptr);
 
-  ClearDEMs(); // ClearDEMs calls Notify()
+  // This will call GDALClose on all datasets
+  m_DatasetList.clear();
+  {
+    const std::lock_guard<std::mutex> lock(demMutex);
+    for (auto tls : m_tlses) {
+      tls->ClearElevationParameters();
+    }
+    m_DEMDirectories.clear();
+  }
+  // TODO: ¿ shouldn't we erase the files as well ?
+
+  Notify();
 }
 
 
@@ -505,7 +944,7 @@ void DEMHandler::SetDefaultHeightAboveEllipsoid(double height)
   Notify();
 }
 
-double DEMHandler::GetDefaultHeightAboveEllipsoid() const
+double DEMHandler::GetDefaultHeightAboveEllipsoid() const noexcept
 {
   return m_DefaultHeightAboveEllipsoid;
 }
@@ -518,4 +957,23 @@ void DEMHandler::Notify() const
   }
 };
 
+
+double GetHeightAboveEllipsoid(DEMHandlerTLS const& tls, double lon, double lat)
+{ return tls.GetHeightAboveEllipsoid(lon, lat, DEMHandler::GetInstance().GetDefaultHeightAboveEllipsoid()); }
+
+double GetHeightAboveMSL      (DEMHandlerTLS const& tls, double lon, double lat)
+{ return tls.GetHeightAboveMSL(lon, lat).value_or(0); }
+
+double GetGeoidHeight         (DEMHandlerTLS const& tls, double lon, double lat)
+{ return tls.GetGeoidHeight(lon, lat).value_or(0); }
+
+double GetHeightAboveEllipsoid(DEMHandlerTLS const& tls, itk::Point<double, 2> geoPoint)
+{ return GetHeightAboveEllipsoid(tls, geoPoint[0], geoPoint[1]); }
+
+double GetHeightAboveMSL      (DEMHandlerTLS const& tls, itk::Point<double, 2> geoPoint)
+{ return GetHeightAboveMSL(tls, geoPoint[0], geoPoint[1]); }
+
+double GetGeoidHeight         (DEMHandlerTLS const& tls, itk::Point<double, 2> geoPoint)
+{ return GetGeoidHeight(tls, geoPoint[0], geoPoint[1]); }
+
 } // namespace otb


=====================================
Modules/IO/IOGDAL/src/otbGDALRPCTransformer.cxx
=====================================
@@ -104,11 +104,11 @@ void GDALRPCTransformer::Update()
     {
       if (demHandler.GetGeoidFile().empty())
       {
-        this->SetOption("RPC_DEM", demHandler.DEM_DATASET_PATH);
+        this->SetOption("RPC_DEM", DEMHandler::DEM_DATASET_PATH);
       }
       else
       {
-        this->SetOption("RPC_DEM", demHandler.DEM_SHIFTED_DATASET_PATH);
+        this->SetOption("RPC_DEM", DEMHandler::DEM_SHIFTED_DATASET_PATH);
       }
       this->SetOption("RPC_DEM_MISSING_VALUE", std::to_string(demHandler.GetDefaultHeightAboveEllipsoid()));
     }


=====================================
Modules/IO/IOGDAL/test/otbGDALRPCTransformerTest.cxx
=====================================
@@ -32,27 +32,27 @@ int otbGDALRPCTransformerTest(int itkNotUsed(argc), char* argv[])
   double LineOffset = 16201.0, SampleOffset = 15184.0,
          LatOffset = 39.7792, LonOffset = 125.7510, HeightOffset = 97.0,
          LineScale = 16480.0, SampleScale = 15217.0,
-		 LatScale = 0.0900, LonScale = 0.1096, HeightScale = 501.0;
+     LatScale = 0.0900, LonScale = 0.1096, HeightScale = 501.0;
   const double line_num_coeff[20] = {
-		  +5.105608E-04, -2.921055E-02, -1.010407E+00, -1.743729E-02, -6.604239E-05,
-		  -7.871396E-05, +3.027877E-04, -4.323587E-04, -2.624751E-04, +6.186490E-06,
-		  +1.084676E-06, +5.389738E-05, +4.145232E-06, +3.911486E-07, +1.772434E-05,
-		  +3.302960E-06, +3.006106E-06, +1.662606E-05, +6.051677E-06, -2.657667E-08};
+      +5.105608E-04, -2.921055E-02, -1.010407E+00, -1.743729E-02, -6.604239E-05,
+      -7.871396E-05, +3.027877E-04, -4.323587E-04, -2.624751E-04, +6.186490E-06,
+      +1.084676E-06, +5.389738E-05, +4.145232E-06, +3.911486E-07, +1.772434E-05,
+      +3.302960E-06, +3.006106E-06, +1.662606E-05, +6.051677E-06, -2.657667E-08};
   const double line_den_coeff[20] = {
-		  +1.000000E+00, -9.652128E-05, +2.488346E-04, +3.089019E-04, -2.120170E-06,
-		  +4.117913E-07,  +1.370009E-06, +1.357281E-05, -4.174324E-06, -3.146787E-06,
-		  -7.724587E-06, +3.524480E-04, -1.303224E-05, -8.507679E-07, -1.670972E-05,
-		  +6.781061E-06, +5.602262E-07, +1.161421E-05, +4.681872E-06, +5.593931E-08};
+      +1.000000E+00, -9.652128E-05, +2.488346E-04, +3.089019E-04, -2.120170E-06,
+      +4.117913E-07,  +1.370009E-06, +1.357281E-05, -4.174324E-06, -3.146787E-06,
+      -7.724587E-06, +3.524480E-04, -1.303224E-05, -8.507679E-07, -1.670972E-05,
+      +6.781061E-06, +5.602262E-07, +1.161421E-05, +4.681872E-06, +5.593931E-08};
   const double samp_num_coeff[20] = {
-		  -2.429563E-04, +1.028320E+00, -3.360972E-02, +3.519600E-03, -6.568341E-04,
-		  +5.951139E-04, -3.875716E-04, +1.260622E-04, -5.273817E-05, -4.418981E-06,
-		  -3.520581E-06, -2.502760E-04, -4.167704E-05, -5.973233E-05, -1.438949E-04,
-		  +7.603041E-06, +2.358136E-06, -2.275274E-05,  +1.602657E-06, -1.716541E-07};
+      -2.429563E-04, +1.028320E+00, -3.360972E-02, +3.519600E-03, -6.568341E-04,
+      +5.951139E-04, -3.875716E-04, +1.260622E-04, -5.273817E-05, -4.418981E-06,
+      -3.520581E-06, -2.502760E-04, -4.167704E-05, -5.973233E-05, -1.438949E-04,
+      +7.603041E-06, +2.358136E-06, -2.275274E-05,  +1.602657E-06, -1.716541E-07};
   const double samp_den_coeff[20] = {
-		  +1.000000E+00, +7.765620E-05, +6.568707E-04, -6.270621E-04, +5.163170E-05,
-		  +6.979463E-06, +2.476334E-07, +1.083558E-04, -4.043734E-05, -5.819288E-05,
-		  +1.778201E-07, +5.665202E-05, +6.927205E-06, +6.793485E-07, +3.604209E-05,
-		  -4.057103E-07, -8.291254E-07, +1.010650E-05, -2.875552E-06, +5.142751E-08};
+      +1.000000E+00, +7.765620E-05, +6.568707E-04, -6.270621E-04, +5.163170E-05,
+      +6.979463E-06, +2.476334E-07, +1.083558E-04, -4.043734E-05, -5.819288E-05,
+      +1.778201E-07, +5.665202E-05, +6.927205E-06, +6.793485E-07, +3.604209E-05,
+      -4.057103E-07, -8.291254E-07, +1.010650E-05, -2.875552E-06, +5.142751E-08};
 
   otb::GDALRPCTransformer transformer(LineOffset, SampleOffset, LatOffset, LonOffset, HeightOffset,
                                       LineScale, SampleScale, LatScale, LonScale, HeightScale,
@@ -165,7 +165,7 @@ int otbGDALRPCTransformerTest(int itkNotUsed(argc), char* argv[])
   zePoint[2] = 0.0;
   exp_x = 125.64828521533849;
   exp_y = 39.869345204440144;
-  transformer.SetOption("RPC_DEM", dem.DEM_DATASET_PATH);
+  transformer.SetOption("RPC_DEM", otb::DEMHandler::DEM_DATASET_PATH);
   transformer.SetOption("RPC_HEIGHT_SCALE", "2");
 
   std::cout << "Test ForwardTransform with DEM." << '\n';


=====================================
Modules/Registration/Stereo/include/otbStereorectificationDisplacementFieldSource.hxx
=====================================
@@ -296,6 +296,7 @@ void StereorectificationDisplacementFieldSource<TInputImage, TOutputImage>::Gene
 
   // Setup the DEM handler if needed
   auto & demHandler = DEMHandler::GetInstance();
+  auto const& threadDemHandler = demHandler.GetHandlerForCurrentThread();
 
   // Set-up a transform to use the DEMHandler
   typedef otb::GenericRSTransform<> RSTransform2DType;
@@ -463,7 +464,7 @@ void StereorectificationDisplacementFieldSource<TInputImage, TOutputImage>::Gene
       RSTransform2DType::InputPointType tmpPoint;
       tmpPoint[0]    = currentPoint1[0];
       tmpPoint[1]    = currentPoint1[1];
-      localElevation = demHandler.GetHeightAboveEllipsoid(leftToGroundTransform->TransformPoint(tmpPoint));
+      localElevation = GetHeightAboveEllipsoid(threadDemHandler, leftToGroundTransform->TransformPoint(tmpPoint));
     }
     currentPoint1[2] = localElevation;
 
@@ -489,7 +490,7 @@ void StereorectificationDisplacementFieldSource<TInputImage, TOutputImage>::Gene
         RSTransform2DType::InputPointType tmpPoint;
         tmpPoint[0]       = nextLineStart1[0];
         tmpPoint[1]       = nextLineStart1[1];
-        nextLineStart1[2] = demHandler.GetHeightAboveEllipsoid(leftToGroundTransform->TransformPoint(tmpPoint));
+        nextLineStart1[2] = GetHeightAboveEllipsoid(threadDemHandler, leftToGroundTransform->TransformPoint(tmpPoint));
       }
 
 


=====================================
RELEASE_NOTES.txt
=====================================
@@ -1,3 +1,14 @@
+OTB-v8.1.1 - Changes since version 8.1.0 (Wednesday 13th, 2023)
+
+Bugs fixed:
+  * !933: Improve DEMHandler performances which fixes #2307 Spatial shifting with Orthorectifcation of Sentinel 1 products by Luc Hermitte
+
+Documentation:
+  * !944: Update OTB-Data link in the documentation by Thibaut ROMAIN
+
+CI:
+  * Update ZLIB to 1.2.13 by Thibaut ROMAIN (backport from !902)
+
 OTB-v 8.1.0 - Changes since version 8.0.1 (September 14th, 2022)
 ---------------------------------------------------------------------
 



View it on GitLab: https://salsa.debian.org/debian-gis-team/otb/-/commit/f53878cd60c1e96c099c1d61d89ddf802ce4a077

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/otb/-/commit/f53878cd60c1e96c099c1d61d89ddf802ce4a077
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20230113/631b75f1/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list