<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Guillaume Attard on Medium]]></title>
        <description><![CDATA[Stories by Guillaume Attard on Medium]]></description>
        <link>https://medium.com/@gui.attard?source=rss-af63146a7096------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*TC4XMpb0rLnOACO7HLOzHw.jpeg</url>
            <title>Stories by Guillaume Attard on Medium</title>
            <link>https://medium.com/@gui.attard?source=rss-af63146a7096------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 11:07:09 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@gui.attard/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Climate Change & Water Resources: Analysis of Groundwater Recharge Around Lyon, France]]></title>
            <link>https://medium.com/@gui.attard/climate-change-water-resources-analysis-of-groundwater-recharge-around-lyon-france-16592b3c9a3e?source=rss-af63146a7096------2</link>
            <guid isPermaLink="false">https://medium.com/p/16592b3c9a3e</guid>
            <category><![CDATA[climate-change]]></category>
            <category><![CDATA[model]]></category>
            <category><![CDATA[science-communication]]></category>
            <category><![CDATA[water]]></category>
            <category><![CDATA[adaptation]]></category>
            <dc:creator><![CDATA[Guillaume Attard]]></dc:creator>
            <pubDate>Mon, 17 Jun 2024 13:32:13 GMT</pubDate>
            <atom:updated>2024-06-17T14:21:26.171Z</atom:updated>
            <content:encoded><![CDATA[<h3>Context</h3><p>For those responsible for defining adaptation strategies, it is crucial to have clear information about climate change impacts, consider uncertainties, and understand the magnitude of potential effects. The Inter-Sectoral Impact Model Intercomparison Project (<a href="https://www.isimip.org/">ISIMIP</a>) provides a framework for consistently projecting the impacts of climate change across different sectors and spatial scales. Through the collaboration of an international network of climate-impact modelers, ISIMIP offers a detailed and consistent picture of the world under different climate change scenarios.</p><h3>General Objectives</h3><p>This study aims to test the applicability and replicability of the workflow developed by <a href="https://doi.org/10.5194/gc-7-121-2024">Müller &amp; Döll (2024)</a> to assess the impact of climate change on a specific variable available in the ISIMIP repository: groundwater recharge. Our focus is on an area of interest (AOI) around Lyon, France.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/996/0*d4MzLo1KBGLb5YDq" /><figcaption>Figure 1: Screenshot of the <a href="https://www.isimip.org/">ISIMIP Platform</a></figcaption></figure><h3>Data Utilized</h3><p>The study utilized an extensive dataset comprising 212 NetCDF files, amounting to 60 GB of data. This dataset includes:</p><ul><li><strong>Historical Data</strong>: 54 NetCDF files (22 GB) covering the period from 1850 to 2014.</li><li><strong>Future Projections</strong>: 158 NetCDF files (38 GB) covering the period from 2015 to 2100.</li><li><strong>Global Climate Models (GCMs)</strong>: Six models were considered — ipsl-cm6a-lr, mri-esm2–0, mpi-esm1–2-hr, ukesm1–0-ll, and gfdl-esm4.</li><li><strong>Global Hydrological Models (GHMs)</strong>: Four models were included — cwatm, watergap2–2e, h08, and miroc-integ-land.</li><li><strong>Shared Socioeconomic Pathways (SSPs)</strong>: Three scenarios were considered — SSP 1, SSP 3, and SSP 5.</li><li><strong>ISIMIP 3b Protocol</strong>: The most recent and representative protocol was used for this study.</li></ul><h3>Method</h3><p>The method of analysis is based on the work described by <a href="https://doi.org/10.5194/gc-7-121-2024">Müller &amp; Döll (2024)</a>:</p><ul><li>Determine a reference distribution of the annual mean groundwater recharge for each model combination over a historical period from 1985 to 2015.</li><li>Define horizons of interest and 30-year distributions (aroun 2050, 2070 and 2084) and calculate the annual mean groundwater recharge for each model combination, then calculate the relative change compared to its reference mean for different SSPs.</li><li>Analyze the interannual variability to determine the relative changes for dry, normal, and wet years. The groundwater recharge values are sorted by magnitude, and relative differences have been calculated between values of the same rank in the future period and the reference period. This sorting corresponds to the exceedance probability, which is indicated as a percentage.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/970/0*YbvrKwOEHRqHYXke" /><figcaption>Figure 2: Combination of multiple GCMs, GHMs and SSPs to understand the relative changes caused by Climate Changes</figcaption></figure><p>In the following, the results are described by means of percentile boxes (Figure 3) to describe the distribution of results given by the multi-models ensemble.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/246/1*Ltd27EvK4OZkMRDZ_R_rTw.png" /><figcaption>Figure 3: illustration of a percentile box.</figcaption></figure><h3>Results</h3><p>The analysis based on Figure 4, revealed significant insights into the future of groundwater recharge around Lyon:</p><ul><li><strong>Short-Term Projections (by 2050)</strong>: Approximately 90% of the models indicate a decrease in groundwater recharge. This trend is observed in both optimistic and pessimistic scenarios.</li><li><strong>Long-Term Projections (by 2084)</strong>: In the pessimistic scenario (SSP 5), 80% of the models project a decrease in mean groundwater recharge by 10% to 40%.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f7LFATNnvx9tSHDJjH3MFQ.png" /><figcaption><em>Figure 4: </em>Percentile boxes showing (a) the average annual recharge over a reference period and (b) the potential percentage changes of the mean groundwater recharge of the multi-model ensemble in the periods around 2050, 2084 and 2084 relative to the reference period.</figcaption></figure><p>Additionnaly, the future interannual fluctuations have been analysed for the horizon 2084 and considering the pessimistic scenario (SSP 5)(Figure 5):</p><ul><li><strong>Wet Years </strong>(exceedence between 0 to 20%): 80% of the models indicate a decrease in recharge by 10% to 30%.</li><li><strong>Normal Years </strong>(exceedence between 40 to 60%): 80% of the models indicate a decrease in recharge by 10% to 40%.</li><li><strong>Dry Years </strong>(exceedence between 80 to 100%): 80% of the models indicate a decrease in recharge by 0% to 55%.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_Q-Y5gVQeGKlWLfzItGtZQ.png" /><figcaption>Figure 5: changes in the interannual variability of groundwater recharge. Percentage change in annual groundwater recharge. The 30 yearly groundwater recharge values are sorted according to their magnitude, and the relative changes were calculated between the values of the same rank in the future and reference periods. This sorting corresponds to the exceedance probability.</figcaption></figure><h3>Conclusion</h3><p>The study successfully developed a reproducible and scalable workflow using ISIMIP repository data and the <a href="https://doi.org/10.5194/gc-7-121-2024">Müller &amp; Döll (2024)</a> methodology to determine the impact of climate change on a complex environmental variable — groundwater recharge — around a location of interest. This workflow is not limited to Lyon or groundwater recharge; it can be applied to any location worldwide and to various other hydrologic or environmental variables, such as agriculture, biodiversity, and marine ecosystems.</p><p>The power of this workflow lies in its comprehensive approach, utilizing all available models of the ISIMIP platform, scenarios, and combinations of GCMs, GHMs, and SSPs. By leveraging this extensive dataset, we provide a holistic view of the potential consequences of climate change on variables of interest. This approach offers valuable insights for policymakers and stakeholders, allowing them to make informed decisions and develop robust adaptation strategies that consider a wide range of potential outcomes — from optimistic to pessimistic scenarios.</p><h3>Acknowledgements</h3><p>Thanks to Laura Müller, Hannes Müller Schmied and Petra Döll for scientific advices.</p><h3>Complementary notes — contact</h3><p>This article and the code has been elaborated with the support of <a href="https://www.ageoce.com/">Ageoce</a>. Ageoce builds digital solutions and services focusing on geodata and geosciences.</p><p>Please feel free to <a href="https://www.ageoce.com/en/contact/">contact us</a> anytime.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=16592b3c9a3e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Evolution of the Soil Wetness Index (SWI) in France: Analysis with Google Earth Engine]]></title>
            <link>https://medium.com/@gui.attard/evolution-of-the-soil-wetness-index-swi-in-france-analysis-with-google-earth-engine-3b541583883b?source=rss-af63146a7096------2</link>
            <guid isPermaLink="false">https://medium.com/p/3b541583883b</guid>
            <category><![CDATA[drought]]></category>
            <category><![CDATA[water]]></category>
            <category><![CDATA[earth-engine]]></category>
            <category><![CDATA[insurance]]></category>
            <category><![CDATA[france]]></category>
            <dc:creator><![CDATA[Guillaume Attard]]></dc:creator>
            <pubDate>Fri, 03 Mar 2023 08:57:53 GMT</pubDate>
            <atom:updated>2023-04-19T17:05:10.984Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Authors: Guillaume Attard &amp; Julien Bardonnet</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EZuqNqwe7Ve3jPLaLjXILA.jpeg" /><figcaption>Spring (left) and summer (right) SWI gain/loss between 1970–2021.</figcaption></figure><p>In France, about 10 millions of houses are exposed to the shrinkage and swelling of clay soils [1] and between 1989 and 2021, the cost of damages related to this phenomenon represented more than 15 billion euros [2]. This phenomenon mostly occurs after severe draught events, that can be identified by low values of the soil wetness index (SWI).</p><p>The SWI represents the state of the soil water reserve (2 meters deep) in relation to the available water at the field capacity (water available for plant feeding). For more details, see the mathematical definition in appendix 1. This index is a key component of the national compensation progam: when damages are caused by the shrinkage and swelling of clay soils, the official recognition of “natural disaster” relies on the SWI [3]. If the return period of the SWI value is higher than 25 years, then municipalities can be recognized and supported by the national natural disasters compensation program.</p><p>In the current context of climate change, the following questions arise:</p><ul><li>How did the SWI evolve in France since the beginning of its calculation at a national scale in 1969?</li><li>Is there eventually some locations more impacted than others in terms of soil wetness evolution?</li></ul><h4><strong>Dataset availability and description</strong></h4><p>We explore the evolution of the uniform SWI (see appendix 1) in France between 1970 to 2021. Uniform SWI values are provided by Meteo-France on a monthly basis since 1969 with a spatial resolution of 8 km. Each monthly value integrates the moisture status of the current month and of the two previous months. The dataset is available <a href="https://donneespubliques.meteofrance.fr/?fond=produit&amp;id_produit=301&amp;id_rubrique=40">on the website of Meteo-France</a>.</p><h4><strong>Google Earth Engine Analysis</strong></h4><p>We first ingested this dataset in Google Earth Engine as an Image Collection. This Earth Engine asset has been made <a href="https://geodatafr.github.io/METEO_FRANCE/SWI_Soil_Wetness_Index/">publicly available</a>.</p><p>Then, the dataset has been reduced and resampled regarding the four seasons of interest to eventually differentiate spring, summer, autumn, or winter soil wetness evolutions over the period 1970–2021.</p><p>The uniform SWI image collection ingested in Earth Engine is composed by 624 images (1 image by month from January 1970 to December 2021). For each year, and for each season, a composite image has been create with the mean SWI values of the season.</p><p>Then, a linear <a href="https://developers.google.com/earth-engine/guides/reducers_regression">fit reduction</a> has been applied to each seasonal collection in order to get the general trend. This process computes the least squares estimate of a linear function of the seasonal uniform SWI variable.</p><p>Finally, the resulting national trend image is reduced at the scale of French departments. It allows for estimating the averaged SWI gain/loss over the period of interest.</p><h4>Results</h4><p>The result of the analysis is illustrated on the figure below. On this figure, the absolute SWI gain/loss between 1970–2021 is mapped. The quantitative table associated with these results is provided in appendix 2, where the averaged SWI gain/loss by department and by season is given.</p><p>These results can be summarized as follow:</p><ul><li>The 5 most impacted departments in terms of SWI loss are <strong>Gers, Cantal, Haute-Garonne, Lozère </strong>and<strong> Loire.</strong></li><li>The spring is the season where the most severe and generalized SWI loss is observed. The most severe spring SWI loss is observed in <strong>Ardèche</strong> (absolute loss of 0.29 over the period 1970–2021).</li><li>In 78 departments (over 96), the SWI trend between 1970–2021 is negative, and in 33 departments the trend is particularly severe. In these departments, the absolute SWI loss is higher than 0.1 (all seasons averaged).</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/755/1*HGYuq1_eAJQurkBfVThx9w.png" /></figure><h4><strong>Complementary notes — contact</strong></h4><p>This article and the code has been elaborated with the support of <a href="https://www.ageoce.com/">Ageoce</a>. Ageoce builds digital solutions and services focusing on geodata intelligence and geosciences.</p><p>Please feel free to <a href="https://www.ageoce.com/en/contact/">contact us</a> anytime.</p><h4>Appendix 1 : <strong>mathematical definition of the SWI</strong></h4><p>The calculation of the SWI reads as follow:</p><p><em>SWI </em>= (<em>θfc </em>​− <em>θwi) / (</em>​<em>θ </em>− <em>θwi)</em>​​</p><p>with:</p><ul><li><em>θ:</em> water content in the soil,</li><li><em>θwi:</em> water content in the soil at the wilting point. The wilting point represents the point below what water cannot be extracted by plant roots,</li><li><em>θfc</em> water content in the soil at the field capacity. The field capacity represents the point after which water cannot be stored by soil any more. After that point, gravitational forces become too high and water starts to infiltrate the lower levels.</li></ul><p>Additionally, for a month <em>m</em>, the uniformized SWI is given as follow:</p><p><em>USWI </em>= (<em>SWIm </em>​+ <em>SWIm</em>−1​ + <em>SWIm</em>−2) / 3​​</p><h4>Appendix 2: Table of SWI evolution by department</h4><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fairtable.com%2Fembed%2FshrWNZM2FLf7Sud7Z&amp;display_name=Airtable&amp;url=https%3A%2F%2Fairtable.com%2FshrWNZM2FLf7Sud7Z&amp;image=https%3A%2F%2Fstatic.airtable.com%2Fimages%2Foembed%2Fairtable.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=airtable" width="800" height="533" frameborder="0" scrolling="no"><a href="https://medium.com/media/efb5efe683ee34f7e8faa2ea3d613e77/href">https://medium.com/media/efb5efe683ee34f7e8faa2ea3d613e77/href</a></iframe><h4><strong>Bibliography</strong></h4><p>[1] SDES (2021) Nouveau zonage d’exposition au retrait-gonflement des argiles : plus de 10,4 millions de maisons individuelles potentiellement très exposées. Article web du ministère en charge de l’écologie en France.</p><p>[2] France Assureurs (2022) Le risque sécheresse et son impact sur les habitations. W<a href="https://www.franceassureurs.fr/wp-content/uploads/le-risque-secheresse-et-son-impact-sur-les-habitations-15-novembre-2022-web.pdf">eb</a> article.</p><p>[3] Ministère de l’intérieur (2019) Circulaire INTE1911312C relative à la procédure de reconnaissance de l’état de catastrophe naturelle — Révision des critères permettant de caractériser l’intensité des épisodes de sécheresse-réhydratation des sols à l’origine de mouvements de terrains différentiels.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3b541583883b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Groundwater recharge estimation using Google Earth Engine]]></title>
            <link>https://medium.com/@gui.attard/groundwater-recharge-estimation-guiattard-medium-2bc2b35168b?source=rss-af63146a7096------2</link>
            <guid isPermaLink="false">https://medium.com/p/2bc2b35168b</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[earth-engine]]></category>
            <category><![CDATA[groundwater]]></category>
            <dc:creator><![CDATA[Guillaume Attard]]></dc:creator>
            <pubDate>Mon, 11 Jul 2022 07:26:58 GMT</pubDate>
            <atom:updated>2023-04-20T07:57:37.011Z</atom:updated>
            <content:encoded><![CDATA[<p><strong><em>Originally published as an </em></strong><a href="https://developers.google.com/earth-engine/tutorials/community/groundwater-recharge-estimation"><strong><em>Earth Engine community tutorial</em></strong></a></p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*hExZDMgSEHLh45tvq6jdFQ.png" /><figcaption>Average annual groundwater recharge rate in France between 2015 and 2019.</figcaption></figure><p>Groundwater recharge represents the amount of water coming from precipitation reaching the groundwater table. Its determination helps to better understand the available/renewable groundwater in watersheds and the shape of groundwater flow systems.</p><p>One of the simplest methods to estimate groundwater recharge is the Thornthwaite-Mather procedure (Steenhuis and Van Der Molen, 1986). This procedure was published by Thornthwaite and Mather (1955, 1957). The idea of this procedure is to calculate the water balance in the root zone of the soil where water can be (1) evaporated into the atmosphere under the effect of heat, (2) transpired by vegetation, (3) stored by the soil, and eventually (4) infiltrated when stored water exceeds the field capacity.</p><p>This procedures relies on several parameters and variables described as follows:</p><ul><li>information about soil texture (e.g. sand and clay content) to describe the hydraulic properties of the soil and its capacity to store/infiltrate,</li><li>meteorological records: precipitation and potential evapotranspiration.</li></ul><p>Of course groundwater recharge can be influenced by many other factors such as the slope of the terrain, the snow cover, the variability of the crop/land cover and the irrigation. In the following these aspects are not taken into account.</p><p>In the first part of the tutorial, the Earth Engine python API will be initialized, some useful libraries will be imported, and the location/period of interest will be defined. If you are not familiar with the Earth Engine python API, having a first look at <a href="https://developers.google.com/earth-engine/tutorials/community/intro-to-python-api">the Intro to the Earth Engine python API</a> is recommanded.</p><p>In the second part, <a href="https://developers.google.com/earth-engine/datasets/tags/opengeohub">OpenLandMap datasets</a> related to soil properties will be explored. The wilting point and field capacity of the soil will be calculated by applying some mathematical expressions to multiple images.</p><p>In the third part, <a href="https://developers.google.com/earth-engine/datasets/catalog/MODIS_006_MOD16A2">evapotranspiration</a> and <a href="https://developers.google.com/earth-engine/datasets/catalog/UCSB-CHG_CHIRPS_DAILY">precipitation</a> datasets will be imported. A function will be defined to resample the time resolution of an ee.ImageCollection and to homogenize time index of both datasets. Both datasets will then be combined into one.</p><p>In the fourth and final part, the Thornthwaite-Mather(TM) procedure will be implemented by iterating over the meteorological ee.ImageCollection. Finally, a comparison between groundwater recharge in two places will be described and the resulting mean annual groundwater recharge will be displayed over France.</p><h4>Run me first</h4><h4>Earth Engine API</h4><p>First of all, run the following cell to initialize the API. The output will contain instructions on how to grant this notebook access to Earth Engine using your account.</p><pre>import ee<br><br># Trigger the authentication flow.<br>ee.Authenticate()<br><br># Initialize the library.<br>ee.Initialize()</pre><h4>Other libraries</h4><p>Import other libraries/modules used in this notebook.</p><ul><li><a href="https://pandas.pydata.org/"><strong>pandas</strong></a>: data analysis (including the <a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html">DataFrame</a> data structure)</li><li><a href="https://matplotlib.org/"><strong>matplotlib</strong></a>: data visualization library</li><li><a href="https://numpy.org/"><strong>numpy</strong></a>: array-processing package</li><li><a href="https://python-visualization.github.io/folium/"><strong>folium</strong></a>: interactive web map</li><li><a href="https://docs.python.org/3/library/pprint.html"><strong>pprint</strong></a>: a pretty printer</li><li><a href="https://python-visualization.github.io/branca/colormap.html"><strong>branca.colormap</strong></a>: utility module for dealing with colormaps.</li></ul><pre>import pandas as pd<br>import matplotlib.pyplot as plt<br>import numpy as np<br>import folium<br>import pprint<br>import branca.colormap as cm</pre><h4>Some input parameters</h4><p>We additionally define some parameters used to evaluate the results of our implementation. Particularly:</p><ul><li>the period of interest to get meteorological records,</li><li>a location of interest based on longitude and latitude coordinates. In the following, the point of interest is located in a productive agricultural region which is about 30 kilometers outside of the city of Lyon (France). This point is used to evaluate and illustrate the progress of the described procedure.</li></ul><pre># Initial date of interest (inclusive).<br>i_date = &quot;2015-01-01&quot;<br><br># Final date of interest (exclusive).<br>f_date = &quot;2020-01-01&quot;<br><br># Define the location of interest with a point.<br>lon = 5.145041<br>lat = 45.772439<br>poi = ee.Geometry.Point(lon, lat)<br><br># A nominal scale in meters of the projection to work in [in meters].<br>scale = 1000</pre><h4>From soil texture to hydraulic properties</h4><h4>Definitions</h4><p>Two hydraulic properties of soil are commonly used in the TM procedure:</p><ul><li>the <em>wilting point</em> represents the point below what water cannot be extracted by plant roots,</li><li>the <em>field capacity</em> represents the point after which water cannot be stored by soil any more. After that point, gravitational forces become too high and water starts to infiltrate the lower levels.</li></ul><p>Some equations given by Saxton &amp; Rawls (2006) are used to link both parameters to the texture of the soil. The calculation of water content at wilting point <strong><em>θWP</em></strong> can be done as follows:</p><p><strong><em>θWP</em> =<em> θ1500t</em> + (</strong>0.14<strong> <em>θ1500t</em> </strong>− 0.002<strong>)</strong> with:</p><p><strong><em>θ1500t</em> = </strong>−0.024 <strong><em>S</em> </strong>+ 0.487 <strong><em>C</em> </strong>+ 0.006 <strong><em>OM</em> </strong>+ 0.005(<strong><em>S</em> </strong>×<strong> <em>OM</em></strong>)− 0.013(<strong><em>C</em> </strong>×<strong> <em>OM</em></strong>) + 0.068(<strong><em>S</em> </strong>×<strong> <em>C</em></strong>) + 0.031</p><p>where:</p><ul><li><strong><em>S</em></strong>: represents the sand content of the soil (mass percentage),</li><li><strong><em>C</em></strong>: represents the clay content of the soil (mass percentage),</li><li><strong><em>OM</em></strong>: represents the organic matter content of the soil (mass percentage).</li></ul><p>Similarly, the calculation of the water content at field capacity <strong><em>θFC</em></strong> can be done as follows:</p><p><strong><em>θFC</em></strong> = <strong><em>θ33t</em></strong> + (1.283 <strong><em>θ33t</em></strong><em>² </em>− 0.374 <strong><em>θ33t </em></strong>−0.15) with:</p><p><strong><em>θ33t</em></strong> = -0.251 <strong><em>S</em></strong> + 0.195 <strong><em>C</em></strong> + 0.011 <strong><em>OM</em></strong> + 0.006 (<strong><em>S</em></strong> × <strong><em>OM</em></strong>) — 0.027 (<em>C</em> × <strong>OM</strong>) + 0.452 (<strong><em>S</em></strong> × <strong><em>C</em></strong>) + 0.299</p><h4>Determination of soil texture and properties</h4><p>In the following, <a href="https://developers.google.com/earth-engine/datasets/tags/opengeohub">OpenLandMap datasets</a> are used to describe clay, sand and organic carbon content of soil. A global dataset of soil water content at the field capacity with a resolution of 250 m has been made available by Hengl &amp; Gupta (2019). However, up to now, there is no dataset dedicated to the water content of soil at the wilting point. Consequently, in the following, both parameters will be determined considering the previous equations and using the global datasets giving the sand, clay and organic matter contents of the soil. According to the <a href="https://developers.google.com/earth-engine/datasets/catalog/OpenLandMap_SOL_SOL_CLAY-WFRACTION_USDA-3A1A1A_M_v02#description">description</a>, these datasets are based on machine learning predictions from global compilation of soil profiles and samples. Processing steps are described in detail <a href="https://gitlab.com/openlandmap/global-layers/tree/master/soil">here</a>. The information (clay, sand content, etc.) is given at 6 standard depths (0, 10, 30, 60, 100 and 200 cm) at 250 m resolution.</p><p>These standard depths and associated bands are defined into a list as follows:</p><pre># Soil depths [in cm] where we have data.<br>olm_depths = [0, 10, 30, 60, 100, 200]<br><br># Names of bands associated with reference depths.<br>olm_bands = [&quot;b&quot; + str(sd) for sd in olm_depths]</pre><p>We now define a function to get the ee.Image associated to the parameter we are interested in (e.g. sand, clay, organic carbon content, etc.).</p><pre>def get_soil_prop(param):<br>    &quot;&quot;&quot;<br>    This function returns soil properties image<br>    param (str): must be one of:<br>        &quot;sand&quot;     - Sand fraction<br>        &quot;clay&quot;     - Clay fraction<br>        &quot;orgc&quot;     - Organic Carbon fraction<br>    &quot;&quot;&quot;<br>    if param == &quot;sand&quot;:  # Sand fraction [%w]<br>        snippet = &quot;OpenLandMap/SOL/SOL_SAND-WFRACTION_USDA-3A1A1A_M/v02&quot;<br>        # Define the scale factor in accordance with the dataset description.<br>        scale_factor = 1 * 0.01<br><br>    elif param == &quot;clay&quot;:  # Clay fraction [%w]<br>        snippet = &quot;OpenLandMap/SOL/SOL_CLAY-WFRACTION_USDA-3A1A1A_M/v02&quot;<br>        # Define the scale factor in accordance with the dataset description.<br>        scale_factor = 1 * 0.01<br><br>    elif param == &quot;orgc&quot;:  # Organic Carbon fraction [g/kg]<br>        snippet = &quot;OpenLandMap/SOL/SOL_ORGANIC-CARBON_USDA-6A1C_M/v02&quot;<br>        # Define the scale factor in accordance with the dataset description.<br>        scale_factor = 5 * 0.001  # to get kg/kg<br>    else:<br>        return print(&quot;error&quot;)<br><br>    # Apply the scale factor to the ee.Image.<br>    dataset = ee.Image(snippet).multiply(scale_factor)<br><br>    return dataset</pre><p>We apply this function to import soil properties:</p><pre># Image associated with the sand content.<br>sand = get_soil_prop(&quot;sand&quot;)<br><br># Image associated with the clay content.<br>clay = get_soil_prop(&quot;clay&quot;)<br><br># Image associated with the organic carbon content.<br>orgc = get_soil_prop(&quot;orgc&quot;)</pre><p>To illustrate the result, we define a new method for handing Earth Engine tiles and using it to display the clay content of the soil at a given reference depth, to a Leaflet map.</p><pre>def add_ee_layer(self, ee_image_object, vis_params, name):<br>    &quot;&quot;&quot;Adds a method for displaying Earth Engine image tiles to folium map.&quot;&quot;&quot;<br>    map_id_dict = ee.Image(ee_image_object).getMapId(vis_params)<br>    folium.raster_layers.TileLayer(<br>        tiles=map_id_dict[&quot;tile_fetcher&quot;].url_format,<br>        attr=&quot;Map Data &amp;copy; &lt;a href=&#39;https://earthengine.google.com/&#39;&gt;Google Earth Engine&lt;/a&gt;&quot;,<br>        name=name,<br>        overlay=True,<br>        control=True,<br>    ).add_to(self)<br><br><br># Add Earth Engine drawing method to folium.<br>folium.Map.add_ee_layer = add_ee_layer<br><br>my_map = folium.Map(location=[lat, lon], zoom_start=3)<br><br># Set visualization parameters.<br>vis_params = {<br>    &quot;bands&quot;: [&quot;b0&quot;],<br>    &quot;min&quot;: 0.01,<br>    &quot;max&quot;: 1,<br>    &quot;opacity&quot;: 1,<br>    &quot;palette&quot;: [&quot;white&quot;, &quot;#464646&quot;],<br>}<br><br># Add the sand content data to the map object.<br>my_map.add_ee_layer(sand, vis_params, &quot;Sand Content&quot;)<br><br># Add a marker at the location of interest.<br>folium.Marker([lat, lon], popup=&quot;point of interest&quot;).add_to(my_map)<br><br># Add a layer control panel to the map.<br>my_map.add_child(folium.LayerControl())<br><br># Display the map.<br>display(my_map)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pj6A0jyjhJzndHZSmTe0nw.png" /></figure><p>Now, a function is defined to get soil properties at a given location. The following function returns a dictionary indicating the value of the parameter of interest for each standard depth (in centimeter). This function uses the <a href="https://developers.google.com/earth-engine/apidocs/ee-image-sample">ee.Image.sample</a> method to evaluate the ee.Image properties on the region of interest. The result is then transferred client-side using the <a href="https://developers.google.com/earth-engine/apidocs/ee-image-getinfo">ee.Image.getInfo</a> method.</p><p>In the example below, we are asking for the sand content.</p><pre>def local_profile(dataset, poi, buffer):<br>    # Get properties at the location of interest and transfer to client-side.<br>    prop = dataset.sample(poi, buffer).select(olm_bands).getInfo()<br><br>    # Selection of the features/properties of interest.<br>    profile = prop[&quot;features&quot;][0][&quot;properties&quot;]<br><br>    # Re-shaping of the dict.<br>    profile = {key: round(val, 3) for key, val in profile.items()}<br><br>    return profile<br><br><br># Apply the function to get the sand profile.<br>profile_sand = local_profile(sand, poi, scale)<br><br># Print the result.<br>print(&quot;Sand content profile at the location of interest:\n&quot;, profile_sand)</pre><p>We now apply the function to plot the profile of the soil regarding sand and clay and organic carbon content at the location of interest:</p><pre># Clay and organic content profiles.<br>profile_clay = local_profile(clay, poi, scale)<br>profile_orgc = local_profile(orgc, poi, scale)<br><br># Data visualization in the form of a bar plot.<br>fig, ax = plt.subplots(figsize=(15, 6))<br>ax.axes.get_yaxis().set_visible(False)<br><br># Definition of label locations.<br>x = np.arange(len(olm_bands))<br><br># Definition of the bar width.<br>width = 0.25<br><br># Bar plot representing the sand content profile.<br>rect1 = ax.bar(<br>    x - width,<br>    [round(100 * profile_sand[b], 2) for b in olm_bands],<br>    width,<br>    label=&quot;Sand&quot;,<br>    color=&quot;#ecebbd&quot;,<br>)<br><br># Bar plot representing the clay content profile.<br>rect2 = ax.bar(<br>    x,<br>    [round(100 * profile_clay[b], 2) for b in olm_bands],<br>    width,<br>    label=&quot;Clay&quot;,<br>    color=&quot;#6f6c5d&quot;,<br>)<br><br># Bar plot representing the organic carbon content profile.<br>rect3 = ax.bar(<br>    x + width,<br>    [round(100 * profile_orgc[b], 2) for b in olm_bands],<br>    width,<br>    label=&quot;Organic Carbon&quot;,<br>    color=&quot;black&quot;,<br>    alpha=0.75,<br>)<br><br># Definition of a function to attach a label to each bar.<br>def autolabel_soil_prop(rects):<br>    &quot;&quot;&quot;Attach a text label above each bar in *rects*, displaying its height.&quot;&quot;&quot;<br>    for rect in rects:<br>        height = rect.get_height()<br>        ax.annotate(<br>            &quot;{}&quot;.format(height) + &quot;%&quot;,<br>            xy=(rect.get_x() + rect.get_width() / 2, height),<br>            xytext=(0, 3),  # 3 points vertical offset.<br>            textcoords=&quot;offset points&quot;,<br>            ha=&quot;center&quot;,<br>            va=&quot;bottom&quot;,<br>            fontsize=10,<br>        )<br><br><br># Application of the function to each barplot.<br>autolabel_soil_prop(rect1)<br>autolabel_soil_prop(rect2)<br>autolabel_soil_prop(rect3)<br><br># Title of the plot.<br>ax.set_title(&quot;Properties of the soil at different depths (mass content)&quot;, fontsize=14)<br><br># Properties of x/y labels and ticks.<br>ax.set_xticks(x)<br>x_labels = [str(d) + &quot; cm&quot; for d in olm_depths]<br>ax.set_xticklabels(x_labels, rotation=45, fontsize=10)<br><br>ax.spines[&quot;left&quot;].set_visible(False)<br>ax.spines[&quot;right&quot;].set_visible(False)<br>ax.spines[&quot;top&quot;].set_visible(False)<br><br># Shrink current axis&#39;s height by 10% on the bottom.<br>box = ax.get_position()<br>ax.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9])<br><br># Add a legend below current axis.<br>ax.legend(<br>    loc=&quot;upper center&quot;, bbox_to_anchor=(0.5, -0.15), fancybox=True, shadow=True, ncol=3<br>)<br><br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*poEM292O5NSzZrAwrtYvhw.png" /></figure><h4>Expression to calculate hydraulic properties</h4><p>Now that soil properties are described, the water content at the field capacity and at the wilting point can be calculated according to the equation defined at the beginning of this section. Please note that in the equation of Saxton &amp; Rawls (2006), the wilting point and field capacity are calculated using the Organic Matter content (<strong><em>OM</em></strong>) and not the Organic Carbon content (<strong><em>OC</em></strong>). In the following, we convert <strong><em>OC</em></strong> into <strong><em>OM</em></strong> using the corrective factor known as the <a href="https://inspire.ec.europa.eu/codelist/SoilDerivedObjectParameterNameValue/organicCarbonContent">Van Bemmelen factor</a>:</p><p><strong><em>0M</em></strong> = 1.724 × <strong><em>OC</em></strong></p><p>Several operators are available to perform basic <a href="https://developers.google.com/earth-engine/guides/image_math">mathematical operations</a> on image bands: add(), subtract(), multiply() and divide(). Here, we multiply the organic content by the Van Bemmelen factor. It is done using the ee.Image.multiply method on the organic carbon content ee.Image.</p><pre># Conversion of organic carbon content into organic matter content.<br>orgm = orgc.multiply(1.724)<br><br># Organic matter content profile.<br>profile_orgm = local_profile(orgm, poi, scale)<br>print(&quot;Organic Matter content profile at the location of interest:\n&quot;, profile_orgm)</pre><p>When the mathematical operation to apply to the ee.Image becomes too complex, the <a href="https://developers.google.com/earth-engine/apidocs/ee-image-expression">ee.Image.expression</a> is a good alternative. We use it in the following code block since the calculation of wilting point and field capacity relies on multiple parameters and images. This method takes two arguments:</p><ul><li>a string formalizing the arithmetic expression we want to evaluate,</li><li>a dict associating images to each parameter of the arithmetic expression.</li></ul><p>The mathematical expression is applied as follows to determine wilting point and field capacity:</p><pre># Initialization of two constant images for wilting point and field capacity.<br>wilting_point = ee.Image(0)<br>field_capacity = ee.Image(0)<br><br># Calculation for each standard depth using a loop.<br>for key in olm_bands:<br>    # Getting sand, clay and organic matter at the appropriate depth.<br>    si = sand.select(key)<br>    ci = clay.select(key)<br>    oi = orgm.select(key)<br><br>    # Calculation of the wilting point.<br>    # The theta_1500t parameter is needed for the given depth.<br>    theta_1500ti = (<br>        ee.Image(0)<br>        .expression(<br>            &quot;-0.024 * S + 0.487 * C + 0.006 * OM + 0.005 * (S * OM)\<br>        - 0.013 * (C * OM) + 0.068 * (S * C) + 0.031&quot;,<br>            {<br>                &quot;S&quot;: si,<br>                &quot;C&quot;: ci,<br>                &quot;OM&quot;: oi,<br>            },<br>        )<br>        .rename(&quot;T1500ti&quot;)<br>    )<br><br>    # Final expression for the wilting point.<br>    wpi = theta_1500ti.expression(<br>        &quot;T1500ti + ( 0.14 * T1500ti - 0.002)&quot;, {&quot;T1500ti&quot;: theta_1500ti}<br>    ).rename(&quot;wpi&quot;)<br><br>    # Add as a new band of the global wilting point ee.Image.<br>    # Do not forget to cast the type with float().<br>    wilting_point = wilting_point.addBands(wpi.rename(key).float())<br><br>    # Same process for the calculation of the field capacity.<br>    # The parameter theta_33t is needed for the given depth.<br>    theta_33ti = (<br>        ee.Image(0)<br>        .expression(<br>            &quot;-0.251 * S + 0.195 * C + 0.011 * OM +\<br>        0.006 * (S * OM) - 0.027 * (C * OM)+\<br>        0.452 * (S * C) + 0.299&quot;,<br>            {<br>                &quot;S&quot;: si,<br>                &quot;C&quot;: ci,<br>                &quot;OM&quot;: oi,<br>            },<br>        )<br>        .rename(&quot;T33ti&quot;)<br>    )<br><br>    # Final expression for the field capacity of the soil.<br>    fci = theta_33ti.expression(<br>        &quot;T33ti + (1.283 * T33ti * T33ti - 0.374 * T33ti - 0.015)&quot;,<br>        {&quot;T33ti&quot;: theta_33ti.select(&quot;T33ti&quot;)},<br>    )<br><br>    # Add a new band of the global field capacity ee.Image.<br>    field_capacity = field_capacity.addBands(fci.rename(key).float())</pre><p>Let’s see the result around our location of interest:</p><pre>profile_wp = local_profile(wilting_point, poi, scale)<br>profile_fc = local_profile(field_capacity, poi, scale)<br><br>print(&quot;Wilting point profile:\n&quot;, profile_wp)<br>print(&quot;Field capacity profile:\n&quot;, profile_fc)</pre><p>The result is displayed using barplots as follows:</p><pre>fig, ax = plt.subplots(figsize=(15, 6))<br>ax.axes.get_yaxis().set_visible(False)<br><br># Definition of the label locations.<br>x = np.arange(len(olm_bands))<br><br># Width of the bar of the barplot.<br>width = 0.25<br><br># Barplot associated with the water content at the wilting point.<br>rect1 = ax.bar(<br>    x - width / 2,<br>    [round(profile_wp[b] * 100, 2) for b in olm_bands],<br>    width,<br>    label=&quot;Water content at wilting point&quot;,<br>    color=&quot;red&quot;,<br>    alpha=0.5,<br>)<br><br># Barplot associated with the water content at the field capacity.<br>rect2 = ax.bar(<br>    x + width / 2,<br>    [round(profile_fc[b] * 100, 2) for b in olm_bands],<br>    width,<br>    label=&quot;Water content at field capacity&quot;,<br>    color=&quot;blue&quot;,<br>    alpha=0.5,<br>)<br><br># Add Labels on top of bars.<br>autolabel_soil_prop(rect1)<br>autolabel_soil_prop(rect2)<br><br># Title of the plot.<br>ax.set_title(&quot;Hydraulic properties of the soil at different depths&quot;, fontsize=14)<br><br># Properties of x/y labels and ticks.<br>ax.set_xticks(x)<br>x_labels = [str(d) + &quot; cm&quot; for d in olm_depths]<br>ax.set_xticklabels(x_labels, rotation=45, fontsize=10)<br><br>ax.spines[&quot;left&quot;].set_visible(False)<br>ax.spines[&quot;right&quot;].set_visible(False)<br>ax.spines[&quot;top&quot;].set_visible(False)<br><br># Shrink current axis&#39;s height by 10% on the bottom.<br>box = ax.get_position()<br>ax.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9])<br><br># Put a legend below current axis.<br>ax.legend(<br>    loc=&quot;upper center&quot;, bbox_to_anchor=(0.5, -0.15), fancybox=True, shadow=True, ncol=2<br>)<br><br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SpGmo5waSGlySSprTPR9yQ.png" /></figure><h4>Getting meteorological datasets</h4><h4>Datasets exploration</h4><p>The meteorological data used in our implementation of the TM procedure relies on the following datasets:</p><ul><li><a href="https://developers.google.com/earth-engine/datasets/catalog/UCSB-CHG_CHIRPS_DAILY">Climate Hazards Group InfraRed Precipitation</a> with Station data (CHIRPS) gives precipitations on a daily basis (resolution of 5 km),</li><li><a href="https://developers.google.com/earth-engine/datasets/catalog/MODIS_006_MOD16A2">MODIS Terra Net</a> gives evapotranspiration on a 8-days basis (resolution of 500 m).</li></ul><p>Both datasets are imported as follows, specifying the bands of interest using <a href="https://developers.google.com/earth-engine/apidocs/ee-imagecollection-select">.select()</a> and the period of interest using <a href="https://developers.google.com/earth-engine/apidocs/ee-imagecollection-filterdate">.filterDate()</a>.</p><pre># Import precipitation.<br>pr = (<br>    ee.ImageCollection(&quot;UCSB-CHG/CHIRPS/DAILY&quot;)<br>    .select(&quot;precipitation&quot;)<br>    .filterDate(i_date, f_date)<br>)<br><br># Import potential evaporation PET and its quality indicator ET_QC.<br>pet = (<br>    ee.ImageCollection(&quot;MODIS/006/MOD16A2&quot;)<br>    .select([&quot;PET&quot;, &quot;ET_QC&quot;])<br>    .filterDate(i_date, f_date)<br>)</pre><p>Now we can have a closer look around our location of interest. To evaluate the properties of an ee.ImageCollection, the <a href="https://developers.google.com/earth-engine/apidocs/ee-imagecollection-getregion">ee.ImageCollection.getRegion</a> method is used and combined with <a href="https://developers.google.com/earth-engine/apidocs/ee-imagecollection-getinfo">ee.ImageCollection.getInfo</a> method for a client-side visualization.</p><pre># Evaluate local precipitation conditions.<br>local_pr = pr.getRegion(poi, scale).getInfo()<br>pprint.pprint(local_pr[:5])</pre><p>We now establish a procedure to get meteorological data around a given location in form of a pandas.DataFrame:</p><pre>def ee_array_to_df(arr, list_of_bands):<br>    &quot;&quot;&quot;Transforms client-side ee.Image.getRegion array to pandas.DataFrame.&quot;&quot;&quot;<br>    df = pd.DataFrame(arr)<br><br>    # Rearrange the header.<br>    headers = df.iloc[0]<br>    df = pd.DataFrame(df.values[1:], columns=headers)<br><br>    # Convert the data to numeric values.<br>    for band in list_of_bands:<br>        df[band] = pd.to_numeric(df[band], errors=&quot;coerce&quot;)<br><br>    # Convert the time field into a datetime.<br>    df[&quot;datetime&quot;] = pd.to_datetime(df[&quot;time&quot;], unit=&quot;ms&quot;)<br><br>    # Keep the columns of interest.<br>    df = df[[&quot;time&quot;, &quot;datetime&quot;, *list_of_bands]]<br><br>    # The datetime column is defined as index.<br>    df = df.set_index(&quot;datetime&quot;)<br><br>    return df</pre><p>We apply the function and see the head of the resulting pandas.DataFrame:</p><pre>pr_df = ee_array_to_df(local_pr, [&quot;precipitation&quot;])<br>pr_df.head(10)</pre><p>We do the same for potential evaporation:</p><pre># Evaluate local potential evapotranspiration.<br>local_pet = pet.getRegion(poi, scale).getInfo()<br><br># Transform the result into a pandas dataframe.<br>pet_df = ee_array_to_df(local_pet, [&quot;PET&quot;, &quot;ET_QC&quot;])<br>pet_df.head(5)</pre><p>Looking at both pandas.DataFrame shows the following points:</p><ul><li>the time resolution between both datasets is not the same,</li><li>for some reasons, potential evapotranspiration cannot be calculated at some dates. It corresponds to lines where the quality indicator ET_QC is higher than 1.</li></ul><p>Both issues must be handled before implementing the iterative process: we want to work on a similar timeline with potential evapotranspiration and precipitation, and we want to avoid missing values.</p><h4>Resampling the time resolution of an ee.ImageCollection</h4><p>To address these issues (homogeneous time index and missing values), we make a sum resampling of both datasets by month. When PET cannot be calculated, the monthly averaged value is considered. The key steps and functions used to resample are described below:</p><ul><li>A new date index is defined as a sequence using the <a href="https://developers.google.com/earth-engine/apidocs/ee-list-sequence">ee.List.sequence</a> method.</li><li>A function representing the resampling operation is defined. This function consists of grouping images of the desired time interval and calculating the sum. The sum is calculated by taking the mean between available images and multiplying it by the duration of the interval.</li><li>The user-supplied function is then mapped over the new time index using .map().</li></ul><p>Finally, the resampling procedure reads as follows:</p><pre>def sum_resampler(coll, freq, unit, scale_factor, band_name):<br>    &quot;&quot;&quot;<br>    This function aims to resample the time scale of an ee.ImageCollection.<br>    The function returns an ee.ImageCollection with the averaged sum of the<br>    band on the selected frequency.<br><br>    coll: (ee.ImageCollection) only one band can be handled<br>    freq: (int) corresponds to the resampling frequence<br>    unit: (str) corresponds to the resampling time unit.<br>                must be &#39;day&#39;, &#39;month&#39; or &#39;year&#39;<br>    scale_factor (float): scaling factor used to get our value in the good unit<br>    band_name (str) name of the output band<br>    &quot;&quot;&quot;<br><br>    # Define initial and final dates of the collection.<br>    firstdate = ee.Date(<br>        coll.sort(&quot;system:time_start&quot;, True).first().get(&quot;system:time_start&quot;)<br>    )<br><br>    lastdate = ee.Date(<br>        coll.sort(&quot;system:time_start&quot;, False).first().get(&quot;system:time_start&quot;)<br>    )<br><br>    # Calculate the time difference between both dates.<br>    # https://developers.google.com/earth-engine/apidocs/ee-date-difference<br>    diff_dates = lastdate.difference(firstdate, unit)<br><br>    # Define a new time index (for output).<br>    new_index = ee.List.sequence(0, ee.Number(diff_dates), freq)<br><br>    # Define the function that will be applied to our new time index.<br>    def apply_resampling(date_index):<br>        # Define the starting date to take into account.<br>        startdate = firstdate.advance(ee.Number(date_index), unit)<br><br>        # Define the ending date to take into account according<br>        # to the desired frequency.<br>        enddate = firstdate.advance(ee.Number(date_index).add(freq), unit)<br><br>        # Calculate the number of days between starting and ending days.<br>        diff_days = enddate.difference(startdate, &quot;day&quot;)<br><br>        # Calculate the composite image.<br>        image = (<br>            coll.filterDate(startdate, enddate)<br>            .mean()<br>            .multiply(diff_days)<br>            .multiply(scale_factor)<br>            .rename(band_name)<br>        )<br><br>        # Return the final image with the appropriate time index.<br>        return image.set(&quot;system:time_start&quot;, startdate.millis())<br><br>    # Map the function to the new time index.<br>    res = new_index.map(apply_resampling)<br><br>    # Transform the result into an ee.ImageCollection.<br>    res = ee.ImageCollection(res)<br><br>    return res</pre><p>The precipitation dataset is now resampled by month as follows:</p><ul><li>the collection to resample is defined as pr,</li><li>we want a collection on a monthly basis then freq = 1 and unit = &quot;month&quot;,</li><li>there is no correction factor to apply according to the dataset description then scale_factor = 1,</li><li>&quot;pr&quot; is the name of the output band.</li></ul><pre># Apply the resampling function to the precipitation dataset.<br>pr_m = sum_resampler(pr, 1, &quot;month&quot;, 1, &quot;pr&quot;)<br><br># Evaluate the result at the location of interest.<br>pprint.pprint(pr_m.getRegion(poi, scale).getInfo()[:5])</pre><p>For evapotranspiration, we have to be careful with the unit. The dataset gives us an 8-day sum and a scale factor of 10 is applied. Then, to get a homogeneous unit, we need to rescale by dividing by 8 and 10 = 0.0125.</p><pre># Apply the resampling function to the PET dataset.<br>pet_m = sum_resampler(pet.select(&quot;PET&quot;), 1, &quot;month&quot;, 0.0125, &quot;pet&quot;)<br><br># Evaluate the result at the location of interest.<br>pprint.pprint(pet_m.getRegion(poi, scale).getInfo()[:5])</pre><p>We now combine both ee.ImageCollection objects (pet_m and pr_m) using the <a href="https://developers.google.com/earth-engine/apidocs/ee-imagecollection-combine">ee.ImageCollection.combine</a> method. Note that <strong>corresponding images in both </strong><strong>ee.ImageCollection objects need to have the same time index before combining</strong>.</p><pre># Combine precipitation and evapotranspiration.<br>meteo = pr_m.combine(pet_m)<br><br># Import meteorological data as an array at the location of interest.<br>meteo_arr = meteo.getRegion(poi, scale).getInfo()<br><br># Print the result.<br>pprint.pprint(meteo_arr[:5])</pre><p>We evaluate the result on our location of interest:</p><pre># Transform the array into a pandas dataframe and sort the index.<br>meteo_df = ee_array_to_df(meteo_arr, [&quot;pr&quot;, &quot;pet&quot;]).sort_index()<br><br># Data visualization<br>fig, ax = plt.subplots(figsize=(15, 6))<br><br># Barplot associated with precipitations.<br>meteo_df[&quot;pr&quot;].plot(kind=&quot;bar&quot;, ax=ax, label=&quot;precipitation&quot;)<br><br># Barplot associated with potential evapotranspiration.<br>meteo_df[&quot;pet&quot;].plot(<br>    kind=&quot;bar&quot;, ax=ax, label=&quot;potential evapotranspiration&quot;, color=&quot;orange&quot;, alpha=0.5<br>)<br><br># Add a legend.<br>ax.legend()<br><br># Add some x/y-labels properties.<br>ax.set_ylabel(&quot;Intensity [mm]&quot;)<br>ax.set_xlabel(None)<br><br># Define the date format and shape of x-labels.<br>x_labels = meteo_df.index.strftime(&quot;%m-%Y&quot;)<br>ax.set_xticklabels(x_labels, rotation=90, fontsize=10)<br><br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XjBkMvLUeDYwQeBEJapIgA.png" /></figure><h4>Implementation of the TM procedure</h4><h4>Description</h4><p>Some additional definitions are needed to formalize the Thornthwaite-Mather procedure. The following definitions are given in accordance with Allen et al. (1998) (the document can be downloaded <a href="http://www.climasouth.eu/sites/default/files/FAO%2056.pdf">here</a>):</p><p><strong>TAW</strong> = 1000 × (<strong><em>θFC</em></strong> − <strong><em>θWP</em></strong>) × <strong><em>Zr</em></strong> where:</p><ul><li><strong><em>TAW</em></strong>: the total available soil water in the root zone [mm],</li><li><strong><em>θFC</em></strong>: the water content at the field capacity [m³ m³],</li><li><strong><em>θWP</em></strong>: the water content at wilting point [m³ m³],</li><li><strong><em>Zr</em></strong>: the rooting depth [mm],</li></ul><p>Typical values of <strong><em>θFC</em></strong> and <strong><em>θWP</em></strong> for different soil types are given in Table 19 of Allen et al. (1998).</p><p>The readily available water (<strong><em>RAW</em></strong>) is given by <strong><em>RAW</em></strong> = <strong><em>p</em></strong> × <strong><em>TAW</em></strong>, where <strong><em>p</em></strong> is the average fraction of <strong><em>TAW</em></strong> that can be depleted from the root zone before moisture stress (ranging between 0 to 1). This quantity is also noted <strong><em>STFC</em></strong> which is the available water stored at field capacity in the root zone.</p><p>Ranges of maximum effective rooting depth <strong><em>Zr</em></strong>, and soil water depletion fraction for no stress <strong><em>p</em></strong>, for common crops are given in the Table 22 of Allen et al. (1998). In addition, a global effective plant rooting depth dataset is provided by Yang et al. (2016) with a resolution of 0.5° by 0.5° (see the paper <a href="https://agupubs.onlinelibrary.wiley.com/doi/10.1002/2016WR019392">here</a> and the dataset <a href="https://data.csiro.au/collection/csiro:19813v1">here</a>).</p><p>According to this global dataset, the effective rooting depth around our region of interest (France) can reasonably assumed to <strong><em>Zr</em></strong> = 0.5. Additionally, the parameter <strong><em>p</em></strong> is also assumed constant and equal to and <strong><em>p</em></strong> = 0.5 which is in line with common values described in Table 22 of Allen et al. (1998).</p><pre>zr = ee.Image(0.5)<br>p = ee.Image(0.5)</pre><p>In the following, we also consider an averaged value between reference depths of the water content at wilting point and field capacity:</p><pre>def olm_prop_mean(olm_image, band_output_name):<br>    &quot;&quot;&quot;<br>    This function calculates an averaged value of<br>    soil properties between reference depths.<br>    &quot;&quot;&quot;<br>    mean_image = olm_image.expression(<br>        &quot;(b0 + b10 + b30 + b60 + b100 + b200) / 6&quot;,<br>        {<br>            &quot;b0&quot;: olm_image.select(&quot;b0&quot;),<br>            &quot;b10&quot;: olm_image.select(&quot;b10&quot;),<br>            &quot;b30&quot;: olm_image.select(&quot;b30&quot;),<br>            &quot;b60&quot;: olm_image.select(&quot;b60&quot;),<br>            &quot;b100&quot;: olm_image.select(&quot;b100&quot;),<br>            &quot;b200&quot;: olm_image.select(&quot;b200&quot;),<br>        },<br>    ).rename(band_output_name)<br><br>    return mean_image<br><br><br># Apply the function to field capacity and wilting point.<br>fcm = olm_prop_mean(field_capacity, &quot;fc_mean&quot;)<br>wpm = olm_prop_mean(wilting_point, &quot;wp_mean&quot;)<br><br># Calculate the theoretical available water.<br>taw = (<br>    (fcm.select(&quot;fc_mean&quot;).subtract(wpm.select(&quot;wp_mean&quot;))).multiply(1000).multiply(zr)<br>)<br><br># Calculate the stored water at the field capacity.<br>stfc = taw.multiply(p)</pre><p>The Thornthwaite-Mather procedure used to estimate groundwater recharge is explicitly described by Steenhuis and Van der Molen (1985). This procedure uses monthly sums of potential evaporation, cumulative precipitation, and the moisture status of the soil which is calculated iteratively. The moisture status of the soils depends on the accumulated potential water loss (<strong><em>APWL</em></strong>). This parameter is calculated depending on whether the potential evaporation is greater than or less than the cumulative precipitation. The procedure reads as follow:</p><p><strong>Case 1: potential evapotranspiration is higher than precipitation.</strong></p><p>In that case, <strong><em>PET</em></strong>&gt;<strong><em>P</em></strong> and <strong><em>APWLm</em></strong> is incremented as follows: <strong><em>APWLm</em></strong> = <strong><em>APWLm-1</em></strong>+ (<strong><em>PETm </em></strong>— <strong><em>Pm</em></strong>) where:</p><ul><li><strong><em>APWLm</em></strong> (respectively <strong><em>APWLm-1</em></strong>) represents the accumulated potential water loss for the month <strong><em>m</em></strong> (respectively at the previous month <strong><em>m-1</em></strong>)</li><li><strong><em>PETm</em></strong> the cumulative potential evapotranspiration at month <strong><em>m</em></strong>,</li><li><strong><em>Pm</em></strong> the cumulative precipitation at month <strong><em>m</em></strong>,</li></ul><p>and the relationship between <strong><em>APWL</em></strong> and the amount of water stored in the root zone for the month <strong>m</strong> is expressed as: <strong><em>STm </em></strong>= <strong><em>STFC</em></strong> × [exp(-<strong><em>APWLm</em></strong>/<strong><em>STFC</em></strong>)] where <strong><em>STm</em></strong> is the available water stored in the root zone for the month <strong><em>m</em></strong>.</p><p><strong>Case 2: potential evapotranspiration is lower than precipitation.</strong></p><p>In that case, <strong><em>PET</em></strong>&lt;<strong><em>P</em></strong> and<strong><em> STm</em></strong> is incremented as follows:</p><p><strong><em>STm</em></strong> = <strong><em>STm-1</em></strong> + (<strong><em>Pm</em></strong>-<strong><em>PETm</em></strong>)</p><p><strong><em>Case 2.1: the storage STm is higher than the water stored at the field capacity.</em></strong></p><p>If <strong><em>STm</em></strong> &gt; <strong><em>STFC</em></strong> the recharge is calculated as:</p><p><strong><em>Rm</em></strong> = <strong><em>STm -</em></strong> <strong><em>STFC</em></strong> + <strong><em>Pm -</em></strong> <strong><em>PETm</em></strong></p><p>In addition, the water stored at the end of the month <strong><em>m</em></strong> becomes equal to <strong><em>STFC</em></strong> and <strong><em>APWLm</em></strong> is set equal to zero.</p><p><strong><em>Case 2.2: the storage STm is less than or equal to the water stored at the field capacity.</em></strong></p><p>If <strong><em>STm</em></strong> &lt;<strong><em> STFC</em></strong>, <strong><em>APWLm</em></strong> is implemented as follows:</p><p><strong><em>APWLm</em></strong> = <strong><em>STFC</em></strong> × ln(<strong><em>STm</em></strong>/<strong><em>STFC</em></strong>), and no percolation occurs.</p><h4>Initialization</h4><p>The initial time of the calculation is defined according to the first date of the meteorological collection:</p><pre># Define the initial time (time0) according to the start of the collection.<br>time0 = meteo.first().get(&quot;system:time_start&quot;)</pre><p>Then, we initialize the calculation with an ee.Image where all bands associated to the hydric state of the soil are set equal to ee.Image(0), except for the initial storage which is considered to be equal to the water content at field capacity, meaning that <strong><em>ST0</em></strong> = <strong><em>STFC</em></strong>.</p><pre># Initialize all bands describing the hydric state of the soil.<br># Do not forget to cast the type of the data with a .float().<br># Initial recharge.<br>initial_rech = ee.Image(0).set(&quot;system:time_start&quot;, time0).select([0], [&quot;rech&quot;]).float()<br><br># Initialization of APWL.<br>initial_apwl = ee.Image(0).set(&quot;system:time_start&quot;, time0).select([0], [&quot;apwl&quot;]).float()<br><br># Initialization of ST.<br>initial_st = stfc.set(&quot;system:time_start&quot;, time0).select([0], [&quot;st&quot;]).float()<br><br># Initialization of precipitation.<br>initial_pr = ee.Image(0).set(&quot;system:time_start&quot;, time0).select([0], [&quot;pr&quot;]).float()<br><br># Initialization of potential evapotranspiration.<br>initial_pet = ee.Image(0).set(&quot;system:time_start&quot;, time0).select([0], [&quot;pet&quot;]).float()</pre><p>We combine all these bands into one ee.Image adding new bands to the first using the <a href="https://developers.google.com/earth-engine/apidocs/ee-image-addbands">ee.Image.addBands</a> method:</p><pre>initial_image = initial_rech.addBands(<br>    ee.Image([initial_apwl, initial_st, initial_pr, initial_pet])<br>)</pre><p>We also initialize a list in which new images will be added after each iteration. We create this server-side list using the <a href="https://developers.google.com/earth-engine/apidocs/ee-list">ee.List</a> method.</p><pre>image_list = ee.List([initial_image])</pre><h4>Iteration over an ee.ImageCollection</h4><p>The procedure is implemented by means of the <a href="https://developers.google.com/earth-engine/apidocs/ee-imagecollection-iterate">ee.ImageCollection.iterate</a> method, which applies a user-supplied function to each element of a collection. For each time step, groundwater recharge is calculated using the recharge_calculator considering the previous hydric state of the soil and current meteorological conditions.</p><p>Of course, considering the TM description, several cases must be distinguished to calculate groundwater recharge. The distinction is made by the definition of binary layers with different <a href="https://developers.google.com/earth-engine/guides/image_relational">logical operations</a>. It allows specific calculations to be applied in areas where a given condition is true using the <a href="https://developers.google.com/earth-engine/apidocs/ee-image-where">ee.Image.where</a> method.</p><p>The function we apply to each element of the meteorological dataset to calculate groundwater recharge is defined as follows.</p><pre>def recharge_calculator(image, image_list):<br>    &quot;&quot;&quot;<br>    Contains operations made at each iteration.<br>    &quot;&quot;&quot;<br>    # Determine the date of the current ee.Image of the collection.<br>    localdate = image.date().millis()<br><br>    # Import previous image stored in the list.<br>    prev_im = ee.Image(ee.List(image_list).get(-1))<br><br>    # Import previous APWL and ST.<br>    prev_apwl = prev_im.select(&quot;apwl&quot;)<br>    prev_st = prev_im.select(&quot;st&quot;)<br><br>    # Import current precipitation and evapotranspiration.<br>    pr_im = image.select(&quot;pr&quot;)<br>    pet_im = image.select(&quot;pet&quot;)<br><br>    # Initialize the new bands associated with recharge, apwl and st.<br>    # DO NOT FORGET TO CAST THE TYPE WITH .float().<br>    new_rech = (<br>        ee.Image(0)<br>        .set(&quot;system:time_start&quot;, localdate)<br>        .select([0], [&quot;rech&quot;])<br>        .float()<br>    )<br><br>    new_apwl = (<br>        ee.Image(0)<br>        .set(&quot;system:time_start&quot;, localdate)<br>        .select([0], [&quot;apwl&quot;])<br>        .float()<br>    )<br><br>    new_st = (<br>        prev_st.set(&quot;system:time_start&quot;, localdate).select([0], [&quot;st&quot;]).float()<br>    )<br><br>    # Calculate bands depending on the situation using binary layers with<br>    # logical operations.<br><br>    # CASE 1.<br>    # Define zone1: the area where PET &gt; P.<br>    zone1 = pet_im.gt(pr_im)<br><br>    # Calculation of APWL in zone 1.<br>    zone1_apwl = prev_apwl.add(pet_im.subtract(pr_im)).rename(&quot;apwl&quot;)<br>    # Implementation of zone 1 values for APWL.<br>    new_apwl = new_apwl.where(zone1, zone1_apwl)<br><br>    # Calculate ST in zone 1.<br>    zone1_st = prev_st.multiply(<br>        ee.Image.exp(zone1_apwl.divide(stfc).multiply(-1))<br>    ).rename(&quot;st&quot;)<br>    # Implement ST in zone 1.<br>    new_st = new_st.where(zone1, zone1_st)<br><br>    # CASE 2.<br>    # Define zone2: the area where PET &lt;= P.<br>    zone2 = pet_im.lte(pr_im)<br><br>    # Calculate ST in zone 2.<br>    zone2_st = prev_st.add(pr_im).subtract(pet_im).rename(&quot;st&quot;)<br>    # Implement ST in zone 2.<br>    new_st = new_st.where(zone2, zone2_st)<br><br>    # CASE 2.1.<br>    # Define zone21: the area where PET &lt;= P and ST &gt;= STfc.<br>    zone21 = zone2.And(zone2_st.gte(stfc))<br><br>    # Calculate recharge in zone 21.<br>    zone21_re = zone2_st.subtract(stfc).rename(&quot;rech&quot;)<br>    # Implement recharge in zone 21.<br>    new_rech = new_rech.where(zone21, zone21_re)<br>    # Implement ST in zone 21.<br>    new_st = new_st.where(zone21, stfc)<br><br>    # CASE 2.2.<br>    # Define zone 22: the area where PET &lt;= P and ST &lt; STfc.<br>    zone22 = zone2.And(zone2_st.lt(stfc))<br><br>    # Calculate APWL in zone 22.<br>    zone22_apwl = (<br>        stfc.multiply(-1).multiply(ee.Image.log(zone2_st.divide(stfc))).rename(&quot;apwl&quot;)<br>    )<br>    # Implement APWL in zone 22.<br>    new_apwl = new_apwl.where(zone22, zone22_apwl)<br><br>    # Create a mask around area where recharge can effectively be calculated.<br>    # Where we have have PET, P, FCm, WPm (except urban areas, etc.).<br>    mask = pet_im.gte(0).And(pr_im.gte(0)).And(fcm.gte(0)).And(wpm.gte(0))<br><br>    # Apply the mask.<br>    new_rech = new_rech.updateMask(mask)<br><br>    # Add all Bands to our ee.Image.<br>    new_image = new_rech.addBands(ee.Image([new_apwl, new_st, pr_im, pet_im]))<br><br>    # Add the new ee.Image to the ee.List.<br>    return ee.List(image_list).add(new_image)</pre><p>The TM procedure can now be applied to the meteorological ee.ImageCollection:</p><pre># Iterate the user-supplied function to the meteo collection.<br>rech_list = meteo.iterate(recharge_calculator, image_list)<br><br># Remove the initial image from our list.<br>rech_list = ee.List(rech_list).remove(initial_image)<br><br># Transform the list into an ee.ImageCollection.<br>rech_coll = ee.ImageCollection(rech_list)</pre><p>Let’s have a look at the result around the location of interest:</p><pre>arr = rech_coll.getRegion(poi, scale).getInfo()<br>rdf = ee_array_to_df(arr, [&quot;pr&quot;, &quot;pet&quot;, &quot;apwl&quot;, &quot;st&quot;, &quot;rech&quot;]).sort_index()<br>rdf.head(12)</pre><p>The result can be displayed in the form of a barplot as follows:</p><pre># Data visualization in the form of barplots.<br>fig, ax = plt.subplots(figsize=(15, 6))<br><br># Barplot associated with precipitation.<br>rdf[&quot;pr&quot;].plot(kind=&quot;bar&quot;, ax=ax, label=&quot;precipitation&quot;, alpha=0.5)<br><br># Barplot associated with potential evapotranspiration.<br>rdf[&quot;pet&quot;].plot(<br>    kind=&quot;bar&quot;, ax=ax, label=&quot;potential evapotranspiration&quot;, color=&quot;orange&quot;, alpha=0.2<br>)<br><br># Barplot associated with groundwater recharge<br>rdf[&quot;rech&quot;].plot(kind=&quot;bar&quot;, ax=ax, label=&quot;recharge&quot;, color=&quot;green&quot;, alpha=1)<br><br># Add a legend.<br>ax.legend()<br><br># Define x/y-labels properties.<br>ax.set_ylabel(&quot;Intensity [mm]&quot;)<br>ax.set_xlabel(None)<br><br># Define the date format and shape of x-labels.<br>x_labels = rdf.index.strftime(&quot;%m-%Y&quot;)<br>ax.set_xticklabels(x_labels, rotation=90, fontsize=10)<br><br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S1J3scpyYJCtCLRikBjsiA.png" /></figure><p>The result shows the distribution of precipitation, potential evapotranspiration, and groundwater recharge along the year. It shows that in our area of interest, groundwater recharge generally occurs from October to March. Even though a significant amount of precipitation occurs from April to September, evapotranspiration largely dominates because of high temperatures and sun exposure during these months. The result is that no percolation into aquifers occurs during this period.</p><p>Now the annual average recharge over the period of interest can be calculated. To do that, we resample the DataFrame we&#39;ve just created:</p><pre># Resample the pandas dataframe on a yearly basis making the sum by year.<br>rdfy = rdf.resample(&quot;Y&quot;).sum()<br><br># Calculate the mean value.<br>mean_recharge = rdfy[&quot;rech&quot;].mean()<br><br># Print the result.<br>print(<br>    &quot;The mean annual recharge at our point of interest is&quot;, int(mean_recharge), &quot;mm/an&quot;<br>)</pre><p>The mean annual recharge at our point of interest is 154 mm/an</p><h4>Groundwater recharge comparison between multiple places</h4><p>We now may want to get local information about groundwater recharge and/or map this variable on an area of interest.</p><p>Let’s define a function to get the local information based on the ee.ImageCollection we&#39;ve just built:</p><pre>def get_local_recharge(i_date, f_date, lon, lat, scale):<br>    &quot;&quot;&quot;<br>    Returns a pandas df describing the cumulative groundwater<br>    recharge by month<br>    &quot;&quot;&quot;<br>    # Define the location of interest with a point.<br>    poi = ee.Geometry.Point(lon, lat)<br><br>    # Evaluate the recharge around the location of interest.<br>    rarr = rech_coll.filterDate(i_date, f_date).getRegion(poi, scale).getInfo()<br><br>    # Transform the result into a pandas dataframe.<br>    rdf = ee_array_to_df(rarr, [&quot;pr&quot;, &quot;pet&quot;, &quot;apwl&quot;, &quot;st&quot;, &quot;rech&quot;]).sort_index()<br>    return rdf</pre><p>We now use this function on a second point of interest located near the city of Montpellier (France). This city is located in the south of France, and precipitation and groundwater recharge are expected to be much lower than in the previous case.</p><pre># Define the second location of interest by longitude/latitude.<br>lon2 = 4.137152<br>lat2 = 43.626945<br><br># Calculate the local recharge condition at this location.<br>rdf2 = get_local_recharge(i_date, f_date, lon2, lat2, scale)<br><br># Resample the resulting pandas dataframe on a yearly basis (sum by year).<br>rdf2y = rdf2.resample(&quot;Y&quot;).sum()<br>rdf2y.head()</pre><pre># Data Visualization<br>fig, ax = plt.subplots(figsize=(15, 6))<br>ax.axes.get_yaxis().set_visible(False)<br><br># Define the x-label locations.<br>x = np.arange(len(rdfy))<br><br># Define the bar width.<br>width = 0.25<br><br># Bar plot associated to groundwater recharge at the 1st location of interest.<br>rect1 = ax.bar(<br>    x - width / 2, rdfy.rech, width, label=&quot;Lyon (France)&quot;, color=&quot;blue&quot;, alpha=0.5<br>)<br><br># Bar plot associated to groundwater recharge at the 2nd location of interest.<br>rect2 = ax.bar(<br>    x + width / 2,<br>    rdf2y.rech,<br>    width,<br>    label=&quot;Montpellier (France)&quot;,<br>    color=&quot;red&quot;,<br>    alpha=0.5,<br>)<br><br># Define a function to attach a label to each bar.<br>def autolabel_recharge(rects):<br>    &quot;&quot;&quot;Attach a text label above each bar in *rects*, displaying its height.&quot;&quot;&quot;<br>    for rect in rects:<br>        height = rect.get_height()<br>        ax.annotate(<br>            &quot;{}&quot;.format(int(height)) + &quot; mm&quot;,<br>            xy=(rect.get_x() + rect.get_width() / 2, height),<br>            xytext=(0, 3),  # 3 points vertical offset<br>            textcoords=&quot;offset points&quot;,<br>            ha=&quot;center&quot;,<br>            va=&quot;bottom&quot;,<br>            fontsize=8,<br>        )<br><br><br>autolabel_recharge(rect1)<br>autolabel_recharge(rect2)<br><br># Calculate the averaged annual recharge at both locations of interest.<br>place1mean = int(rdfy[&quot;rech&quot;].mean())<br>place2mean = int(rdf2y[&quot;rech&quot;].mean())<br><br># Add an horizontal line associated with averaged annual values (location 1).<br>ax.hlines(<br>    place1mean,<br>    xmin=min(x) - width,<br>    xmax=max(x) + width,<br>    color=&quot;blue&quot;,<br>    lw=0.5,<br>    label=&quot;average &quot; + str(place1mean) + &quot; mm/y&quot;,<br>    alpha=0.5,<br>)<br><br># Add an horizontal line associated with averaged annual values (location 2).<br>ax.hlines(<br>    place2mean,<br>    xmin=min(x) - width,<br>    xmax=max(x) + width,<br>    color=&quot;red&quot;,<br>    lw=0.5,<br>    label=&quot;average &quot; + str(place2mean) + &quot; mm/y&quot;,<br>    alpha=0.5,<br>)<br><br># Add a title.<br>ax.set_title(&quot;Groundwater recharge comparison between two places&quot;, fontsize=12)<br><br># Define some x/y-axis properties.<br>ax.set_xticks(x)<br>x_labels = rdfy.index.year.tolist()<br>ax.set_xticklabels(x_labels, rotation=45, fontsize=10)<br><br>ax.spines[&quot;left&quot;].set_visible(False)<br>ax.spines[&quot;right&quot;].set_visible(False)<br>ax.spines[&quot;top&quot;].set_visible(False)<br><br># Shrink current axis&#39;s height by 10% on the bottom.<br>box = ax.get_position()<br>ax.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9])<br><br># Add a legend below current axis.<br>ax.legend(<br>    loc=&quot;upper center&quot;, bbox_to_anchor=(0.5, -0.15), fancybox=True, shadow=True, ncol=2<br>)<br><br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3P83TrkkIOsqIPh-0m0KJg.png" /></figure><p>The result shows that the annual recharge in Lyon is almost twice as high as in the area of Montpellier. The result also shows a great variability of the annual recharge ranging from 98 mm/y to 258 mm/y in Lyon and from 16 mm/y to 147 mm/y in Montpellier.</p><h4>Groundwater recharge map of France</h4><p>To get a map of groundwater recharge around our region of interest, let’s create a mean composite ee.Image based on our resulting ee.ImageCollection.</p><pre># Calculate the averaged annual recharge.<br>annual_rech = rech_coll.select(&quot;rech&quot;).mean().multiply(12)<br><br># Calculate the average annual precipitation.<br>annual_pr = rech_coll.select(&quot;pr&quot;).mean().multiply(12)<br><br># Get a feature collection of administrative boundaries.<br>countries = ee.FeatureCollection(&quot;FAO/GAUL/2015/level0&quot;).select(&quot;ADM0_NAME&quot;)<br><br># Filter the feature collection to subset France.<br>france = countries.filter(ee.Filter.eq(&quot;ADM0_NAME&quot;, &quot;France&quot;))<br><br># Clip the composite ee.Images around the region of interest.<br>rech_france = annual_rech.clip(france)<br>pr_france = annual_pr.clip(france)</pre><p>And finally the map can be drawn.</p><pre># Create a folium map.<br>my_map = folium.Map(location=[lat, lon], zoom_start=6, zoom_control=False)<br><br># Set visualization parameters for recharge.<br>rech_vis_params = {<br>    &quot;bands&quot;: &quot;rech&quot;,<br>    &quot;min&quot;: 0,<br>    &quot;max&quot;: 500,<br>    &quot;opacity&quot;: 1,<br>    &quot;palette&quot;: [&quot;red&quot;, &quot;orange&quot;, &quot;yellow&quot;, &quot;green&quot;, &quot;blue&quot;, &quot;purple&quot;],<br>}<br><br># Set visualization parameters for precipitation.<br>pr_vis_params = {<br>    &quot;bands&quot;: &quot;pr&quot;,<br>    &quot;min&quot;: 500,<br>    &quot;max&quot;: 1500,<br>    &quot;opacity&quot;: 1,<br>    &quot;palette&quot;: [&quot;white&quot;, &quot;blue&quot;],<br>}<br><br># Define a recharge colormap.<br>rech_colormap = cm.LinearColormap(<br>    colors=rech_vis_params[&quot;palette&quot;],<br>    vmin=rech_vis_params[&quot;min&quot;],<br>    vmax=rech_vis_params[&quot;max&quot;],<br>)<br><br># Define a precipitation colormap.<br>pr_colormap = cm.LinearColormap(<br>    colors=pr_vis_params[&quot;palette&quot;],<br>    vmin=pr_vis_params[&quot;min&quot;],<br>    vmax=pr_vis_params[&quot;max&quot;],<br>)<br><br># Caption of the recharge colormap.<br>rech_colormap.caption = &quot;Average annual recharge rate (mm/year)&quot;<br><br># Caption of the precipitation colormap.<br>pr_colormap.caption = &quot;Average annual precipitation rate (mm/year)&quot;<br><br># Add the precipitation composite to the map object.<br>my_map.add_ee_layer(pr_france, pr_vis_params, &quot;Precipitation&quot;)<br><br># Add the recharge composite to the map object.<br>my_map.add_ee_layer(rech_france, rech_vis_params, &quot;Recharge&quot;)<br><br># Add a marker at both locations of interest.<br>folium.Marker([lat, lon], popup=&quot;Area of Lyon&quot;).add_to(my_map)<br>folium.Marker([lat2, lon2], popup=&quot;Area of Montpellier&quot;).add_to(my_map)<br><br># Add the colormaps to the map.<br>my_map.add_child(rech_colormap)<br>my_map.add_child(pr_colormap)<br><br># Add a layer control panel to the map.<br>my_map.add_child(folium.LayerControl())<br><br># Display the map.<br>display(my_map)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hExZDMgSEHLh45tvq6jdFQ.png" /></figure><p>The resulting map represents the averaged annual precipitation and groundwater recharge rate in France over the period 2015–2019. Groundwater recharge is represented between 0 (red) to 500 mm/year (purple). Precipitation is represented between 500 (white) to 1500 mm/year (blue).</p><p>Please note the following points:</p><ul><li>The precipitation dataset used in this tutorial is not available over +/- 50 deg latitudes. That is why (1) the northern part of France is not covered by the precipitation/recharge, and (2) some anomalies/discontinuities can be observed near the boundary of the dataset.</li><li>The procedure described in this tutorial does not consider the terrain slope which has a strong influence on runoff, especially in areas with significant relief. In these areas, groundwater recharge is overestimated because runoff is not considered.</li><li>Snow cover has a more complex contribution to groundwater recharge than precipitation. This process is not taken into account in this tutorial. Hence, groundwater recharge is overestimated in areas where snow cover period is significant.</li><li>The recharge calculation can be highly influenced by missing precipitation or potential evapotranspiration values. This issue is partially addressed by the dataset resampling procedure, but missing value can still exist and this point must be checked before considering that the calculated recharge is representative and reliable.</li></ul><h4>Bibliography</h4><p><strong>Allen RG, Pereira LS, Raes D, Smith M (1998).</strong> Crop evapotranspiration: guidelines for computing crop water requirements. <em>Irrigation and Drainage Paper 56</em>, FAO, Rome.</p><p><strong>Saxton, K. E., &amp; Rawls, W. J. (2006).</strong> Soil water characteristic estimates by texture and organic matter for hydrologic solutions. <em>Soil science society of America Journal</em>, 70(5), 1569–1578.</p><p><strong>Steenhuis, T. S., &amp; Van der Molen, W. H. (1986).</strong> The Thornthwaite-Mather procedure as a simple engineering method to predict recharge. <em>Journal of Hydrology</em>, 84(3–4), 221–229.</p><p><strong>Thornthwaite, C. W., &amp; Mather, J. R. (1957).</strong> Instructions and tables for computing potential evapotranspiration and the water balance. <em>Publ. Climatol.</em>, 10(3).</p><p><strong>Yang, Y., Donohue, R. J., &amp; McVicar, T. R. (2016).</strong> Global estimation of effective plant rooting depth: Implications for hydrological modeling. <em>Water Resources Research</em>, 52(10), 8260–8276.</p><h4>Acknowledgements</h4><p>Thanks to Susanne Benz for precious advice. Thanks to Justin Braaten and Gino Miceli for reviewing and helping write and publish this tutorial on the <a href="https://developers.google.com/earth-engine/tutorials/community/groundwater-recharge-estimation">Earth Engine Community platform</a>.</p><h4>Complementary notes — contact</h4><p>This article and the code has been elaborated with the support of <a href="https://www.ageoce.com/">Ageoce</a>. Ageoce builds digital solutions and services focusing on geodata intelligence and geosciences.</p><p>Please feel free to <a href="https://www.ageoce.com/en/contact/">contact us</a> anytime.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2bc2b35168b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Pre-processing the DEM of France (RGE ALTI® 5m) for implementation into Earth Engine]]></title>
            <link>https://medium.com/@gui.attard/pre-processing-the-dem-of-france-rge-alti-5m-for-implementation-into-earth-engine-de9a0778e0d9?source=rss-af63146a7096------2</link>
            <guid isPermaLink="false">https://medium.com/p/de9a0778e0d9</guid>
            <category><![CDATA[earth-engine]]></category>
            <category><![CDATA[france]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[dem]]></category>
            <dc:creator><![CDATA[Guillaume Attard]]></dc:creator>
            <pubDate>Mon, 06 Jun 2022 19:40:33 GMT</pubDate>
            <atom:updated>2022-06-06T20:36:21.299Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<p>Authors: <a href="https://guillaumeattard.com/">Guillaume Attard</a> &amp; <a href="https://github.com/jbardo">Julien Bardonnet</a></p><figure><img alt="france-dem-large" src="https://cdn-images-1.medium.com/max/1024/0*wnVtBDb3MajZR3Nl.png" /></figure><h4>Introduction</h4><p>A Digital Elevation Model (DEM) is a description of the ground elevation. DEMs are used in many fields such as hydrology, agriculture, and natural risks analysis. Since few years, some high resolution DEMs have become open and freely available. Some of them are available on the <a href="https://developers.google.com/earth-engine/datasets/tags/dem">Earth Engine data catalog</a>, but only few are available at a very high resolution. And when it comes to analyse the human and financial consequences of natural disasters such as floods and sea level rise, this high resolution is needed.</p><p>Hence, the aim of this article is to detail the implementation steps of the high resolution France DEM (RGE ALTI® 5m — IGN) into Earth Engine. In the following, the procedure is applied on the southern part of Corsica (Department 2A) but can be easily transposed to the all country.</p><p>The structure of the article reads as follow:</p><ul><li>the dataset (RGE ALTI® 5m) is described,</li><li>a procedure to automate the download of the DEM of multiple departments of France is developed from the FTP server,</li><li>the DEM asc files are turned into geoTIFF files and merged into one,</li><li>then, geoTIFF files are uploaded as new assets in Earth Engine.</li></ul><p>The final result has been published on <a href="https://samapriya.github.io/awesome-gee-community-datasets/projects/france5m/">Awsome Google Earth Engine Community Datasets</a>.</p><h4>Run me first</h4><p>The following librairies are required:</p><pre>import os<br>import shutil<br><br>from ftplib import FTP<br><br>import py7zr<br><br>import pandas as pd<br>import numpy as np<br><br>import rasterio<br>from rasterio.merge import merge<br>from rasterio.plot import show<br><br>from osgeo import gdal, osr<br><br>import pprint<br>from tqdm.notebook import tqdm<br><br>import matplotlib.pyplot as plt<br>from matplotlib.patches import Rectangle<br>%matplotlib inline</pre><pre># Prepare data folder<br>DATAPATH = &#39;data&#39;<br>os.makedirs(DATAPATH, exist_ok=True)</pre><h4>Description of the RGE ALTI® 5m dataset</h4><p>The RGE ALTI® 5m dataset describes the ground elevation of France with a spatial resolution of 5x5m. It is produced by the National Institute of Geographic and Forest Information (IGN — <a href="https://www.ign.fr/)">https://www.ign.fr/)</a> which is a public administrative establishment placed under the joint authority of the Ministries in charge of ecology and forestry. The full dataset description is available <a href="https://geoservices.ign.fr/documentation/donnees/alti/rgealti">here</a>.</p><p>The accuracy of the DEM varies according to areas and issues: it is increased to 20 cm in floodplain areas or coastal areas. The RGE ALTI® is updated from surveys obtained by airborne LIDAR or by correlation of aerial images. The dataset can be downloaded at a <a href="https://geoservices.ign.fr/rgealti#telechargement1m">1x1m resolution</a> or at a <a href="https://geoservices.ign.fr/rgealti#telechargement5m">5x5m resolution</a>.</p><p>This dataset is published under the open licence <a href="https://www.etalab.gouv.fr/wp-content/uploads/2018/11/open-licence.pdf">Etalab</a>.</p><h4>Dataset accessibility — automate download and extraction</h4><p>The RGE ALTI® 5m dataset is split in multiple 7-Zip files, and all individual files are available <a href="https://geoservices.ign.fr/rgealti#telechargement5m">here</a>. These files are stored on a FTP server ftp3.ign.fr with a public username/password.</p><p>Then, a procedure is given below to get the info and download files stored on the FTP associated with the dataset:</p><pre># Define requested info to access the FTP.<br>URL = &quot;ftp3.ign.fr&quot;<br>USERNAME = &quot;RGE_ALTI_ext&quot;<br>PASSWORD = &quot;Thae5eerohsei8ve&quot;<br><br># Create a small helper class to manipulate our ftp connection<br># without leaving the connection open between calls<br>class FTPHelper:<br>    def __init__(self, url, username, password):<br>        self.url = url<br>        self.username = username<br>        self.password = password<br>    <br>    def list_files(self):<br>        &quot;&quot;&quot;<br>        Connects to ftp server and returns a list of file names stored on it.<br>        &quot;&quot;&quot;<br>        with FTP(self.url) as ftp:<br>            ftp.login(self.username, self.password)<br>            filenames = ftp.nlst()<br>        return filenames<br><br><br>    def download_file(self, filename, local_folder, verbose=True):<br>        &quot;&quot;&quot;<br>        Connects to ftp server and downloads remote `filename` to local `local_folder`.<br><br>        Disable verbose to avoid printing the progress bar.<br>        &quot;&quot;&quot;<br>        # Create the directory to store the downloaded file.<br>        os.makedirs(local_folder, exist_ok = True)<br>        # Define the output filename.<br>        output_filepath = os.path.join(local_folder, filename)<br>        # Download the file (big files so ze show a progress bar)<br>        with FTP(self.url) as ftp:<br>            ftp.login(self.username, self.password)<br>            filesize = ftp.size(filename)<br>            with open(output_filepath, &#39;wb&#39;) as f:<br>                with tqdm(total=filesize,<br>                    unit=&#39;B&#39;, unit_scale=True, unit_divisor=1024,<br>                    disable=not verbose) as pbar:<br>                    pbar.set_description(f&quot;Downloading {filename}&quot;)<br>                    def callback_(data):<br>                        l = len(data)<br>                        pbar.update(l)<br>                        f.write(data)<br>                    ftp.retrbinary(&#39;RETR &#39; + filename, callback_)<br>        print(f&quot;{filename} is downloaded.&quot;)<br>        return output_filepath<br><br># Instantiate helper<br>ftp_h = FTPHelper(URL, USERNAME, PASSWORD)</pre><p>The FTPHelper.list_files() function is used to store all files info into a pandas dataframe.</p><pre># Create a pandas.df to store the list of available files.<br>df = pd.DataFrame(data=ftp_h.list_files(), columns=[&quot;name&quot;])<br><br># Rearrange the content of the pandas df.<br>df = pd.concat([df, df[&quot;name&quot;].str.split(&quot;_&quot;, expand=True)], axis=1)<br>df = df.rename(columns = {<br>    0:&quot;dataset&quot;, 1:&quot;version&quot;, 2:&quot;res&quot;, 3:&quot;filetype&quot;, 4:&quot;crs&quot;, <br>    5:&quot;dep&quot;, 6:&quot;date&quot;})<br><br># Loc the dataset of interest RGE Alti 5m.<br>df = df.loc[(df.dataset == &quot;RGEALTI&quot;) &amp; (df.res == &quot;5M&quot;)]<br><br># Clean the date and set the name as index.<br>df[&#39;date&#39;] = df[&#39;date&#39;].apply(lambda x:x[:-3])<br>df = df.set_index(&quot;name&quot;)<br><br>df.head()</pre><figure><img alt="png" src="https://cdn-images-1.medium.com/proxy/1*Z-Ru57hL4xQuDFODVLluHg.png" /></figure><p>In the following we define a department of interest corresponding to the southern part of Corsica (France), and we print the associated file:</p><pre>deps_of_interest = [&#39;D02A&#39;]<br><br># Loc lines of interest in the pandas.df.<br>dfi = df.loc[df.dep.isin(deps_of_interest)]<br>dfi</pre><figure><img alt="png" src="https://cdn-images-1.medium.com/proxy/1*r17gvDe6xPhGA19fm1_kxA.png" /></figure><p>The file of interest is downloaded using our previously define function FTPHelper.download_file(). Please note that the server can be very slow.</p><pre>filename = dfi.index[0]<br><br># Define the name (global variable) of the temp directory to store zip files.<br>TEMP_ZIP = os.path.join(DATAPATH, &quot;temp7z&quot;)<br><br># Comment after running to no download again.<br>ftp_h.download_file(filename, TEMP_ZIP)</pre><p>Then we need to extract the content of the downloaded file. A procedure is defined accordingly:</p><pre># Define the name (global variable) of the temp directory to store extracted files.<br>EXTRACTION_PATH = os.path.join(DATAPATH, &quot;RGE_ALTI_5m&quot;)<br><br>def extract_rge5m(filename):<br>    &quot;&quot;&quot;<br>    Extracts the zip DEM.<br>    &quot;&quot;&quot;<br>    zipfile_path = os.path.join(TEMP_ZIP, filename)<br>    os.makedirs(EXTRACTION_PATH, exist_ok = True)<br>    with py7zr.SevenZipFile(zipfile_path, mode=&#39;r&#39;) as archive:<br>        archive.extractall(path=EXTRACTION_PATH) #WIN: check the path lenght in case of error FileNotFound<br>    print(f&quot;{zipfile_path} is extracted to {EXTRACTION_PATH}.&quot;)</pre><p>The function is applied to our filename of interest.</p><pre>extract_rge5m(filename)</pre><p>Now the unzip folder should look like this:</p><pre>RGE_ALTI_5m<br>└───RGEALTI_2-0_5M_ASC_LAMB93-IGN78C_D02A_2020-04-16<br>    └───RGEALTI<br>        │   LISEZ-MOI.txt<br>        └───1_DONNEES_LIVRAISON_2021-06-00061<br>        │   │   RGEALTI_MNT_5M_ASC_LAMB93_IGN78C_D02A.md5<br>        │   └───RGEALTI_MNT_5M_ASC_LAMB93_IGN78C_D02A<br>        │       │   RGEALTI_FXX_1155_6135_MNT_LAMB93_IGN78C.asc<br>        │       │   RGEALTI_FXX_1155_6140_MNT_LAMB93_IGN78C.asc<br>        │       │   RGEALTI_FXX_1155_6145_MNT_LAMB93_IGN78C.asc<br>        │       │   ...<br>        └───2_METADONNEES_LIVRAISON_2021-06-00061<br>        └───3_SUPPLEMENTS_LIVRAISON_2021-06-00061</pre><p>The folder/subfolder names depend on the acquisition period of the dataset, and on the department of interest. Hence, a procedure is given to go straight to the path where the asc files of the DEM are located. We store all asc files into a list. We finally print the 3 first asc paths of the list.</p><pre>def get_path_asc_paths(filename):<br>    &quot;&quot;&quot;<br>    This funciton returns a list of paths of asc files.<br>    &quot;&quot;&quot;<br>    for root, dirs, files in os.walk(EXTRACTION_PATH, filename[:-3]):<br>        if &quot;1_DONNEES_LIVRAISON&quot; in root:<br>            asc_paths_list = sorted([os.path.join(root, name) for name in files if name.endswith(&quot;.asc&quot;)])<br><br>    return asc_paths_list<br><br>asc_paths_list = get_path_asc_paths(filename)<br>pprint.pprint(asc_paths_list[:3])</pre><p>Let’s have a look at a DEM piece (.asc file) located in this folder. The 10th element is selected and the header of the file (6 first lines) is printed.</p><pre># Select one asc file in the DEM directory.<br>sample_path = asc_paths_list[10]<br><br># Open and print the 10 first lines.<br>with open(sample_path) as file:<br>    content = file.readlines()<br>    header = content[:6]<br>    data = content[6:]<br><br>pprint.pprint(header)</pre><p>As we can see, the header of the file gives us:</p><ul><li>ncols the number of columns,</li><li>nrows the number of rows,</li><li>xllcorner the x location (in epsg 2154) of the lower left corner of the DEM,</li><li>yllcorner the y location (in epsg 2154) of the lower left corner of the DEM,</li><li>cellsize the cell size in meters,</li><li>NODATA_value the nodata value.</li></ul><p>On the other hand, the rest of the content represents the elevation data. The 1000 first characters of the data content is printed as follow:</p><pre>pprint.pprint(data[0][:1000])</pre><p>We define a function to store these data into a dictionary. For further manipulations, it will be convenient to define a class associated with RGE pieces. Hence, we also define such a class in which the DEM data is loaded using the np.loadtxt() function.</p><pre>def get_header_asc(filepath):<br>    &quot;&quot;&quot;<br>    This function reads the header of an asc file and returns <br>    the data into a dictionnary<br>    &quot;&quot;&quot;<br>    file = open(filepath)<br>    content = file.readlines()[:6]<br>    content = [item.split() for item in content]<br>    return dict(content)<br><br>class RGEitem():<br>    &quot;&quot;&quot;<br>    This class is used to handle RGE items.<br>    &quot;&quot;&quot;<br>    def __init__(self, filepath):<br>        self.filename = os.path.basename(filepath)<br>        self.dir = os.path.dirname(filepath)<br>        self.data = np.loadtxt(filepath, skiprows=6)<br>        self.header = get_header_asc(filepath)<br>        self.ncols = int(self.header[&#39;ncols&#39;])<br>        self.nrows = int(self.header[&#39;nrows&#39;])<br>        self.xllc = float(self.header[&#39;xllcorner&#39;])<br>        self.yllc = float(self.header[&#39;yllcorner&#39;])<br>        self.res = float(self.header[&#39;cellsize&#39;])<br>        self.zmin = float(self.data.min())<br>        self.zmax = float(self.data.max())<br>        self.novalue = -99999.</pre><p>Let’s have a look at our sample item:</p><pre>file = RGEitem(sample_path)<br><br>plt.rcParams[&quot;figure.figsize&quot;] = (7,7)<br>plt.imshow(file.data, vmin=0, vmax=300)<br>plt.show()<br><br>print(&quot;The max elevation is&quot;, file.zmax,&quot;m.&quot;)</pre><figure><img alt="png" src="https://cdn-images-1.medium.com/proxy/1*UVWG2GENXjhl_xSZW2eh0Q.png" /></figure><pre>The max elevation is 619.01 m.</pre><h4>Turn .asc files into geoTIFF files</h4><p>Now that we see how to download and access these data, we can transform each .asc file into a geoTIFF using the osgeo library. We do this as follow:</p><pre>def asc_to_tif(file, output_raster_dir, epsg):<br>    &quot;&quot;&quot;<br>    Transforms an .asc file into a geoTIFF.<br><br>    Params:<br>    -------<br>    file: an RGEitem<br>    output_raster_dir (str): path to the directory where the tif will be saved.<br>    epsg (int): projection system.<br><br>    Returns:<br>    --------<br>    output_rasterpath (str): name of the output geoTIFF<br>    &quot;&quot;&quot;<br>    xmin = file.xllc<br>    ymax = file.yllc + file.nrows * file.res <br>    geotransform = (xmin, file.res, 0, ymax, 0, -file.res)<br><br>    output_rasterpath = os.path.join(output_raster_dir, file.filename[:-4] + &quot;.tif&quot;)<br><br>    # Open the file<br>    output_raster = gdal.GetDriverByName(&#39;GTiff&#39;).Create(output_rasterpath, file.ncols, file.nrows, 1, gdal.GDT_Float32)<br>    # Specify the coordinates.  <br>    output_raster.SetGeoTransform(geotransform)<br>    # Establish the coordinate encoding.  <br>    srs = osr.SpatialReference()  <br>    # Specify the projection.               <br>    srs.ImportFromEPSG(epsg)                     <br>    # Export the coordinate system to the file.<br>    output_raster.SetProjection(srs.ExportToWkt())<br>    # Writes the array.   <br>    output_raster.GetRasterBand(1).WriteArray(file.data)<br>    # Set nodata value.  <br>    output_raster.GetRasterBand(1).SetNoDataValue(file.novalue) <br>    output_raster.FlushCache()<br>    return output_rasterpath</pre><p>We apply the function to get the geoTIFF associated with our sample and we open it using rasterio.</p><pre># Generate the tif.<br>SAMPLEPATH = os.path.join(DATAPATH, &quot;sample&quot;)<br>os.makedirs(SAMPLEPATH, exist_ok = True)<br>tiffile = asc_to_tif(file, SAMPLEPATH, 2154)<br><br>with rasterio.open(tiffile) as img:<br>    show(img)<br>    plt.show()</pre><figure><img alt="png" src="https://cdn-images-1.medium.com/proxy/1*ZXWdVQ39bJextJFawdKA9g.png" /></figure><p>Note that x and y axes are now in line with the coordinate reference system we are using (epsg:2154).</p><h4>Merge geoTIFF files into a mosaic</h4><p>We can now define a procedure to trnasform each .asc file into .tif and create a mosaic with all these files following the procedure described by <a href="https://medium.com/spatial-data-science/how-to-mosaic-merge-raster-data-in-python-fb18e44f3c8">Abdishakur</a>.</p><pre>def create_rge_mosaic(asc_paths_list, result_path, mosaic_name, crs):<br>    &quot;&quot;&quot;<br>    Creates a mosaic associated to multiple asc files.<br><br>    Params:<br>    -------<br>    asc_paths_list (list): list of asc files paths.<br>    mosaic_path (str): path of the output mosaic.<br>    crs (int): coordinate reference system (ex. 2154 for EPSG:2154).<br>    &quot;&quot;&quot;<br><br>    # Create tmp dir to save intermediate tifs.<br>    tmpdir = os.path.join(DATAPATH, &#39;local_tifs&#39;)<br>    os.makedirs(tmpdir, exist_ok = True)<br>    <br>    output_raster_paths_list = [asc_to_tif(RGEitem(ascpath), tmpdir, crs) for ascpath in asc_paths_list]<br>    <br>    # Safely load rasters and create the mosaic.<br>    from contextlib import ExitStack<br>    with ExitStack() as stack:<br>        raster_to_mosaic_list = [stack.enter_context(rasterio.open(path)) <br>            for path in output_raster_paths_list<br>        ]<br>        mosaic, output = merge(raster_to_mosaic_list)<br>        output_meta = raster_to_mosaic_list[0].meta.copy()<br>    <br>    output_meta.update({<br>        &quot;driver&quot;: &quot;GTiff&quot;,<br>        &quot;height&quot;: mosaic.shape[1],<br>        &quot;width&quot;: mosaic.shape[2],<br>        &quot;transform&quot;: output,<br>    })<br><br>    # Save the result.<br>    os.makedirs(result_path, exist_ok=True)<br>    mosaic_path = os.path.join(result_path, mosaic_name)<br>    with rasterio.open(mosaic_path, &quot;w&quot;, **output_meta) as m:<br>        m.write(mosaic)<br>    <br>    # Purge the tmp dir.<br>    shutil.rmtree(tmpdir)<br><br>    return mosaic_path</pre><p>The function is applied to create the mosaic of the southern part of Corsica (department 2A). The procedure an take few minutes and the final resulting file is about 1.4 GB:</p><pre>result_path = os.path.join(DATAPATH, &quot;result&quot;)<br>mosaic_name = &quot;mymosaic2A_2022.tif&quot;<br><br>mosaic_path = create_rge_mosaic(asc_paths_list, result_path, mosaic_name, 2154)</pre><p>The result can be displayed as follows. On the following figure, the initial sample area is delineated with a red rectangle.</p><pre>fig, ax = plt.subplots(1, figsize=(12, 12))<br><br># Indicate the location of the sample tiff with a red rectangle.<br>x1, y1 = file.xllc, file.yllc<br>dx, dy = file.ncols*file.res, file.nrows*file.res<br>ax.add_patch(Rectangle((x1, y1), dx, dy, edgecolor=&#39;red&#39;, fill=False))<br><br># PLot the mosaic of the 2A departement.<br>with rasterio.open(mosaic_path) as img:<br>    show(img, interpolation=&#39;none&#39;, ax=ax)<br><br>plt.title(&#39;Elevation of the southern part of Corsica (2A)&#39;)<br>plt.show()</pre><figure><img alt="png" src="https://cdn-images-1.medium.com/proxy/1*z4T_P2fc3Ishrfu9ZeLHPQ.png" /></figure><h4>Group mosaics and upload in Earth Engine as new assets</h4><p>The procedure described in this notebook has been applied for all departments of France (Continental). Then, the resulting geoTIFFs have been uploaded into Google Earth Engine as new assets. The procedure to manage an assets in Earth Engine is described <a href="https://developers.google.com/earth-engine/guides/asset_manager">here</a>.</p><p>The final result has been published on <a href="https://samapriya.github.io/awesome-gee-community-datasets/projects/france5m/">Awsome Google Earth Engine Community Datasets</a>.</p><figure><img alt="AGCD - Samapriya Roy" src="https://cdn-images-1.medium.com/max/1024/0*J_xW_qiz5ZZrYRKb.gif" /></figure><p>The Earth Engine snippet reads as follows:</p><pre>var rge_alti5 = ee.Image(&quot;projects/sat-io/open-datasets/IGN_RGE_Alti_5m&quot;);</pre><p>Sample Code: <a href="https://code.earthengine.google.com/b9054b6d5c4d7de1a4af39ff307e73d7">https://code.earthengine.google.com/b9054b6d5c4d7de1a4af39ff307e73d7</a></p><h4>Acknowledgements</h4><p>Thanks to Samapriya Roy for final merging of the dataset into Earth Engine, for publication on the <a href="https://samapriya.github.io/awesome-gee-community-datasets/projects/france5m/">AGCD</a>, and for the production of the GIF used before intro.</p><h4>Note</h4><p>You can find this notebook and many others on my <a href="https://github.com/guiattard/geoscience-notebooks">GitHub geoscience-notebooks repository</a> or on my <a href="https://guillaumeattard.com/">website</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=de9a0778e0d9" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>