From d7904ee99d522871d1d874115e0611e133ebd843 Mon Sep 17 00:00:00 2001
From: pty <pierre.tysebaert@thalesgroup.com>
Date: Fri, 7 Apr 2023 17:22:14 +0200
Subject: [PATCH] =?UTF-8?q?Application=20du=20fix=20d=C3=A9crit=20dans=20l?=
 =?UTF-8?q?'issur=20111?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 python/s2snow/hillshade.py | 74 ++++++++++++++++++++++++++------------
 1 file changed, 51 insertions(+), 23 deletions(-)

diff --git a/python/s2snow/hillshade.py b/python/s2snow/hillshade.py
index 1e7ecfdb..3cab96d2 100755
--- a/python/s2snow/hillshade.py
+++ b/python/s2snow/hillshade.py
@@ -1,15 +1,13 @@
+import logging
 import os
-from osgeo import gdal
+
 import numpy as np
 from lxml import etree
-
-import logging
-
-from s2snow.lis_exception import UnknownProductException
+from osgeo import gdal, osr
 from s2snow.lis_constant import AZIMUTH_PATH, ZENITH_PATH
+from s2snow.lis_exception import UnknownProductException
 from s2snow.otb_wrappers import band_math
 
-
 """
 This scripts compute the hillshade from a DEM and a Sentinel2 Metadata file containing the sun angles.
 
@@ -27,16 +25,48 @@ def get_grid_values_from_xml(xml_path, angle="Zenith"):
        Then, extract the VALUES in <values> v1 v2 v3 </values> <values> v4 v5 v6 </values> format as numpy array
        Loop through the arrays to compute the mean.
     '''
+
     tree = etree.parse(xml_path)
+
+    # get col and row steps (5 km)
+    node = tree.xpath(f".//Sun_Angles_Grids/{angle}/COL_STEP")[0]
+    colstep = float(node.text)
+    node = tree.xpath(f".//Sun_Angles_Grids/{angle}/ROW_STEP")[0]
+    # assume it's a north up image
+    rowstep = -1*float(node.text)
+
+    # get EPSG code
+    epsg = tree.find(".//HORIZONTAL_CS_CODE").text
+
+    # get grid corners coordinates
+    # warning: in array geometry the lower left corner is the upper left in raster geometry
+    ulx = float(tree.find(".//*[@name='upperLeft']/X").text)
+    uly = float(tree.find(".//*[@name='upperLeft']/Y").text)
+    lrx = float(tree.find(".//*[@name='lowerRight']/X").text)
+    lry = float(tree.find(".//*[@name='lowerRight']/Y").text)
+
     node = tree.xpath(f".//Sun_Angles_Grids/{angle}/Values_List")[0] # VALUES
     arrays_lst = []
     for values in node:
         values_arr = [float(k) for k in values.text.split(" ")]
         arrays_lst.append(values_arr)
-    return np.array(arrays_lst)
 
+    # We assume that the above coordinates correspond to the *centers* of corner pixels
+    # otherwise the 23x23 grid would have an extra row and column somewhere
+    ulxMTD = ulx - colstep/2
+    ulyMTD = uly - rowstep/2
+
+    # define the affine transformation coefficients
+    geoTransform = (ulxMTD, colstep, 0, ulyMTD, 0, rowstep)
+
+    # create spatial reference
+    spatialRef = osr.SpatialReference()
+    spatialRef.ImportFromEPSG(int(epsg))
+
+    return np.array(arrays_lst), geoTransform, spatialRef
 
-def resample_angles(angle_array, ref_dem, output_angle):
+
+def resample_angles(angle_array, angle_geoTransform, angle_spatialRef, ref_dem, output_angle):
     """
     Resample the small grid of angles to the size of the dem to take into consideration the variation of sun angles given in the metadata files.
 
@@ -46,13 +76,14 @@ def resample_angles(angle_array, ref_dem, output_angle):
     :return Gdal object: the gdal Dataset with the resampled angles
     """
     driver = gdal.GetDriverByName("GTiff")
-    resampled_angles = driver.Create(output_angle, angle_array.shape[0], angle_array.shape[1], 1, gdal.GDT_Float32)
-    resampled_angles.SetGeoTransform(ref_dem.GetGeoTransform())
-    resampled_angles.SetProjection(ref_dem.GetProjection())
-    resampled_angles.GetRasterBand(1).WriteArray(angle_array)
-    resampled_angles.FlushCache()
-    gdal.Warp(output_angle, resampled_angles, resampleAlg=gdal.GRIORA_Cubic, width=ref_dem.RasterXSize, height=ref_dem.RasterYSize)
-    
+    angles = driver.Create(output_angle, angle_array.shape[0], angle_array.shape[1], 1, gdal.GDT_Float32)
+
+    angles.SetGeoTransform(angle_geoTransform)
+    angles.SetProjection(angle_spatialRef.ExportToWkt())
+    angles.GetRasterBand(1).WriteArray(angle_array)
+    angles.FlushCache()
+    gdal.Warp(output_angle, angles, resampleAlg=gdal.GRIORA_Cubic, width=ref_dem.RasterXSize, height=ref_dem.RasterYSize)
+
     return gdal.Open(output_angle)
 
 
@@ -141,9 +172,6 @@ def compute_hillshade_mask(output_file, dem_path, hillshade_lim, xml, output_fol
     if not os.path.exists(xml):
         raise UnknownProductException("Could not find the Metadata *MTD_ALL.xml file.")
     
-    zenith_angles = get_grid_values_from_xml(xml, angle="Zenith")
-    azimuth_angles = get_grid_values_from_xml(xml, angle="Azimuth")
-    
     dem = gdal.Open(dem_path)
 
     # len_y cannot be negative
@@ -154,7 +182,7 @@ def compute_hillshade_mask(output_file, dem_path, hillshade_lim, xml, output_fol
         logging.error(msg)
         raise UnknownProductException(msg)
     
-    dem_array = np.copy(dem.ReadAsArray(buf_xsize=dem.RasterXSize, buf_ysize=dem.RasterYSize, resample_alg=gdal.GRA_Lanczos).astype(dtype=np.float64))
+    dem_array = dem.ReadAsArray()
     
     # Compute the hill gradient ( https://people.geog.ucsb.edu/~kclarke/G232/terrain/Corripio_2003.pdf )
     grad = gradient(dem_array, len_x, len_y)
@@ -162,10 +190,10 @@ def compute_hillshade_mask(output_file, dem_path, hillshade_lim, xml, output_fol
     
     # angle covers a tile of side 23*5000m, dem covers a tile of size
     # If there are NaN in the xml azimuth and zenith data, the bicubic resizing will create a zone with nan around it 
-    zenith_angles = get_grid_values_from_xml(xml, angle="Zenith")
-    zenith = resample_angles(zenith_angles, dem, os.path.join(output_folder, ZENITH_PATH)).ReadAsArray()
-    azimuth_angles = get_grid_values_from_xml(xml, angle="Azimuth")
-    azimuth = resample_angles(azimuth_angles, dem, os.path.join(output_folder, AZIMUTH_PATH)).ReadAsArray()
+    angle_array, angle_geoTransform, angle_spatialRef = get_grid_values_from_xml(xml, angle="Zenith")
+    zenith = resample_angles(angle_array, angle_geoTransform, angle_spatialRef, dem, os.path.join(output_folder, ZENITH_PATH)).ReadAsArray()
+    angle_array, angle_geoTransform, angle_spatialRef = get_grid_values_from_xml(xml, angle="Azimuth")
+    azimuth = resample_angles(angle_array, angle_geoTransform, angle_spatialRef, dem, os.path.join(output_folder, AZIMUTH_PATH)).ReadAsArray()
     
     sun_vector = sun_vector_from_az_zen(zenith, azimuth)
     
-- 
GitLab