{ "cells": [ { "cell_type": "markdown", "id": "8444a873", "metadata": {}, "source": [ "# Getting Started with OPERA DIST Product\n", "---\n", "\n", "**This notebook serves as an introduction to the OPERA Land Surface Disturbance (DIST) product and visualizing relevant raster layers for wildfire applications.**\n", "\n", " Download the provisional data at https://www.jpl.nasa.gov/go/opera/products/dist-product-suite " ] }, { "cell_type": "code", "execution_count": null, "id": "01505ba8", "metadata": {}, "outputs": [], "source": [ "# Notebook dependencies\n", "\n", "import xarray as xr\n", "import hvplot.xarray\n", "import geoviews as gv\n", "import pyproj\n", "from pyproj import Proj\n", "import holoviews as hv\n", "\n", "from bokeh.models import FixedTicker\n", "hv.extension('bokeh')\n", "\n", "import warnings\n", "warnings.filterwarnings('ignore')" ] }, { "cell_type": "markdown", "id": "f0ef71e2", "metadata": {}, "source": [ "\n", "\n", "### Product Background\n", "---\n", "\n", "The DIST product maps per pixel vegetation disturbance (specifically, vegetation cover loss) from the Harmonized Landsat-8 and Sentinel-2 A/B (HLS) scenes. Vegetation disturbance is mapped when there is an indicated decrease in vegetation cover within an HLS pixel. This notebook focuses on relevant layers within the **DIST_ALERT** product for wildfire applications, which is released at the cadence of HLS imagery. " ] }, { "cell_type": "markdown", "id": "c0a4a5a2", "metadata": {}, "source": [ "\n", "\n", "### Metadata\n", "---\n", "\n", "HLS products provide surface reflectance (SR) data from the Operational Land Imager (OLI) aboard the Landsat-8 remote sensing satellite and the Multi-Spectral Instrument (MSI) aboard the Sentinel-2 A/B remote sensing satellite. HLS products are distributed over projected map coordinates aligned with the Military Grid Reference System (MGRS). Each tile covers 109.8 kilometers squared divided into 3660 rows and 3660 columns at 30 meter pixel spacing. Each tile overlaps neighbors by 4900 meters in each direction." ] }, { "cell_type": "markdown", "id": "f92637c8", "metadata": {}, "source": [ "\n", "\n", "### Raster Layers\n", "___\n", "\n", "The **DIST_ALERT** product is distributed as a set of Cloud-Optimized GeoTIFF (COG) files to enable download of only particular layers of interest to a given user. All L3 DIST layers are stored in files following GeoTIFF format specifications.\n", "\n", "___" ] }, { "cell_type": "code", "execution_count": null, "id": "42a65d2e", "metadata": {}, "outputs": [], "source": [ "# User-specified input\n", "data_dir ='https://opera-provisional-products.s3.us-west-2.amazonaws.com/DIST/DIST_HLS/WG/DIST-ALERT/McKinney_Wildfire/OPERA_L3_DIST-ALERT-HLS_T10TEM_20220815T185931Z_20220817T153514Z_S2A_30_v0.1/OPERA_L3_DIST-ALERT-HLS_T10TEM_20220815T185931Z_20220817T153514Z_S2A_30_v0.1_'\n", "bandlist = ['VEG-ANOM-MAX', 'VEG-DIST-DATE', 'VEG-DIST-STATUS']\n", "bandpath = f\"{data_dir}%s.tif\"" ] }, { "cell_type": "code", "execution_count": null, "id": "f76db601", "metadata": {}, "outputs": [], "source": [ "# Functions\n", "def stack_bands(bandpath:str, bandlist:list): \n", " '''\n", " Returns geocube with three bands stacked into one multi-dimensional array.\n", " Parameters:\n", " bandpath (str): Path to bands that should be stacked\n", " bandlist (list): Three bands that should be stacked\n", " Returns:\n", " bandStack (xarray Dataset): Geocube with stacked bands\n", " crs (int): Coordinate Reference System corresponding to bands\n", " '''\n", " bandStack = []; bandS = []; bandStack_ = [];\n", " for i,band in enumerate(bandlist):\n", " if i==0:\n", " bandStack_ = xr.open_rasterio(bandpath%band)\n", " crs = pyproj.CRS.to_epsg(pyproj.CRS.from_proj4(bandStack_.crs))\n", " bandStack_ = bandStack_ * bandStack_.scales[0]\n", " bandStack = bandStack_.squeeze(drop=True)\n", " bandStack = bandStack.to_dataset(name='z')\n", " bandStack.coords['band'] = i+1\n", " bandStack = bandStack.rename({'x':'longitude', 'y':'latitude', 'band':'band'})\n", " bandStack = bandStack.expand_dims(dim='band') \n", " else:\n", " bandS = xr.open_rasterio(bandpath%band)\n", " bandS = bandS * bandS.scales[0]\n", " bandS = bandS.squeeze(drop=True)\n", " bandS = bandS.to_dataset(name='z')\n", " bandS.coords['band'] = i+1\n", " bandS = bandS.rename({'x':'longitude', 'y':'latitude', 'band':'band'})\n", " bandS = bandS.expand_dims(dim='band')\n", " bandStack = xr.concat([bandStack, bandS], dim='band')\n", " return bandStack, crs" ] }, { "cell_type": "code", "execution_count": null, "id": "992eeed2", "metadata": {}, "outputs": [], "source": [ "# Creates geocube of stacked bands\n", "da, crs = stack_bands(bandpath, bandlist)\n", "\n", "# Creates basemap\n", "base = gv.tile_sources.EsriTerrain.opts(width=1000, height=1000, padding=0.1)" ] }, { "cell_type": "markdown", "id": "57fbc929", "metadata": {}, "source": [ "## **Band 1: Maximum Vegetation Anomaly Value (VEG_ANOM_MAX)**\n", "***\n", "\n", "**Data Type:** UInt8
\n", "**Description:** Difference between historical and current year observed vegetation cover at the date of maximum decrease, measured on scale from 0-100%
" ] }, { "cell_type": "code", "execution_count": null, "id": "186400ce", "metadata": {}, "outputs": [], "source": [ "base = gv.tile_sources.EsriNatGeo.opts(width=1000, height=1000, padding=0.1)\n", "veg_anom_max = da.z.sel({'band':1})\n", "veg_anom_max.hvplot.image(x='longitude', \n", " y='latitude', \n", " crs=crs, \n", " rasterize=True, \n", " dynamic=True, \n", " aspect='equal', \n", " frame_width=500, \n", " frame_height=500, \n", " cmap='hot_r', \n", " clim=(0,100), alpha=0.8).opts(title=f\"VEG_ANOM_MAX\", xlabel='Longitude', ylabel='Latitude') * base" ] }, { "cell_type": "markdown", "id": "bbde0c7f", "metadata": {}, "source": [ "## **Band 2: Date of Initial Vegetation Disturbance (VEG_DIST_DATE)**\n", "***\n", "\n", "**Data Type:** Int16
\n", "**Description:** Day of first loss anomaly detection in the last year, denoted as the number of days since December 31st, 2020.
" ] }, { "cell_type": "code", "execution_count": null, "id": "05b64128", "metadata": {}, "outputs": [], "source": [ "veg_dist_date = da.z.sel({'band':2})\n", "veg_dist_date.hvplot.image(x='longitude', \n", " y='latitude', \n", " crs=crs, \n", " rasterize=True, \n", " dynamic=True, \n", " aspect='equal', \n", " frame_width=500, \n", " frame_height=500, \n", " cmap='hot_r', \n", " alpha=0.8).opts(title=f\"VEG_DIST_DATE\", xlabel='Longitude', ylabel='Latitude',clim=(0, 592)) * base\n" ] }, { "cell_type": "markdown", "id": "2b6c8690", "metadata": {}, "source": [ "## **Band 3: Vegetation Disturbance Status (VEG_DIST_STATUS)**\n", "***\n", "\n", "**Data Type:** UInt8
\n", "**Description:** Indication of vegetation cover loss (vegetation disturbance); \"provisional\" is used from the first detection until vegetation disturbance is detected for consecutive number of HLS scenes, when it is then labeled \"confirmed.\"
" ] }, { "cell_type": "code", "execution_count": null, "id": "337fc6d6", "metadata": { "scrolled": false }, "outputs": [], "source": [ "veg_dist_status = da.z.sel({'band':3})\n", "veg_dist_status.hvplot.image(x='longitude', \n", " y='latitude', \n", " crs=crs, \n", " rasterize=True, \n", " dynamic=True, \n", " aspect='equal', \n", " frame_width=500, \n", " frame_height=500, \n", " cmap='hot_r', \n", " alpha=0.8).opts(title=f\"VEG_DIST_STATUS\", clim=(0,4), colorbar_opts={'ticker': FixedTicker(ticks=[0, 1, 2, 3, 4])}, xlabel='Longitude', ylabel='Latitude') * base\n" ] }, { "cell_type": "markdown", "id": "4c2316e6", "metadata": {}, "source": [ "**Layer Values:**
\n", "* **0:** No disturbance
\n", "* **1:** Provisional (**first detection**) Disturbance with vegetation cover change <50%
\n", "* **2:** Confirmed (**recurrent detection**) Disturbance with vegetation cover change < 50%
\n", "* **3:** Provisional Disturbance with vegetation cover change ≥ 50%
\n", "* **4:** Confirmed Disturbance with vegetation cover change ≥ 50%
" ] }, { "cell_type": "code", "execution_count": null, "id": "94759a53", "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.7.12" } }, "nbformat": 4, "nbformat_minor": 5 }