diff --git a/Documentation/SoftwareGuide/Latex/OrthoRectification.tex b/Documentation/SoftwareGuide/Latex/OrthoRectification.tex
index f372d4eb5269db49b1f96e341434f7b1a6ea4d55..6d1354cdfc839d926ef206bca9f715723da8cebb 100644
--- a/Documentation/SoftwareGuide/Latex/OrthoRectification.tex
+++ b/Documentation/SoftwareGuide/Latex/OrthoRectification.tex
@@ -158,20 +158,28 @@ OTB, through the use of the OSSIM library --
 sensors either through a physical or an analytical approach. This is
 transparent for the user, since the geometrical model for a given
 image is instantiated using the information stored in its meta-data. The 
-search for a sensor model is not straightforward. It is done in 3 steps :
+search for a sensor model is not straightforward. It is done in several steps :
 \begin{enumerate}
+  \item Load an external \code{.geom} file specified through extended filenames
+(if present)
+  \item Load the \code{.geom} file attached with the input image (if present).
+They share the same name, without extension.
   \item Search in the OSSIM plugin factory for a suitable model 
 (\code{ossimplugins::ossimPluginProjectionFactory}). For instance, this
 factory contains Pl\'eiades and TerraSar sensor models.
   \item If no model was found, search in the OSSIM projection factory 
 (\code{ossimProjectionFactoryRegistry}). For instance this factory contains
 Spot5, Landsat and Quickbird sensor models.
-  \item If still no model was found, search for a valid sensor model defined
- in an external \code{.geom} file. If no model is found, check if there are 
-any RPC tags embedded within the image (GDAL is used to detect those RPC 
-tags). When the tags are present, an \code{ossimRpcModel} is created.
+  \item If no model was found, search any RPC tags in the input image. When the
+tags are present, an \code{ossimRpcModel} is created.
+  \item If still no model was found, search for a valid sensor model in other
+files attached to the current dataset. For instance, with a Sentinel-1 SAFE XML
+product, it will inspect underlying \code{.tiff} files. With a VRT dataset, it
+will inspect the files referenced by the VRT.
 \end{enumerate}
 
+Note that the \code{.geom} metadata file can store any sensor model recognized
+by OSSIM.
 
 \subsection{Using Sensor Models}
 \label{sec:UsingSensorModels}
diff --git a/Modules/Core/ImageBase/include/otbImageIOBase.h b/Modules/Core/ImageBase/include/otbImageIOBase.h
index ecadb4f00813fa85561d4f75962d3af413b89810..09303fb5bbce2b2fa2e704f38ac311a8e7eb5515 100644
--- a/Modules/Core/ImageBase/include/otbImageIOBase.h
+++ b/Modules/Core/ImageBase/include/otbImageIOBase.h
@@ -429,6 +429,9 @@ public:
    * conversion)*/
   void DoMapBuffer(void* buffer, size_t numberOfPixels, std::vector<unsigned int>& bandList);
 
+  /** Returns a const ref to the list of attached files*/
+  itkGetConstReferenceMacro(AttachedFileNames, std::vector<std::string> );
+
 protected:
   ImageIOBase();
   ~ImageIOBase() override;
@@ -550,6 +553,9 @@ protected:
                                                                unsigned int numberOfActualSplits,
                                                                const itk::ImageIORegion &pasteRegion) const;
 
+  /** List of files part of the same dataset as the input filename */
+  std::vector<std::string> m_AttachedFileNames;
+
 private:
   ImageIOBase(const Self&) = delete;
   void operator=(const Self&) = delete;
diff --git a/Modules/IO/IOGDAL/src/otbGDALImageIO.cxx b/Modules/IO/IOGDAL/src/otbGDALImageIO.cxx
index 439fd0e106ae427d7d082b496ca8828eff57916e..e800d8f4f0c47fc3e65c2eb1a4e89975c1904350 100644
--- a/Modules/IO/IOGDAL/src/otbGDALImageIO.cxx
+++ b/Modules/IO/IOGDAL/src/otbGDALImageIO.cxx
@@ -435,7 +435,7 @@ void GDALImageIO::InternalReadImageInformation()
   // supported gdal format using the m_DatasetNumber value
   // HDF4_SDS:UNKNOWN:"myfile.hdf":2
   // and make m_Dataset point to it.
-  if (m_Dataset->GetDataSet()->GetRasterCount() == 0)
+  if (m_Dataset->GetDataSet()->GetRasterCount() == 0 || m_DatasetNumber > 0)
     {
     // this happen in the case of a hdf file with SUBDATASETS
     // Note: we assume that the datasets are in order
@@ -659,6 +659,24 @@ void GDALImageIO::InternalReadImageInformation()
       }
     }
 
+  // get list of other files part of the same dataset
+  char** datasetFileList = dataset->GetFileList();
+  m_AttachedFileNames.clear();
+  if (datasetFileList != nullptr)
+    {
+    char** currentFile = datasetFileList;
+    while (*currentFile != nullptr)
+      {
+      if (m_FileName.compare(*currentFile) != 0)
+        {
+        m_AttachedFileNames.emplace_back(*currentFile);
+        otbLogMacro(Debug,<<"Found attached file : "<< *currentFile);
+        }
+      currentFile++;
+      }
+    CSLDestroy(datasetFileList);
+    }
+
   /*----------------------------------------------------------------------*/
   /*-------------------------- METADATA ----------------------------------*/
   /*----------------------------------------------------------------------*/
diff --git a/Modules/IO/ImageIO/include/otbImageFileReader.hxx b/Modules/IO/ImageIO/include/otbImageFileReader.hxx
index c26730b0058a322f728ad905c540c7e571796b5b..9e3215277e01a6b92d55cfe398fff4d95a28c9a6 100644
--- a/Modules/IO/ImageIO/include/otbImageFileReader.hxx
+++ b/Modules/IO/ImageIO/include/otbImageFileReader.hxx
@@ -433,7 +433,20 @@ ImageFileReader<TOutputImage, ConvertPixelTraits>
         }
       else
         {
-        otbLogMacro(Info,<< "No kwl metadata found in file "<<lFileNameOssimKeywordlist);
+        // Try attached files
+        for (const std::string& path : m_ImageIO->GetAttachedFileNames())
+          {
+          otb_kwl = ReadGeometryFromImage(path,!m_FilenameHelper->GetSkipRpcTag());
+          if(!otb_kwl.Empty())
+            {
+            otbLogMacro(Info,<< "Loading kwl metadata in attached file "<<path);
+            break;
+            }
+          }
+        if (otb_kwl.Empty())
+          {
+          otbLogMacro(Info,<< "No kwl metadata found in file "<<lFileNameOssimKeywordlist);
+          }
         }
       }
 
diff --git a/Modules/IO/ImageIO/test/CMakeLists.txt b/Modules/IO/ImageIO/test/CMakeLists.txt
index 59b02a6d60000b595ee5f3bbb6bf94356f40541c..f6cbe8fb68c8d9689ed167a1346198265da57de9 100644
--- a/Modules/IO/ImageIO/test/CMakeLists.txt
+++ b/Modules/IO/ImageIO/test/CMakeLists.txt
@@ -75,6 +75,7 @@ otbCompareWritingComplexImage.cxx
 otbImageFileReaderOptBandTest.cxx
 otbImageFileWriterOptBandTest.cxx
 otbMultiImageFileWriterTest.cxx
+otbWriteGeomFile.cxx
 )
 
 add_executable(otbImageIOTestDriver ${OTBImageIOTests})
@@ -1357,3 +1358,12 @@ otb_add_test(NAME ioTvMultiImageFileWriter_DiffSize
   ${TEMP}/ioTvMultiImageFileWriter_DiffSize1.tif
   ${TEMP}/ioTvMultiImageFileWriter_DiffSize2.tif
   25)
+
+otb_add_test(NAME ioTvCompoundMetadataReaderTest
+  COMMAND otbImageIOTestDriver
+  --compare-ascii ${EPSILON_9}
+  ${INPUTDATA}/QB_Toulouse_SensorModel_labelImage.geom
+  ${TEMP}/ioTvCompoundMetadataReaderTest.geom
+  otbWriteGeomFile
+  ${INPUTDATA}/QB_Toulouse_combo.vrt
+  ${TEMP}/ioTvCompoundMetadataReaderTest.tif)
diff --git a/Modules/IO/ImageIO/test/otbImageIOTestDriver.cxx b/Modules/IO/ImageIO/test/otbImageIOTestDriver.cxx
index 0dab5832434baab0f12df11545fcd93e348858ac..1d59f88e8a8ba93e3616e1efd30f644bfe4ce0db 100644
--- a/Modules/IO/ImageIO/test/otbImageIOTestDriver.cxx
+++ b/Modules/IO/ImageIO/test/otbImageIOTestDriver.cxx
@@ -152,4 +152,5 @@ void RegisterTests()
   REGISTER_TEST(otbImageFileReaderOptBandTest);
   REGISTER_TEST(otbImageFileWriterOptBandTest);
   REGISTER_TEST(otbMultiImageFileWriterTest);
+  REGISTER_TEST(otbWriteGeomFile);
 }
diff --git a/Modules/IO/ImageIO/test/otbWriteGeomFile.cxx b/Modules/IO/ImageIO/test/otbWriteGeomFile.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5ccf3cb985cb3a7eea1adcb64b9ae6a3108fc46b
--- /dev/null
+++ b/Modules/IO/ImageIO/test/otbWriteGeomFile.cxx
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2005-2017 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.
+ */
+
+#include "otbVectorImage.h"
+#include "itkMacro.h"
+#include <iostream>
+
+#include "otbImageFileReader.h"
+
+int otbWriteGeomFile(int itkNotUsed(argc), char* argv[])
+{
+
+  // Verify the number of parameters in the command line
+  std::string inputFilename(argv[1]);
+  std::string outputFilename(argv[2]);
+
+  typedef double InputPixelType;
+  const unsigned int Dimension = 2;
+
+  typedef otb::VectorImage<InputPixelType,  Dimension> InputImageType;
+  typedef otb::ImageFileReader<InputImageType>  ReaderType;
+
+  ReaderType::Pointer reader = ReaderType::New();
+  reader->SetFileName(inputFilename);
+  reader->UpdateOutputInformation();
+
+  otb::ImageKeywordlist otb_kwl;
+  itk::MetaDataDictionary &dict = reader->GetOutput()->GetMetaDataDictionary();
+  itk::ExposeMetaData<otb::ImageKeywordlist>(dict, otb::MetaDataKey::OSSIMKeywordlistKey, otb_kwl);
+  if (otb_kwl.Empty())
+    {
+    return EXIT_FAILURE;
+    }
+
+  otb::WriteGeometry(otb_kwl, outputFilename);
+
+  return EXIT_SUCCESS;
+}