From 559ae0d2ca22028a2f8a89d5eddb3f2bbed26290 Mon Sep 17 00:00:00 2001
From: "arnaud.morvan@camptocamp.com" <arnaud.morvan@camptocamp.com>
Date: Mon, 9 Nov 2020 14:10:10 +0100
Subject: [PATCH] Add algorithm ImportLayerFromDEM

---
 PreCourlis/PreCourlis.py                      |   4 +
 PreCourlis/core/precourlis_file.py            |  44 ++++--
 PreCourlis/i18n/fr.ts                         |  46 +++++-
 .../import_layer_from_dem_algorithm.py        | 132 ++++++++++++++++++
 PreCourlis/processing/interpolate_lines.py    |   6 +-
 PreCourlis/processing/precourlis_provider.py  |   4 +
 test/data/expected/import_layer_from_dem.gml  | 116 +++++++++++++++
 test/data/expected/import_layer_from_dem.xsd  |  96 +++++++++++++
 test/processing/test_import_layer_from_dem.py |  20 +++
 9 files changed, 450 insertions(+), 18 deletions(-)
 create mode 100644 PreCourlis/processing/import_layer_from_dem_algorithm.py
 create mode 100644 test/data/expected/import_layer_from_dem.gml
 create mode 100644 test/data/expected/import_layer_from_dem.xsd
 create mode 100644 test/processing/test_import_layer_from_dem.py

diff --git a/PreCourlis/PreCourlis.py b/PreCourlis/PreCourlis.py
index 8c407f6..ff5cb17 100644
--- a/PreCourlis/PreCourlis.py
+++ b/PreCourlis/PreCourlis.py
@@ -108,6 +108,7 @@ class PreCourlisPlugin:
         # self.add_action("Projeter un semis de point sur les profils", self.projZProfil)
         # self.add_action("Projeter les berges", self.projAxeBerge)
         self.add_action("Interpoler des profils", self.interpolate_profiles)
+        self.add_action("Ajouter une couche depuis un MNT", self.import_layer_from_dem)
         # self.add_action("A propos", slot, QIcon(":/plugins/precourlis/icon.png"))
         self.add_action("Exporter un fichier de géométrie Courlis", self.export_courlis)
         self.add_action(
@@ -183,6 +184,9 @@ class PreCourlisPlugin:
             self.profile_dialog.destroyed.connect(self.profile_dialog_destroyed)
         self.profile_dialog.show()
 
+    def import_layer_from_dem(self):
+        execAlgorithmDialog("precourlis:import_layer_from_dem")
+
     def profile_dialog_destroyed(self):
         self.profile_dialog.graphWidget.close_figure()
         self.profile_dialog = None
diff --git a/PreCourlis/core/precourlis_file.py b/PreCourlis/core/precourlis_file.py
index 8aed826..e78624f 100644
--- a/PreCourlis/core/precourlis_file.py
+++ b/PreCourlis/core/precourlis_file.py
@@ -184,7 +184,16 @@ class PreCourlisFileLine(PreCourlisFileBase):
         else:
             self._layer.setCustomProperty(key, color_to_hex(color))
 
-    def add_sedimental_layer(self, name, from_layer, deltaz=0):
+    def add_sedimental_layer(self, name, from_layer=None, deltaz=0):
+        """
+        Add new sedimental layer.
+        If from_layer is None:
+            - value is calculated from geometry Z value
+            - layer is appended at the end of existing layers
+        If from_layer is not None:
+            - value = value from layer_name + delta
+            - layer is inserted before or after layer_named depending on delta
+        """
         layers = self.layers()
         if self._layer.fields().indexFromName(name) != -1:
             raise KeyError("Field {} already exists".format(name))
@@ -197,29 +206,36 @@ class PreCourlisFileLine(PreCourlisFileBase):
         self._layer.updateFields()
 
         # Update layers list and set value of new attributes
-        if from_layer == "zfond":
-            from_layer_index = -1
-            if deltaz > 0:
-                raise ValueError("Impossible to add layer on top of zfond")
+        if from_layer is None:
+            new_layer_index = len(layers)
         else:
-            from_layer_index = layers.index(from_layer)
-        new_layer_index = from_layer_index if deltaz > 0 else from_layer_index + 1
+            if from_layer == "zfond":
+                from_layer_index = -1
+                if deltaz > 0:
+                    raise ValueError("Impossible to add layer on top of zfond")
+            else:
+                from_layer_index = layers.index(from_layer)
+            new_layer_index = from_layer_index if deltaz > 0 else from_layer_index + 1
+            source_field_index = self._layer.fields().indexFromName(from_layer)
 
         layers.insert(new_layer_index, name)
         layers_list = ",".join(layers)
 
         layers_field_index = self._layer.fields().indexFromName("layers")
-        source_field_index = self._layer.fields().indexFromName(from_layer)
         dest_field_index = self._layer.fields().indexFromName(name)
 
         for f in self._layer.getFeatures():
             self._layer.changeAttributeValue(f.id(), layers_field_index, layers_list)
-            value = f.attribute(source_field_index)
-            if not is_null(value):
-                values = value.split(",")
-                value = ",".join(
-                    [str(v if v == "NULL" else float(v) + deltaz) for v in values]
-                )
+            if from_layer is None:
+                line = next(f.geometry().constParts()).clone()
+                value = ",".join([str(p.z()) for p in line.points()])
+            else:
+                value = f.attribute(source_field_index)
+                if not is_null(value):
+                    values = value.split(",")
+                    value = ",".join(
+                        [str(v if v == "NULL" else float(v) + deltaz) for v in values]
+                    )
             self._layer.changeAttributeValue(f.id(), dest_field_index, value)
 
         self._layer.endEditCommand()
diff --git a/PreCourlis/i18n/fr.ts b/PreCourlis/i18n/fr.ts
index 50bdd29..7e6fb27 100644
--- a/PreCourlis/i18n/fr.ts
+++ b/PreCourlis/i18n/fr.ts
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS><TS version="2.0" language="fr_FR" sourcelanguage="">
+<!DOCTYPE TS>
+<TS version="2.1" language="fr_FR">
 <context>
     <name>ExportCourlisAlgorithm</name>
     <message>
@@ -89,6 +90,44 @@
         <translation>Système de projection</translation>
     </message>
 </context>
+<context>
+    <name>ImportLayerFromDemAlgorithm</name>
+    <message>
+        <location filename="../processing/import_layer_from_dem_algorithm.py" line="33"/>
+        <source>Layer name</source>
+        <translation>Nom de la couche</translation>
+    </message>
+    <message>
+        <location filename="../processing/import_layer_from_dem_algorithm.py" line="38"/>
+        <source>Digital Elevation Model</source>
+        <translation>Modèle numérique de terrain</translation>
+    </message>
+    <message>
+        <location filename="../processing/import_layer_from_dem_algorithm.py" line="43"/>
+        <source>Band number</source>
+        <translation>Numéro de la bande</translation>
+    </message>
+    <message>
+        <location filename="../processing/import_layer_from_dem_algorithm.py" line="51"/>
+        <source>Layer added</source>
+        <translation>Coouche ajoutée</translation>
+    </message>
+    <message>
+        <location filename="../processing/import_layer_from_dem_algorithm.py" line="119"/>
+        <source>Import layer from DEM</source>
+        <translation>Importer une couche depuis un MNT</translation>
+    </message>
+    <message>
+        <location filename="../processing/import_layer_from_dem_algorithm.py" line="122"/>
+        <source>Import</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../processing/import_layer_from_dem_algorithm.py" line="25"/>
+        <source>Input</source>
+        <translation>Entrée</translation>
+    </message>
+</context>
 <context>
     <name>ImportTracksAlgorithm</name>
     <message>
@@ -325,7 +364,7 @@
 <context>
     <name>PreCourlisProvider</name>
     <message>
-        <location filename="../processing/precourlis_provider.py" line="84"/>
+        <location filename="../processing/precourlis_provider.py" line="88"/>
         <source>PreCourlis</source>
         <translation></translation>
     </message>
@@ -378,7 +417,8 @@ Errors: {}
 </source>
         <translation>Impossible d&apos;enregistrer les modifications à la couche {}
 
-Erreurs: {}</translation>
+Erreurs: {}
+</translation>
     </message>
 </context>
 <context>
diff --git a/PreCourlis/processing/import_layer_from_dem_algorithm.py b/PreCourlis/processing/import_layer_from_dem_algorithm.py
new file mode 100644
index 0000000..43381c7
--- /dev/null
+++ b/PreCourlis/processing/import_layer_from_dem_algorithm.py
@@ -0,0 +1,132 @@
+from qgis.core import (
+    QgsProcessing,
+    QgsProcessingMultiStepFeedback,
+    QgsProcessingOutputLayerDefinition,
+    QgsProcessingParameterBand,
+    QgsProcessingParameterFeatureSink,
+    QgsProcessingParameterFeatureSource,
+    QgsProcessingParameterRasterLayer,
+    QgsProcessingParameterString,
+)
+import processing
+
+from PreCourlis.core.precourlis_file import PreCourlisFileLine
+from PreCourlis.processing.precourlis_algorithm import PreCourlisAlgorithm
+
+
+class ImportLayerFromDemAlgorithm(PreCourlisAlgorithm):
+
+    INPUT = "INPUT"
+    LAYER_NAME = "LAYER_NAME"
+    DEM = "DEM"
+    BAND = "BAND"
+    OUTPUT = "OUTPUT"
+
+    def initAlgorithm(self, config=None):
+        self.addParameter(
+            QgsProcessingParameterFeatureSource(
+                self.INPUT,
+                self.tr("Input"),
+                types=[QgsProcessing.TypeVectorLine],
+                defaultValue=None,
+            )
+        )
+        self.addParameter(
+            QgsProcessingParameterString(
+                self.LAYER_NAME, self.tr("Layer name"), defaultValue=None
+            )
+        )
+        self.addParameter(
+            QgsProcessingParameterRasterLayer(
+                self.DEM, self.tr("Digital Elevation Model"), defaultValue=None
+            )
+        )
+        self.addParameter(
+            QgsProcessingParameterBand(
+                self.BAND,
+                self.tr("Band number"),
+                1,
+                "RASTER",
+            )
+        )
+        self.addParameter(
+            QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr("Layer added"))
+        )
+
+    def processAlgorithm(self, parameters, context, model_feedback):
+        feedback = QgsProcessingMultiStepFeedback(2, model_feedback)
+        results = {}
+        outputs = {}
+
+        source = self.parameterAsSource(parameters, self.INPUT, context)
+
+        # setzfromraster
+        alg_params = {
+            "INPUT": parameters[self.INPUT],
+            "RASTER": parameters[self.DEM],
+            "BAND": parameters[self.BAND],
+            "NODATA": 999999,
+            "SCALE": 1,
+            "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
+        }
+        outputs["SetZFromRaster"] = processing.run(
+            "native:setzfromraster",
+            alg_params,
+            context=context,
+            feedback=feedback,
+            is_child_algorithm=True,
+        )
+        current = outputs["SetZFromRaster"]["OUTPUT"]
+
+        feedback.setCurrentStep(1)
+        if feedback.isCanceled():
+            return {}
+
+        layer_name = self.parameterAsString(parameters, self.LAYER_NAME, context)
+
+        # temporary alter the output of setzfromraster
+        layer = context.getMapLayer(current)
+        assert layer.isValid()
+        file = PreCourlisFileLine(layer)
+        assert layer.startEditing()
+        file.add_sedimental_layer(layer_name)
+
+        feedback.setCurrentStep(2)
+        if feedback.isCanceled():
+            return {}
+
+        output = QgsProcessingOutputLayerDefinition(parameters[self.OUTPUT])
+        output.destinationName = self.tr("Layer added")
+
+        # assignprojection (save layer to specified output)
+        alg_params = {
+            "INPUT": current,
+            "CRS": source.sourceCrs(),
+            "OUTPUT": output,
+        }
+        outputs["AssignProjection"] = processing.run(
+            "native:assignprojection",
+            alg_params,
+            context=context,
+            feedback=feedback,
+            is_child_algorithm=True,
+        )
+        current = outputs["AssignProjection"]["OUTPUT"]
+
+        results["OUTPUT"] = current
+        return results
+
+    def name(self):
+        return "import_layer_from_dem"
+
+    def displayName(self):
+        return self.tr("Import layer from DEM")
+
+    def group(self):
+        return self.tr("Import")
+
+    def groupId(self):
+        return "Import"
+
+    def createInstance(self):
+        return ImportLayerFromDemAlgorithm()
diff --git a/PreCourlis/processing/interpolate_lines.py b/PreCourlis/processing/interpolate_lines.py
index 5df1521..7dad2b9 100644
--- a/PreCourlis/processing/interpolate_lines.py
+++ b/PreCourlis/processing/interpolate_lines.py
@@ -2,6 +2,7 @@ from qgis.core import (
     QgsProcessing,
     QgsFeatureRequest,
     QgsProcessingMultiStepFeedback,
+    QgsProcessingOutputLayerDefinition,
     QgsProcessingParameterFeatureSink,
     QgsProcessingParameterFeatureSource,
     QgsProcessingParameterField,
@@ -156,6 +157,9 @@ class InterpolateLinesAlgorithm(PreCourlisAlgorithm):
         if feedback.isCanceled():
             return {}
 
+        output = QgsProcessingOutputLayerDefinition(parameters[self.OUTPUT])
+        output.destinationName = self.tr("Interpolated")
+
         # Points to lines
         alg_params = {
             "INPUT": current,
@@ -163,7 +167,7 @@ class InterpolateLinesAlgorithm(PreCourlisAlgorithm):
             "FIRST_SECTION_ABS_LONG": first_abs_long,
             "GROUP_FIELD": "abs_long",
             "ORDER_FIELD": "p_id",
-            "OUTPUT": parameters[self.OUTPUT],
+            "OUTPUT": output,
         }
         outputs["PointsToLines"] = processing.run(
             "precourlis:points_to_lines",
diff --git a/PreCourlis/processing/precourlis_provider.py b/PreCourlis/processing/precourlis_provider.py
index c3bfa3b..eb47cf2 100644
--- a/PreCourlis/processing/precourlis_provider.py
+++ b/PreCourlis/processing/precourlis_provider.py
@@ -4,6 +4,9 @@ from qgis.core import QgsProcessingProvider
 from qgis.PyQt.QtGui import QIcon
 from processing.gui.RenderingStyles import RenderingStyles
 
+from PreCourlis.processing.import_layer_from_dem_algorithm import (
+    ImportLayerFromDemAlgorithm,
+)
 from PreCourlis.processing.export_courlis_algorithm import ExportCourlisAlgorithm
 from PreCourlis.processing.export_mascaret_algorithm import ExportMascaretAlgorithm
 from PreCourlis.processing.import_georef_algorithm import ImportGeorefAlgorithm
@@ -40,6 +43,7 @@ class PreCourlisProvider(QgsProcessingProvider):
         self.addAlgorithm(ExportCourlisAlgorithm())
         self.addAlgorithm(ExportMascaretAlgorithm())
         self.addAlgorithm(ImportGeorefAlgorithm())
+        self.addAlgorithm(ImportLayerFromDemAlgorithm())
         self.addAlgorithm(ImportTracksAlgorithm())
         self.addAlgorithm(InterpolateLinesAlgorithm())
         self.addAlgorithm(InterpolatePointsAlgorithm())
diff --git a/test/data/expected/import_layer_from_dem.gml b/test/data/expected/import_layer_from_dem.gml
new file mode 100644
index 0000000..9dd6231
--- /dev/null
+++ b/test/data/expected/import_layer_from_dem.gml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<ogr:FeatureCollection
+     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+     xsi:schemaLocation="http://ogr.maptools.org/ import_layer_from_dem.xsd"
+     xmlns:ogr="http://ogr.maptools.org/"
+     xmlns:gml="http://www.opengis.net/gml">
+  <gml:boundedBy>
+    <gml:Box>
+      <gml:coord><gml:X>425952.0061349694</gml:X><gml:Y>244648.444785276</gml:Y><gml:Z>7.900692939758301</gml:Z></gml:coord>
+      <gml:coord><gml:X>427870.6932515338</gml:X><gml:Y>247351.9559847028</gml:Y><gml:Z>999999</gml:Z></gml:coord>
+    </gml:Box>
+  </gml:boundedBy>
+                                            
+  <gml:featureMember>
+    <ogr:import_layer_from_dem fid="import_layer_from_dem.0">
+      <ogr:geometryProperty><gml:LineString srsName="EPSG:27563"><gml:coordinates>427843.745398773,244648.444785276,22.4119033813477 427847.113880368,244745.457055215,22.0043601989746 427850.482361963,244842.469325153,21.6008014678955 427853.850843558,244939.481595092,21.2532844543457 427857.219325153,245036.493865031,11.600076675415 427860.587806749,245133.506134969,11.143404006958 427863.956288344,245230.518404908,17.2182998657227 427867.324769939,245327.530674847,17.0419750213623 427870.693251534,245424.542944785,12.8960342407227</gml:coordinates></gml:LineString></ogr:geometryProperty>
+      <ogr:sec_id>1</ogr:sec_id>
+      <ogr:sec_name>P1</ogr:sec_name>
+      <ogr:abs_long>0</ogr:abs_long>
+      <ogr:axis_x>427858.100665553</ogr:axis_x>
+      <ogr:axis_y>245061.876468527</ogr:axis_y>
+      <ogr:layers>Layer1,from_dem</ogr:layers>
+      <ogr:p_id>1,2,3,4,5,6,7,8,9</ogr:p_id>
+      <ogr:topo_bat>B,B,B,B,B,B,B,B,B</ogr:topo_bat>
+      <ogr:abs_lat>0.0,97.07073290599445,194.1414658119909,291.21219871798536,388.28293162395073,485.3536645299472,582.4243974359416,679.4951303419381,776.5658632479325</ogr:abs_lat>
+      <ogr:zfond>22.411903381347656,22.00436019897461,21.600801467895508,21.253284454345703,11.600076675415039,11.143404006958008,17.218299865722656,17.041975021362305,12.896034240722656</ogr:zfond>
+      <ogr:Layer1>21.411903381347656,21.00436019897461,20.600801467895508,20.253284454345703,10.600076675415039,10.143404006958008,16.218299865722656,16.041975021362305,11.896034240722656</ogr:Layer1>
+      <ogr:from_dem>22.411903381347656,22.00436019897461,21.600801467895508,21.253284454345703,11.600076675415039,11.143404006958008,17.218299865722656,17.041975021362305,12.896034240722656</ogr:from_dem>
+    </ogr:import_layer_from_dem>
+  </gml:featureMember>
+  <gml:featureMember>
+    <ogr:import_layer_from_dem fid="import_layer_from_dem.1">
+      <ogr:geometryProperty><gml:LineString srsName="EPSG:27563"><gml:coordinates>427353.294478528,244691.561349693,19.5327396392822 427369.463190184,244783.95398773,19.4982223510742 427385.631901841,244876.346625767,19.554515838623 427401.800613497,244968.739263804,11.5093402862549 427417.969325153,245061.13190184,11.6000442504883 427434.13803681,245153.524539877,14.0178060531616 427450.306748466,245245.917177914,14.5559692382812 427466.475460123,245338.309815951,12.6259002685547</gml:coordinates></gml:LineString></ogr:geometryProperty>
+      <ogr:sec_id>2</ogr:sec_id>
+      <ogr:sec_name>P2</ogr:sec_name>
+      <ogr:abs_long>451.308571519184</ogr:abs_long>
+      <ogr:axis_x>427409.701837606</ogr:axis_x>
+      <ogr:axis_y>245013.889115853</ogr:axis_y>
+      <ogr:layers>Layer1,from_dem</ogr:layers>
+      <ogr:p_id>1,2,3,4,5,6,7,8</ogr:p_id>
+      <ogr:topo_bat>B,B,B,B,B,B,B,B</ogr:topo_bat>
+      <ogr:abs_lat>0.0,93.7967312864825,187.59346257297503,281.39019385945755,375.1869251459501,468.9836564324326,562.7803877189251,656.5771190054077</ogr:abs_lat>
+      <ogr:zfond>19.532739639282227,19.49822235107422,19.554515838623047,11.509340286254883,11.600044250488281,14.017806053161621,14.55596923828125,12.625900268554688</ogr:zfond>
+      <ogr:Layer1>18.532739639282227,18.49822235107422,18.554515838623047,10.509340286254883,10.600044250488281,13.017806053161621,13.55596923828125,11.625900268554688</ogr:Layer1>
+      <ogr:from_dem>19.532739639282227,19.49822235107422,19.554515838623047,11.509340286254883,11.600044250488281,14.017806053161621,14.55596923828125,12.625900268554688</ogr:from_dem>
+    </ogr:import_layer_from_dem>
+  </gml:featureMember>
+  <gml:featureMember>
+    <ogr:import_layer_from_dem fid="import_layer_from_dem.2">
+      <ogr:geometryProperty><gml:LineString srsName="EPSG:27563"><gml:coordinates>426641.871165644,244934.09202454,22.1308994293213 426706.54601227,245007.749488753,21.0386238098145 426771.220858896,245081.406952965,20.0380458831787 426835.895705522,245155.064417178,11.4290409088135 426850.71702454,245241.297546012,18.1597900390625 426865.538343558,245327.530674847,19.3317260742188 426880.359662577,245413.763803681,18.6849765777588 426895.180981595,245499.996932515,17.5910263061523</gml:coordinates></gml:LineString></ogr:geometryProperty>
+      <ogr:sec_id>3</ogr:sec_id>
+      <ogr:sec_name>P3</ogr:sec_name>
+      <ogr:abs_long>1063.86595130483</ogr:abs_long>
+      <ogr:axis_x>426836.804290604</ogr:axis_x>
+      <ogr:axis_y>245160.350730384</ogr:axis_y>
+      <ogr:layers>Layer1,from_dem</ogr:layers>
+      <ogr:p_id>1,2,3,4,5,6,7,8</ogr:p_id>
+      <ogr:topo_bat>B,B,B,B,B,B,B,B</ogr:topo_bat>
+      <ogr:abs_lat>0.0,98.02172116581677,196.04344233163354,294.0651634974503,381.56272921222467,469.0602949269604,556.5578606416962,644.0554263564704</ogr:abs_lat>
+      <ogr:zfond>22.13089942932129,21.038623809814453,20.03804588317871,11.429040908813477,18.1597900390625,19.33172607421875,18.68497657775879,17.591026306152344</ogr:zfond>
+      <ogr:Layer1>21.13089942932129,20.038623809814453,19.03804588317871,10.429040908813477,17.1597900390625,18.33172607421875,17.68497657775879,16.591026306152344</ogr:Layer1>
+      <ogr:from_dem>22.13089942932129,21.038623809814453,20.03804588317871,11.429040908813477,18.1597900390625,19.33172607421875,18.68497657775879,17.591026306152344</ogr:from_dem>
+    </ogr:import_layer_from_dem>
+  </gml:featureMember>
+  <gml:featureMember>
+    <ogr:import_layer_from_dem fid="import_layer_from_dem.3">
+      <ogr:geometryProperty><gml:LineString srsName="EPSG:27563"><gml:coordinates>425952.006134969,245607.788343558,20.029296875 426018.836809816,245651.982822086,20 426085.667484663,245696.177300613,20.8007736206055 426152.498159509,245740.371779141,21.8434524536133 426219.328834356,245784.566257669,22.2000064849854 426286.159509202,245828.760736196,22.1594505310059 426375.626380368,245822.293251534,21.2831058502197 426465.093251534,245815.825766871,9.47635841369629 426554.560122699,245809.358282209,12.558611869812 426644.026993865,245802.890797546,20.0598659515381 426733.493865031,245796.423312883,18.7307224273682 426788.73696319,245854.361196319,17.8370246887207 426843.98006135,245912.299079755,17.2751560211182 426899.223159509,245970.23696319,16.7658405303955 426954.466257669,246028.174846626,17.0295658111572</gml:coordinates></gml:LineString></ogr:geometryProperty>
+      <ogr:sec_id>4</ogr:sec_id>
+      <ogr:sec_name>P4</ogr:sec_name>
+      <ogr:abs_long>1840.74453316491</ogr:abs_long>
+      <ogr:axis_x>426505.77541644</ogr:axis_x>
+      <ogr:axis_y>245812.88488748</ogr:axis_y>
+      <ogr:layers>Layer1,from_dem</ogr:layers>
+      <ogr:p_id>1,2,3,4,5,6,7,8,9,10,11,12,13,14,15</ogr:p_id>
+      <ogr:topo_bat>B,B,B,B,B,B,B,B,B,B,B,B,B,B,B</ogr:topo_bat>
+      <ogr:abs_lat>0.0,80.12172634672488,160.24345269349834,240.36517904020718,320.48690538698065,400.6086317337055,490.3089628035253,580.009293873287,669.7096249431088,759.4099560128705,849.1102870826902,929.1640079909264,1009.2177288991626,1089.2714498074602,1169.3251707156965</ogr:abs_lat>
+      <ogr:zfond>20.029296875,20.0,20.80077362060547,21.84345245361328,22.20000648498535,22.15945053100586,21.283105850219727,9.476358413696289,12.558611869812012,20.059865951538086,18.730722427368164,17.837024688720703,17.275156021118164,16.765840530395508,17.029565811157227</ogr:zfond>
+      <ogr:Layer1>19.029296875,19.0,19.80077362060547,20.84345245361328,21.20000648498535,21.15945053100586,20.283105850219727,8.476358413696289,11.558611869812012,19.059865951538086,17.730722427368164,16.837024688720703,16.275156021118164,15.765840530395508,16.029565811157227</ogr:Layer1>
+      <ogr:from_dem>20.029296875,20.0,20.80077362060547,21.84345245361328,22.20000648498535,22.15945053100586,21.283105850219727,9.476358413696289,12.558611869812012,20.059865951538086,18.730722427368164,17.837024688720703,17.275156021118164,16.765840530395508,17.029565811157227</ogr:from_dem>
+    </ogr:import_layer_from_dem>
+  </gml:featureMember>
+  <gml:featureMember>
+    <ogr:import_layer_from_dem fid="import_layer_from_dem.4">
+      <ogr:geometryProperty><gml:LineString srsName="EPSG:27563"><gml:coordinates>425993.233373578,246286.349743189,21.3598308563232 426066.077550244,246352.950133284,20.9186058044434 426138.92172691,246419.550523378,21.0887336730957 426211.765903576,246486.150913473,21.4499168395996 426284.610080242,246552.751303568,20.8497676849365 426357.454256908,246619.351693662,19.9836940765381 426430.298433574,246685.952083757,15.7775392532349 426503.14261024,246752.552473851,7.9006929397583 426575.986786906,246819.152863946,19.9889278411865 426648.830963572,246885.753254041,22.7705020904541 426721.675140238,246952.353644135,24.1362380981445 426794.519316904,247018.95403423,25.1037940979004 426867.36349357,247085.554424324,27.0954704284668 426940.207670236,247152.154814419,28.5509452819824 427013.051846902,247218.755204514,999999 427085.896023568,247285.355594608,999999 427158.740200234,247351.955984703,999999</gml:coordinates></gml:LineString></ogr:geometryProperty>
+      <ogr:sec_id>5</ogr:sec_id>
+      <ogr:sec_name>P5</ogr:sec_name>
+      <ogr:abs_long>2826.74680044045</ogr:abs_long>
+      <ogr:axis_x>426473.273606768</ogr:axis_x>
+      <ogr:axis_y>246725.243670677</ogr:axis_y>
+      <ogr:layers>Layer1,from_dem</ogr:layers>
+      <ogr:p_id>1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17</ogr:p_id>
+      <ogr:topo_bat>B,B,B,B,B,B,B,B,B,B,B,B,B,B,B,B,B</ogr:topo_bat>
+      <ogr:abs_lat>0.0,98.70099307959877,197.4019861591742,296.102979238773,394.8039723183288,493.5049653979276,592.2059584774834,690.9069515571018,789.6079446367006,888.3089377162564,987.0099307958749,1085.7109238754306,1184.4119169550293,1283.1129100345852,1381.8139031141839,1480.5148961937593,1579.215889273358</ogr:abs_lat>
+      <ogr:zfond>21.359830856323242,20.91860580444336,21.088733673095703,21.44991683959961,20.849767684936523,19.983694076538086,15.777539253234863,7.900692939758301,19.988927841186523,22.7705020904541,24.13623809814453,25.10379409790039,27.095470428466797,28.550945281982422,NULL,NULL,NULL</ogr:zfond>
+      <ogr:Layer1>20.359830856323242,19.91860580444336,20.088733673095703,20.44991683959961,19.849767684936523,18.983694076538086,14.777539253234863,6.900692939758301,18.988927841186523,21.7705020904541,23.13623809814453,24.10379409790039,26.095470428466797,27.550945281982422,NULL,NULL,NULL</ogr:Layer1>
+      <ogr:from_dem>21.359830856323242,20.91860580444336,21.088733673095703,21.44991683959961,20.849767684936523,19.983694076538086,15.777539253234863,7.900692939758301,19.988927841186523,22.7705020904541,24.13623809814453,25.10379409790039,27.095470428466797,28.550945281982422,999999.0,999999.0,999999.0</ogr:from_dem>
+    </ogr:import_layer_from_dem>
+  </gml:featureMember>
+  <gml:featureMember>
+    <ogr:import_layer_from_dem fid="import_layer_from_dem.5">
+      <ogr:geometryProperty><gml:LineString srsName="EPSG:27563"><gml:coordinates>426109.784056244,246611.0266449,21.0615005493164 426170.140659767,246689.420854074,20.745626449585 426230.49726329,246767.815063248,19.6539516448975 426290.853866813,246846.209272422,8.63309192657471 426351.210470337,246924.603481596,10.2650547027588 426411.56707386,247002.99769077,22.9713706970215 426471.923677383,247081.391899944,25.1982536315918</gml:coordinates></gml:LineString></ogr:geometryProperty>
+      <ogr:sec_id>6</ogr:sec_id>
+      <ogr:sec_name>P6</ogr:sec_name>
+      <ogr:abs_long>3045.15513690845</ogr:abs_long>
+      <ogr:axis_x>426296.882707939</ogr:axis_x>
+      <ogr:axis_y>246854.039836183</ogr:axis_y>
+      <ogr:layers>Layer1,from_dem</ogr:layers>
+      <ogr:p_id>1,2,3,4,5,6,7</ogr:p_id>
+      <ogr:topo_bat>B,B,B,B,B,B,B</ogr:topo_bat>
+      <ogr:abs_lat>0.0,98.93721049664458,197.87442099325364,296.81163148987514,395.7488419865197,494.6860524831288,593.6232629797734</ogr:abs_lat>
+      <ogr:zfond>21.061500549316406,20.74562644958496,19.65395164489746,8.633091926574707,10.265054702758789,22.971370697021484,25.198253631591797</ogr:zfond>
+      <ogr:Layer1>20.061500549316406,19.74562644958496,18.65395164489746,7.633091926574707,9.265054702758789,21.971370697021484,24.198253631591797</ogr:Layer1>
+      <ogr:from_dem>21.061500549316406,20.74562644958496,19.65395164489746,8.633091926574707,10.265054702758789,22.971370697021484,25.198253631591797</ogr:from_dem>
+    </ogr:import_layer_from_dem>
+  </gml:featureMember>
+</ogr:FeatureCollection>
diff --git a/test/data/expected/import_layer_from_dem.xsd b/test/data/expected/import_layer_from_dem.xsd
new file mode 100644
index 0000000..fbf1468
--- /dev/null
+++ b/test/data/expected/import_layer_from_dem.xsd
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
+<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
+<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
+<xs:complexType name="FeatureCollectionType">
+  <xs:complexContent>
+    <xs:extension base="gml:AbstractFeatureCollectionType">
+      <xs:attribute name="lockId" type="xs:string" use="optional"/>
+      <xs:attribute name="scope" type="xs:string" use="optional"/>
+    </xs:extension>
+  </xs:complexContent>
+</xs:complexType>
+<xs:element name="import_layer_from_dem" type="ogr:import_layer_from_dem_Type" substitutionGroup="gml:_Feature"/>
+<xs:complexType name="import_layer_from_dem_Type">
+  <xs:complexContent>
+    <xs:extension base="gml:AbstractFeatureType">
+      <xs:sequence>
+        <xs:element name="geometryProperty" type="gml:LineStringPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
+        <xs:element name="sec_id" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:integer">
+              <xs:totalDigits value="10"/>
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="sec_name" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="abs_long" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:decimal">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="axis_x" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:decimal">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="axis_y" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:decimal">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="layers" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="p_id" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="topo_bat" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="abs_lat" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="zfond" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="Layer1" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="from_dem" nillable="true" minOccurs="0" maxOccurs="1">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+      </xs:sequence>
+    </xs:extension>
+  </xs:complexContent>
+</xs:complexType>
+</xs:schema>
diff --git a/test/processing/test_import_layer_from_dem.py b/test/processing/test_import_layer_from_dem.py
new file mode 100644
index 0000000..b73e2ff
--- /dev/null
+++ b/test/processing/test_import_layer_from_dem.py
@@ -0,0 +1,20 @@
+import os
+
+from .. import DATA_PATH, PROFILE_LINES_PATH
+from . import TestCase
+
+DEM_PATH = os.path.join(DATA_PATH, "input", "cas2Mnt.asc")
+
+
+class TestImportTracksAlgorithm(TestCase):
+
+    ALGORITHM_ID = "precourlis:import_layer_from_dem"
+    DEFAULT_PARAMS = {
+        "INPUT": PROFILE_LINES_PATH,
+        "LAYER_NAME": "from_dem",
+        "DEM": DEM_PATH,
+        "BAND": 1,
+    }
+
+    def test_algorithm(self):
+        self.check_algorithm({}, {"OUTPUT": "import_layer_from_dem.gml"})
-- 
GitLab