{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "81740019-a6a2-4912-bc5a-852d855e1fbc",
   "metadata": {},
   "source": [
    "# Download and aggregate ECMWF Open Data 6 hour forecasts to daily timestep and create map-stacks for the next 10 days for the African continent\n",
    "\n",
    "Notebook prepared by Ben Maathuis. ITC-University of Twente, Enschede. The Netherlands"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ee5af87-4e94-406a-b253-4fce7bc4c1b5",
   "metadata": {},
   "source": [
    "The ECMWF Open Data service provides free access to selected forecast products from the ECMWF Integrated Forecasting System (IFS). It is intended for operational forecasting, research, and educational use. For further information see: https://www.ecmwf.int/en/forecasts/datasets/open-data.\n",
    "\n",
    "The main characteristics are:\n",
    "\n",
    "| Feature            | ECMWF Open Data                                                       |\n",
    "| ------------------ | --------------------------------------------------------------------- |\n",
    "| Data type          | Operational forecasts                                                 |\n",
    "| Forecast frequency | 00 UTC and 12 UTC                                                     |\n",
    "| Forecast horizon   | Up to 10 days (higher frequency initially, then longer intervals)     |\n",
    "| Spatial resolution | Approximately 0.25° (~28 km)                                          |\n",
    "| Variables          | Temperature, precipitation, wind, pressure, humidity, radiation, etc. |\n",
    "| Format             | GRIB2                                                                 |\n",
    "| Access             | HTTP and AWS S3                                                       |\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e71329fb-50b7-4058-8de4-46713862a5b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "#!pip install ecmwf-opendata #https://pypi.org/project/ecmwf-opendata/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79f1519a-b2ad-4794-8f2b-02fe94166b4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import xarray as xr\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import os\n",
    "from ecmwf.opendata import Client\n",
    "import matplotlib.pyplot as plt\n",
    "import cartopy.crs as ccrs\n",
    "import cartopy.feature as cfeature\n",
    "import ilwis\n",
    "\n",
    "import warnings\n",
    "warnings.filterwarnings(\"ignore\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb4a5517-0b44-4cd1-a69d-0a8a47bec739",
   "metadata": {},
   "outputs": [],
   "source": [
    "#downloaded data and newly created animations in same folder as notebook, change accordingly!\n",
    "work_dir = os.getcwd()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e78101af-b746-4652-b459-d7de97c07d44",
   "metadata": {},
   "outputs": [],
   "source": [
    "#set the working directory for ILWISPy\n",
    "ilwis.setWorkingCatalog(work_dir)\n",
    "print(work_dir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "07bbfb4a-3350-4c49-9a5e-952d4b8fe1d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Link to the client - source is either the name of server to contact or a fully qualified URL. \n",
    "#Possible values are ecmwf to access ECMWF's servers, aws for data hosted by Amazon Web Services, \n",
    "#or azure to access data hosted on Microsoft's Azure. Default is ecmwf.\n",
    "\n",
    "#Known sources are ['ecmwf', 'azure', 'aws', 'ecmwf-esuites']\n",
    "#client = Client(source=\"aws\", model=\"ifs\", resol=\"0p25\") #0.25 degree \n",
    "client = Client(source=\"ecmwf\", model=\"ifs\", resol=\"0p25\") #0.25 degree "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ba8648a6-4314-46fb-8bad-298505ac042f",
   "metadata": {},
   "outputs": [],
   "source": [
    "#retrieve the 10 day forecast, 6 hourly interval\n",
    "steps = list(range(0, 240, 6))\n",
    "print(steps)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b55e75d-e67c-45fb-9900-2ad48426e219",
   "metadata": {},
   "outputs": [],
   "source": [
    "# uncomment lines below to retrieve latest forecast - all parameters at once\n",
    "# client.retrieve(\n",
    "#     param=[\"tp\", \"msl\", \"10u\", \"10v\", \"2t\", \"ro\"],\n",
    "#     step=steps,\n",
    "#     type=\"fc\",\n",
    "#     target=\"latest_fc_ECMWF.grib2\"\n",
    "# )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ba59f2a-020d-4fc5-9123-8854db4eb3c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "target=\"latest_fc_ECMWF.grib2\"\n",
    "print(str(target))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5dfa08a6-405f-4c31-bf9e-aa97cb67d4de",
   "metadata": {},
   "outputs": [],
   "source": [
    "t2m = xr.open_dataset(target, engine=\"cfgrib\", filter_by_keys={\"shortName\": \"2t\"}) # K\n",
    "\n",
    "msl = xr.open_dataset(target, engine=\"cfgrib\", filter_by_keys={\"shortName\": \"msl\"}) # Pa\n",
    "\n",
    "tp = xr.open_dataset(target, engine=\"cfgrib\", filter_by_keys={\"shortName\": \"tp\"}) # accumulated precipitation\n",
    "\n",
    "ro = xr.open_dataset(target, engine=\"cfgrib\", filter_by_keys={\"shortName\": \"ro\"}) # accumulated runoff\n",
    "\n",
    "u10 = xr.open_dataset(target, engine=\"cfgrib\", filter_by_keys={\"shortName\": \"10u\"}) # m/s\n",
    "\n",
    "v10 = xr.open_dataset(target, engine=\"cfgrib\", filter_by_keys={\"shortName\": \"10v\"}) # m/s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "969be264-9293-462b-a66f-0506f9266037",
   "metadata": {},
   "outputs": [],
   "source": [
    "#data check of selected parameter\n",
    "print(t2m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a69bdadc-81f8-4e28-a4f2-8b0e47e424ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "valid_time = t2m.time + t2m.step"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2bc48920-a742-475c-a903-6cc8d58f9af2",
   "metadata": {},
   "outputs": [],
   "source": [
    "for ds in [t2m, msl, tp, ro, u10, v10]:\n",
    "    ds.coords[\"valid_time\"] = valid_time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "367544ab-cc84-4495-a4a3-95ce65192865",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(t2m.valid_time.values)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3af28eaf-362d-44f2-a7f3-b3889914533a",
   "metadata": {},
   "outputs": [],
   "source": [
    "#get info on number of timesteps\n",
    "t2m = t2m.swap_dims({\"step\": \"valid_time\"})\n",
    "msl = msl.swap_dims({\"step\": \"valid_time\"})\n",
    "tp  = tp.swap_dims({\"step\": \"valid_time\"})\n",
    "ro  = ro.swap_dims({\"step\": \"valid_time\"})\n",
    "u10 = u10.swap_dims({\"step\": \"valid_time\"})\n",
    "v10 = v10.swap_dims({\"step\": \"valid_time\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "569c06df-2994-4a8e-9624-2a40fd6df183",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(t2m.dims)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "629b7954-2d93-4fae-8c7d-3d78f51333e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "#map extent\n",
    "lat_min, lat_max = -38, 40\n",
    "lon_min, lon_max = -26, 52"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c26b6e94-8f69-4f34-a152-d37142ca005d",
   "metadata": {},
   "outputs": [],
   "source": [
    "#create subset\n",
    "t2m = t2m.sel(latitude=slice(lat_max, lat_min), longitude=slice(lon_min, lon_max))\n",
    "\n",
    "msl = msl.sel(latitude=slice(lat_max, lat_min), longitude=slice(lon_min, lon_max))\n",
    "\n",
    "tp = tp.sel(latitude=slice(lat_max, lat_min), longitude=slice(lon_min, lon_max))\n",
    "\n",
    "ro = ro.sel(latitude=slice(lat_max, lat_min), longitude=slice(lon_min, lon_max))\n",
    "\n",
    "u10 = u10.sel(latitude=slice(lat_max, lat_min), longitude=slice(lon_min, lon_max))\n",
    "\n",
    "v10 = v10.sel(latitude=slice(lat_max, lat_min), longitude=slice(lon_min, lon_max))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "00d1851a-37dd-4141-becf-9c5e144aeb81",
   "metadata": {},
   "outputs": [],
   "source": [
    "#convert from Kelvin to Celsius and resample to daily timestep\n",
    "t2m_daily_mean = t2m[\"t2m\"].resample(valid_time=\"1D\").mean() - 273.15\n",
    "t2m_daily_max  = t2m[\"t2m\"].resample(valid_time=\"1D\").max() - 273.15\n",
    "t2m_daily_min  = t2m[\"t2m\"].resample(valid_time=\"1D\").min() - 273.15"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b2561042-d315-4cb5-9502-3d2e2f510d48",
   "metadata": {},
   "outputs": [],
   "source": [
    "t2m_daily_max"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "974b560d-2801-474c-a1fe-8f0fa8a9050f",
   "metadata": {},
   "outputs": [],
   "source": [
    "#convert from Pascal to hPa and resample to (mean) daily timestep\n",
    "msl_daily = msl[\"msl\"].resample(valid_time=\"1D\").mean() / 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a81ff346-780e-4b22-979a-fe0983530113",
   "metadata": {},
   "outputs": [],
   "source": [
    "#process wind speed and directions and resample to (mean) daily timestep\n",
    "u_daily = u10[\"u10\"].resample(valid_time=\"1D\").mean()\n",
    "v_daily = v10[\"v10\"].resample(valid_time=\"1D\").mean()\n",
    "\n",
    "ws_daily = np.sqrt(u_daily**2 + v_daily**2)\n",
    "\n",
    "wd_daily = (180 + np.degrees(np.arctan2(u_daily, v_daily))) % 360"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9c269335-e8de-45fa-8a6c-01780d2d4fde",
   "metadata": {},
   "outputs": [],
   "source": [
    "#retrieve accumulated precipitation\n",
    "\n",
    "#The .diff() function computes the difference between consecutive time steps along the valid_time dimension\n",
    "tp_interval = tp[\"tp\"].diff(dim=\"valid_time\") \n",
    "\n",
    "#Since .diff() removes the first time step, pad() adds it back\n",
    "#valid_time=(1,0), means add 1 value before the existing data, add 0 values after. And constant_values=0, ensures that new first value is filled with 0\n",
    "tp_interval = tp_interval.pad(valid_time=(1,0), constant_values=0) \n",
    "\n",
    "#After padding, the coordinate labels are no longer exactly the same as the original dataset.\n",
    "#This line replaces the time coordinates with the original ones\n",
    "tp_interval = tp_interval.assign_coords(valid_time=tp.valid_time.values) \n",
    "\n",
    "#Print resulting timesteps\n",
    "print(tp_interval.valid_time.values[:10])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "97415934-a299-4d9e-ab81-82125ae1cd80",
   "metadata": {},
   "outputs": [],
   "source": [
    "#convert from meter to mm and aggregate precipitation values (sum) to daily timestep\n",
    "tp_daily = tp_interval.resample(valid_time=\"1D\").sum() * 1000\n",
    "tp_daily"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e2a813b0-944c-44df-a130-4819b9ca9b85",
   "metadata": {},
   "outputs": [],
   "source": [
    "ro_interval = ro[\"ro\"].diff(dim=\"valid_time\")\n",
    "ro_interval = ro_interval.pad(valid_time=(1, 0), constant_values=0)\n",
    "\n",
    "ro_interval = ro_interval.assign_coords(valid_time=ro.valid_time.values)\n",
    "\n",
    "#convert from meter to mm and aggregate total runoff values to daily timestep\n",
    "ro_daily = ro_interval.resample(valid_time=\"1D\").sum() * 1000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a75c61dc-3caf-4632-a053-fb88ee27c6f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "ro_daily"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "74aa286a-e41d-460f-a49d-1edca87a7b91",
   "metadata": {},
   "outputs": [],
   "source": [
    "# =========================================================\n",
    "# KEEP FIRST 10 DAYS\n",
    "# =========================================================\n",
    "\n",
    "tp_daily_mm = tp_daily.isel(valid_time=slice(0, 10))\n",
    "\n",
    "ro_daily_mm = ro_daily.isel(valid_time=slice(0, 10))\n",
    "\n",
    "t2m_daily_mean_c = t2m_daily_mean.isel(valid_time=slice(0, 10))\n",
    "t2m_daily_min_c = t2m_daily_min.isel(valid_time=slice(0, 10))\n",
    "t2m_daily_max_c = t2m_daily_max.isel(valid_time=slice(0, 10))\n",
    "\n",
    "msl_daily_hpa = msl_daily.isel(valid_time=slice(0, 10))\n",
    "\n",
    "wind_speed_daily = ws_daily.isel(valid_time=slice(0, 10))\n",
    "wind_dir_daily = wd_daily.isel(valid_time=slice(0, 10))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10afa06f-355f-489e-a660-7b13c047c41d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# =========================================================\n",
    "# SUMMARY\n",
    "# =========================================================\n",
    "\n",
    "print(\"Daily precipitation:\", tp_daily_mm.shape)\n",
    "print(\"Daily runoff:\", ro_daily_mm.shape)\n",
    "print(\"Daily temperature mean:\", t2m_daily_mean_c.shape)\n",
    "print(\"Daily temperature min:\", t2m_daily_min_c.shape)\n",
    "print(\"Daily temperature max:\", t2m_daily_max_c.shape)\n",
    "print(\"Daily pressure:\", msl_daily_hpa.shape)\n",
    "print(\"Daily wind speed:\", wind_speed_daily.shape)\n",
    "print(\"Daily wind direction:\", wind_dir_daily.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "be421a12-e640-4314-89d2-cb634bacaf57",
   "metadata": {},
   "outputs": [],
   "source": [
    "# First day\n",
    "temp_day1 = t2m_daily_mean_c.isel(valid_time=0)\n",
    "prec_day1 = tp_daily_mm.isel(valid_time=0)\n",
    "prec_day1_masked = np.ma.masked_less(prec_day1, 0.1) #mask no or very low pcp\n",
    "\n",
    "# Create figure with two subplots\n",
    "fig, axes = plt.subplots(\n",
    "    1, 2,\n",
    "    figsize=(14, 6),\n",
    "    subplot_kw={\"projection\": ccrs.PlateCarree()}\n",
    ")\n",
    "\n",
    "# ==========================\n",
    "# Temperature\n",
    "# ==========================\n",
    "ax = axes[0]\n",
    "\n",
    "ax.set_extent([lon_min, lon_max, lat_min, lat_max], crs=ccrs.PlateCarree())\n",
    "\n",
    "ax.coastlines(linewidth=0.8)\n",
    "ax.add_feature(cfeature.BORDERS, linewidth=0.5)\n",
    "ax.add_feature(cfeature.RIVERS, linewidth=0.5)\n",
    "ax.add_feature(cfeature.LAKES)\n",
    "\n",
    "im1 = ax.pcolormesh(\n",
    "    temp_day1.longitude,\n",
    "    temp_day1.latitude,\n",
    "    temp_day1,\n",
    "    cmap=\"RdYlBu_r\",\n",
    "    shading=\"auto\",\n",
    "    transform=ccrs.PlateCarree()\n",
    ")\n",
    "\n",
    "cbar1 = plt.colorbar(im1, ax=ax, shrink=0.7, pad=0.03)\n",
    "cbar1.set_label(\"Temperature (°C)\")\n",
    "\n",
    "# ==========================\n",
    "# Precipitation\n",
    "# ==========================\n",
    "ax = axes[1]\n",
    "\n",
    "ax.set_extent([lon_min, lon_max, lat_min, lat_max], crs=ccrs.PlateCarree())\n",
    "\n",
    "ax.coastlines(linewidth=0.8)\n",
    "ax.add_feature(cfeature.BORDERS, linewidth=0.5)\n",
    "ax.add_feature(cfeature.RIVERS, linewidth=0.5)\n",
    "ax.add_feature(cfeature.LAKES)\n",
    "\n",
    "im2 = ax.pcolormesh(\n",
    "    prec_day1.longitude,\n",
    "    prec_day1.latitude,\n",
    "    prec_day1_masked,\n",
    "    cmap=\"jet\",\n",
    "    shading=\"auto\",\n",
    "    vmin=0,\n",
    "    vmax=25,\n",
    "    transform=ccrs.PlateCarree()\n",
    ")\n",
    "\n",
    "cbar2 = plt.colorbar(im2, ax=ax, shrink=0.7, pad=0.03)\n",
    "cbar2.set_label(\"Precipitation (mm/day)\")\n",
    "\n",
    "# Overall title\n",
    "date = str(temp_day1.valid_time.values)[:10]\n",
    "fig.suptitle(f\"ECMWF Daily Weather Fields\\n{date}\", fontsize=15)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2235f790-46fb-4774-bea0-265b0599d3e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "grf_LL= ilwis.GeoReference('code=georef:type=corners, csy=epsg:4326, envelope=-26 40 52 -38 , gridsize=313 313, cornerofcorners=yes') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6893202e-4db4-4e9b-9ca0-d26267e0b125",
   "metadata": {},
   "outputs": [],
   "source": [
    "dfNum = ilwis.DataDefinition(ilwis.NumericDomain('code=value'), ilwis.NumericRange(-100, 100.0, 0))\n",
    "rcNew = ilwis.RasterCoverage()\n",
    "rcNew.setSize(ilwis.Size(161, 213,10))\n",
    "rcNew.setGeoReference(grf_LL)\n",
    "rcNew.setDataDef(dfNum)\n",
    "\n",
    "print(rcNew.size())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4deef472-d3ba-4160-bc21-04c4a5524fed",
   "metadata": {},
   "source": [
    "### Export the daily forecasts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bab843a3-5788-404c-967e-15246be2ff1a",
   "metadata": {},
   "outputs": [],
   "source": [
    "#T-Mean\n",
    "mapstack_out = np.array([t2m_daily_mean_c]).flatten() \n",
    "print(mapstack_out.shape)\n",
    "rcNew.array2raster(mapstack_out)\n",
    "print(rcNew.size())\n",
    "rcNew.store('T_mean.tif', 'GTiff', 'gdal')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5e65ed10-c613-482b-aea5-e4b8489ab5bb",
   "metadata": {},
   "outputs": [],
   "source": [
    "#T-Min\n",
    "mapstack_out = np.array([t2m_daily_min_c]).flatten()\n",
    "rcNew.array2raster(mapstack_out)\n",
    "print(rcNew.size())\n",
    "rcNew.store('T_min.tif', 'GTiff', 'gdal')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c145e3d-7f89-4886-80cc-2c3b723d8a1f",
   "metadata": {},
   "outputs": [],
   "source": [
    "#T-Max\n",
    "mapstack_out = np.array([t2m_daily_max_c]).flatten()\n",
    "rcNew.array2raster(mapstack_out)\n",
    "print(rcNew.size())\n",
    "rcNew.store('T_max.tif', 'GTiff', 'gdal')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eac5af55-030b-413c-8392-4d2cb8209a6a",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Daily precipitation\n",
    "mapstack_out = np.array([tp_daily_mm]).flatten()\n",
    "rcNew.array2raster(mapstack_out)\n",
    "print(rcNew.size())\n",
    "rcNew.store('Prec.tif', 'GTiff', 'gdal')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4356f193-98ed-4da0-9cef-89981ecf7c86",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Daily runoff\n",
    "mapstack_out = np.array([ro_daily_mm]).flatten()\n",
    "rcNew.array2raster(mapstack_out)\n",
    "print(rcNew.size())\n",
    "rcNew.store('Runoff.tif', 'GTiff', 'gdal')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fd712cd5-83df-48bd-9f29-7d0b73adfa55",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Daily pressure\n",
    "mapstack_out = np.array([msl_daily_hpa]).flatten()\n",
    "rcNew.array2raster(mapstack_out)\n",
    "print(rcNew.size())\n",
    "rcNew.store('Pressure.tif', 'GTiff', 'gdal')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fafbd11b-b2d5-4f80-8dbe-ab1d364ed62c",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Daily wind speed\n",
    "mapstack_out = np.array([wind_speed_daily]).flatten()\n",
    "rcNew.array2raster(mapstack_out)\n",
    "print(rcNew.size())\n",
    "rcNew.store('W_speed.tif', 'GTiff', 'gdal')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "74e09023-0a2f-4d0c-bbcc-f3909399e305",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Daily wind direction\n",
    "mapstack_out = np.array([wind_dir_daily]).flatten()\n",
    "rcNew.array2raster(mapstack_out)\n",
    "print(rcNew.size())\n",
    "rcNew.store('W_dir.tif', 'GTiff', 'gdal')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac71656b-54e2-44e8-9265-8ee20a19ae67",
   "metadata": {},
   "source": [
    "Check your results using ILWIS desktop, import the geotif files and display the maplists as animated sequences."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "614775da-3b18-47bf-82ff-19f27e03385d",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
