diff --git a/demo/voila_demo.ipynb b/demo/voila_demo.ipynb index aee2976..5bea1f4 100644 --- a/demo/voila_demo.ipynb +++ b/demo/voila_demo.ipynb @@ -85,8 +85,10 @@ "source": [ "# load the necessary packages\n", "from io import BytesIO\n", - "from sliderule import icesat2, ipysliderule, io, sliderule\n", + "from sliderule import earthdata, icesat2, ipysliderule, io, sliderule\n", "import ipywidgets as widgets\n", + "from ipyleaflet import WidgetControl\n", + "from concurrent.futures import ThreadPoolExecutor\n", "import geopandas\n", "import logging\n", "import base64\n", @@ -129,8 +131,6 @@ "update_button = widgets.Button(description=\"Update Map\")\n", "run_button = widgets.Button(description=\"Run SlideRule!\")\n", "run_output = widgets.Output()\n", - "refresh_button = widgets.Button(description=\"Refresh Plot\")\n", - "refresh_output = widgets.Output()\n", "download_output = widgets.Output()\n", "download_atl06_button = widgets.Button(description=\"Download File\")\n", "download_atl03_button = widgets.Button(description=\"Download File\")\n", @@ -230,6 +230,21 @@ "%matplotlib inline\n", "granule_count = 0\n", "\n", + "# plot controls on map\n", + "points_dropdown = widgets.Dropdown(\n", + " options = [\"10K\", \"100K\", \"all\"],\n", + " value = \"10K\",\n", + " description = \"Pts to Draw\",\n", + " disabled = False,\n", + ")\n", + "plot_controls = SRwidgets.VBox([\n", + " SRwidgets.variable,\n", + " SRwidgets.cmap,\n", + " points_dropdown,\n", + " SRwidgets.reverse\n", + "])\n", + "widget_control = WidgetControl(widget=plot_controls, position='bottomright')\n", + "\n", "# callbacks for events and exceptions\n", "def demo_logeventrec(rec):\n", " # print(f'{rec[\"attr\"]} \\r', end=\"\")\n", @@ -280,6 +295,7 @@ "\n", " # clear existing geodataframe results\n", " elevations = [sliderule.emptyframe()]\n", + " sliderule.logger.warning('No valid regions to run') if not m.regions else None\n", "\n", " # for each region of interest\n", " for poly in m.regions:\n", @@ -295,7 +311,7 @@ "\n", "# run sliderule action\n", "def on_run_clicked(b):\n", - " global atl06_rsps, points_dropdown\n", + " global atl06_rsps, widget_control, points_dropdown, stride\n", " with run_output:\n", " print(f'SlideRule processing request... initiated\\r', end=\"\")\n", " perf_start = time.perf_counter()\n", @@ -307,15 +323,16 @@ " if points_dropdown.value == \"100K\":\n", " max_plot_points = 100000\n", " elif points_dropdown.value == \"all\":\n", - " max_plot_points = 1000000000\n", - " if max_plot_points > atl06_rsps.shape[0]:\n", - " max_plot_points = atl06_rsps.shape[0]\n", - " print(f'Plotting {max_plot_points} of {atl06_rsps.shape[0]} elevations. This may take 10-60+ seconds for larger point datasets.')\n", + " max_plot_points = np.inf\n", + " # limit number of points to plot\n", + " plot_points = np.minimum(max_plot_points, atl06_rsps.shape[0])\n", + " stride = int(atl06_rsps.shape[0]//plot_points)\n", + " print(f'Plotting {plot_points} of {atl06_rsps.shape[0]} elevations. This may take 10-60+ seconds for larger point datasets.')\n", " fields = atl06_rsps.leaflet.default_atl06_fields()\n", " atl06_rsps.leaflet.GeoData(m.map,\n", " column_name=SRwidgets.variable.value,\n", " cmap=SRwidgets.colormap,\n", - " max_plot_points=max_plot_points,\n", + " stride=stride,\n", " tooltip=True,\n", " colorbar=True,\n", " fields=fields\n", @@ -324,58 +341,96 @@ " atl06_rsps.leaflet.set_observables(SRwidgets)\n", " atl06_rsps.leaflet.add_selected_callback(SRwidgets.atl06_click_handler)\n", " m.add_region_callback(atl06_rsps.leaflet.handle_region)\n", + " # add plot controls to map\n", + " m.map.add(widget_control)\n", "\n", "# refresh action\n", - "def on_refresh_clicked(b):\n", - " global atl06_rsps\n", - " with refresh_output:\n", - " if atl06_rsps is not None and atl06_rsps.shape[0] > 0:\n", + "def refresh_leaflet_draw(sender):\n", + " global atl06_rsps, points_dropdown, stride\n", + " if atl06_rsps is not None and atl06_rsps.shape[0] > 0:\n", + " if isinstance(sender['new'], str) and (sender['new'] == \"10K\"):\n", " max_plot_points = 10000\n", - " if points_dropdown.value == \"100K\":\n", - " max_plot_points = 100000\n", - " elif points_dropdown.value == \"all\":\n", - " max_plot_points = 1000000000\n", - " if max_plot_points > atl06_rsps.shape[0]:\n", - " max_plot_points = atl06_rsps.shape[0]\n", - " print(f'Plotting {max_plot_points} of {atl06_rsps.shape[0]} elevations. This may take 10-60+ seconds for larger point datasets.')\n", - " fields = atl06_rsps.leaflet.default_atl06_fields()\n", - " atl06_rsps.leaflet.GeoData(m.map,\n", - " column_name=SRwidgets.variable.value,\n", - " cmap=SRwidgets.colormap,\n", - " max_plot_points=max_plot_points,\n", - " tooltip=True,\n", - " colorbar=True,\n", - " fields=fields\n", - " )\n", - " # install handlers and callbacks\n", - " atl06_rsps.leaflet.set_observables(SRwidgets)\n", - " atl06_rsps.leaflet.add_selected_callback(SRwidgets.atl06_click_handler)\n", - " m.add_region_callback(atl06_rsps.leaflet.handle_region)\n", + " elif isinstance(sender['new'], str) and (sender['new'] == \"100K\"):\n", + " max_plot_points = 100000\n", + " elif isinstance(sender['new'], str) and (sender['new'] == \"all\"):\n", + " max_plot_points = np.inf\n", + " else:\n", + " return\n", + " # limit number of points to plot\n", + " plot_points = np.minimum(max_plot_points, atl06_rsps.shape[0])\n", + " stride_new = int(atl06_rsps.shape[0]//plot_points)\n", + " if (stride_new == stride):\n", + " return\n", + " # update stride\n", + " stride = stride_new\n", + " print(f'Plotting {plot_points} of {atl06_rsps.shape[0]} elevations. This may take 10-60+ seconds for larger point datasets.')\n", + " # sliced geodataframe for plotting\n", + " atl06_rsps.leaflet._gdf_selected = atl06_rsps.leaflet._gdf[slice(None,None,stride)]\n", + " atl06_rsps.leaflet._gdf_selected['data'] = atl06_rsps.leaflet._gdf_selected[atl06_rsps.leaflet.column_name]\n", + " atl06_rsps.leaflet.redraw()\n", + "\n", + "def handle_plot_controls(obj, action, geo_json, **kwargs):\n", + " global widget_control\n", + " # remove any prior instances of plot_controls\n", + " if (action == 'deleted'):\n", + " try:\n", + " m.map.remove(widget_control)\n", + " except Exception as exc:\n", + " pass\n", + " try:\n", + " SRwidgets.variable.unobserve(atl06_rsps.leaflet.set_column_name)\n", + " except Exception as exc:\n", + " pass\n", + " try:\n", + " SRwidgets.cmap.unobserve(atl06_rsps.leaflet.set_colormap)\n", + " except Exception as exc:\n", + " pass\n", + " try:\n", + " SRwidgets.reverse.unobserve(atl06_rsps.leaflet.set_colormap)\n", + " except Exception as exc:\n", + " pass\n", + "\n", + "# # install handler for plot controls\n", + "m.draw_control.on_draw(handle_plot_controls)\n", "\n", "# show code action\n", + "global code06_is_shown\n", + "code06_is_shown = False\n", "def on_show_code06_clicked(b):\n", - " global url_textbox, atl06_parms\n", + " global url_textbox, atl06_parms, code06_is_shown\n", " with show_code06_output:\n", " display.clear_output()\n", - " print(f'icesat2.init()')\n", - " # validate boolean entries to be in title case\n", - " atl06_json = json.dumps(atl06_parms, indent=4)\n", - " atl06_json = re.sub(r'\\b(true|false)', lambda m: m.group(1).title(), atl06_json)\n", - " print('parms = ', atl06_json, sep='')\n", - " print('gdf = icesat2.atl06p(parms)')\n", - "\n", - "# Download ATL06-SR data as geojson\n", + " code06_is_shown = not code06_is_shown\n", + " if code06_is_shown:\n", + " print(f'icesat2.init()')\n", + " # validate boolean entries to be in title case\n", + " atl06_json = json.dumps(atl06_parms, indent=4)\n", + " atl06_json = re.sub(r'\\b(true|false)', lambda m: m.group(1).title(), atl06_json)\n", + " print('parms = ', atl06_json, sep='')\n", + " print('gdf = icesat2.atl06p(parms)')\n", + " show_code06_button.description = \"Hide Code\"\n", + " else:\n", + " show_code06_button.description = \"Show Code\"\n", + "\n", + "\n", + "# Download data to file\n", "display.display(download_output)\n", - "#SRwidgets.file_format.value = 'geoparquet'\n", "def download_file(gdf, filename, mime_type='text/json'):\n", " if (mime_type == 'text/json'):\n", " content = base64.b64encode(gdf.to_json().encode()).decode()\n", " elif (mime_type == 'text/csv'):\n", - " content = base64.b64encode(gdf.to_csv().encode()).decode()\n", + " df1 = gdf.reset_index().rename(columns={\"index\":\"time\"}).drop(columns=['geometry'])\n", + " df2 = geopandas.pd.DataFrame(\n", + " np.c_[gdf.geometry.x.values, gdf.geometry.y.values],\n", + " columns=['longitude','latitude']\n", + " )\n", + " df = geopandas.pd.concat([df1, df2], axis=1)\n", + " content = base64.b64encode(df.to_csv(index=False).encode()).decode()\n", " elif (mime_type == 'application/vnd.apache.parquet'):\n", " fid = BytesIO()\n", " parms = copy.copy(atl06_parms)\n", " version = sliderule.get_version()\n", + " parms['lineage'] = dict(short_name='ATL03', version=earthdata.__get_version(\"ATL03\"))\n", " parms['version'] = version['icesat2']['version']\n", " parms['commit'] = version['icesat2']['commit']\n", " io.to_parquet(gdf, fid, parameters=parms, regions=m.regions)\n", @@ -393,14 +448,18 @@ " display.display(display.HTML(f''))\n", "\n", "def on_atl06_download_clicked(e=None):\n", - " download_file(atl06_rsps, SRwidgets.atl06_filename,\n", - " mime_type=SRwidgets.mime_type)\n", + " with ThreadPoolExecutor(max_workers=1) as executor:\n", + " future = executor.submit(download_file,\n", + " atl06_rsps, SRwidgets.atl06_filename,\n", + " mime_type=SRwidgets.mime_type)\n", + " future.result()\n", "\n", "# link buttons\n", "run_button.on_click(on_run_clicked)\n", - "refresh_button.on_click(on_refresh_clicked)\n", "show_code06_button.on_click(on_show_code06_clicked)\n", - "download_atl06_button.on_click(on_atl06_download_clicked)" + "download_atl06_button.on_click(on_atl06_download_clicked)\n", + "# observe points to plot\n", + "points_dropdown.observe(refresh_leaflet_draw)" ] }, { @@ -423,14 +482,6 @@ }, "outputs": [], "source": [ - "# points to plot drop down\n", - "points_dropdown = widgets.Dropdown(\n", - " options = [\"10K\", \"100K\", \"all\"],\n", - " value = \"10K\",\n", - " description = \"Pts to Draw\",\n", - " disabled = False,\n", - ")\n", - "\n", "# display widgets for setting SlideRule parameters\n", "display.display(widgets.VBox([\n", " SRwidgets.length,\n", @@ -442,16 +493,12 @@ " SRwidgets.count,\n", " SRwidgets.window,\n", " SRwidgets.sigma,\n", - " SRwidgets.variable,\n", - " SRwidgets.cmap,\n", - " points_dropdown,\n", - " SRwidgets.reverse,\n", "]))\n", "\n", "# display buttons\n", - "display.display(SRwidgets.HBox([run_button, refresh_button, refresh_output]))\n", + "display.display(run_button)\n", "display.display(SRwidgets.HBox([download_atl06_button, SRwidgets.file_format]))\n", - "display.display(SRwidgets.HBox([show_code06_button, show_code06_output]))\n" + "display.display(show_code06_button, show_code06_output)" ] }, { @@ -564,7 +611,7 @@ "\n", "# photon_cloud action\n", "def on_pc_clicked(b):\n", - " global atl03_rsps, atl06_rsps, elev_dropdown\n", + " global atl03_rsps, atl06_rsps, elev_dropdown, fig\n", " with pc_output:\n", " pc_output.clear_output(True)\n", "\n", @@ -585,11 +632,13 @@ " # start at the first segment\n", " x_offset = atl03_rsps['segment_dist'].min()\n", " # plot ATL03 and ATL06 data\n", + " atl03_plot_kwargs = SRwidgets.plot_kwargs.copy()\n", + " atl03_plot_kwargs.pop('column_name')\n", " atl03_rsps.icesat2.plot(ax=ax, kind='scatter',\n", - " data_type='atl03', cmap=SRwidgets.colormap,\n", + " data_type='atl03', column_name='height', cmap=SRwidgets.colormap,\n", " classification=SRwidgets.plot_classification.value,\n", " x_offset=x_offset, legend=True, legend_frameon=True,\n", - " **SRwidgets.plot_kwargs)\n", + " **atl03_plot_kwargs)\n", " if (elev_dropdown.value == 'enabled'):\n", " atl06_rsps.icesat2.plot(ax=ax, kind='scatter',\n", " data_type='atl06', x_offset=x_offset,\n", @@ -597,8 +646,37 @@ " **SRwidgets.plot_kwargs)\n", " # draw and show plot\n", " plt.show()\n", - " plt.draw()\n", - "\n", + " fig.canvas.draw()\n", + "\n", + "def refresh_ATL03_plot(sender):\n", + " global atl03_rsps, atl06_rsps, elev_dropdown, fig\n", + " if isinstance(sender['new'], str):\n", + " # reset figure and axes\n", + " fig.clear()\n", + " fig.set_facecolor('white')\n", + " fig.canvas.header_visible = False\n", + " ax = fig.add_subplot(111)\n", + " # reset title and labels\n", + " ax.set_title(\"Photon Cloud\")\n", + " ax.set_ylabel('height (m)')\n", + " # start at the first segment\n", + " x_offset = atl03_rsps['segment_dist'].min()\n", + " # plot ATL03 and ATL06 data\n", + " atl03_plot_kwargs = SRwidgets.plot_kwargs.copy()\n", + " atl03_plot_kwargs.pop('column_name')\n", + " atl03_rsps.icesat2.plot(ax=ax, kind='scatter',\n", + " data_type='atl03', column_name='height', cmap=SRwidgets.colormap,\n", + " classification=SRwidgets.plot_classification.value,\n", + " x_offset=x_offset, legend=True, legend_frameon=True,\n", + " **atl03_plot_kwargs)\n", + " if (elev_dropdown.value == 'enabled'):\n", + " atl06_rsps.icesat2.plot(ax=ax, kind='scatter',\n", + " data_type='atl06', x_offset=x_offset,\n", + " legend=True, legend_frameon=True,\n", + " **SRwidgets.plot_kwargs)\n", + " # draw and show plot\n", + " fig.canvas.draw()\n", + " \n", "# create button to display geodataframe\n", "pc_button.on_click(on_pc_clicked)\n", "\n", @@ -614,21 +692,30 @@ "m.add_selected_callback(click_handler)\n", "\n", "# show code action\n", + "global code03_is_shown\n", + "code03_is_shown = False\n", "def on_show_code03_clicked(b):\n", - " global url_textbox, atl03_parms\n", + " global url_textbox, atl03_parms, code03_is_shown\n", " with show_code03_output:\n", " display.clear_output()\n", - " print(f'icesat2.init()')\n", - " # validate boolean entries to be in title case\n", - " atl03_json = json.dumps(atl03_parms, indent=4)\n", - " atl03_json = re.sub(r'\\b(true|false)', lambda m: m.group(1).title(), atl03_json)\n", - " print('parms = ', atl03_json, sep='')\n", - " print('gdf = icesat2.atl03sp(parms)')\n", - "\n", + " code03_is_shown = not code03_is_shown\n", + " if code03_is_shown:\n", + " print(f'icesat2.init()')\n", + " # validate boolean entries to be in title case\n", + " atl03_json = json.dumps(atl03_parms, indent=4)\n", + " atl03_json = re.sub(r'\\b(true|false)', lambda m: m.group(1).title(), atl03_json)\n", + " print('parms = ', atl03_json, sep='')\n", + " print('gdf = icesat2.atl03sp(parms)')\n", + " show_code03_button.description = \"Hide Code\"\n", + " else:\n", + " show_code03_button.description = \"Show Code\"\n", "\n", "def on_atl03_download_clicked(e=None):\n", - " download_file(atl03_rsps, SRwidgets.atl03_filename,\n", - " mime_type=SRwidgets.mime_type)\n", + " with ThreadPoolExecutor(max_workers=1) as executor:\n", + " future = executor.submit(download_file,\n", + " atl03_rsps, SRwidgets.atl03_filename,\n", + " mime_type=SRwidgets.mime_type)\n", + " future.result()\n", "\n", "# install click handler callback\n", "show_code03_button.on_click(on_show_code03_clicked)\n", @@ -638,22 +725,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "extensions": { - "jupyter_dashboards": { - "version": 1, - "views": { - "default_view": { - "col": 0, - "height": 15, - "row": 32, - "width": 3 - } - } - } - }, - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "# elevation plot drop down\n", @@ -672,7 +744,9 @@ "display.display(pc_button)\n", "display.display(pc_output)\n", "display.display(SRwidgets.HBox([download_atl03_button, SRwidgets.file_format]))\n", - "display.display(show_code03_button, show_code03_output)" + "display.display(show_code03_button, show_code03_output)\n", + "SRwidgets.plot_classification.observe(refresh_ATL03_plot)\n", + "elev_dropdown.observe(refresh_ATL03_plot)" ] }, { @@ -717,7 +791,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.0" + "version": "3.10.14" }, "toc-showtags": false }, diff --git a/examples/api_widgets_demo.ipynb b/examples/api_widgets_demo.ipynb index 7a64982..b6301f7 100644 --- a/examples/api_widgets_demo.ipynb +++ b/examples/api_widgets_demo.ipynb @@ -29,7 +29,7 @@ "import warnings\n", "warnings.filterwarnings('ignore') # turn off warnings for demo\n", "\n", - "from sliderule import icesat2, ipysliderule, io, sliderule\n", + "from sliderule import earthdata, icesat2, ipysliderule, io, sliderule\n", "import geopandas\n", "import logging\n", "\n", @@ -369,6 +369,10 @@ "version = sliderule.get_version()\n", "parms['version'] = version['icesat2']['version']\n", "parms['commit'] = version['icesat2']['commit']\n", + "parms['lineage'] = dict(\n", + " short_name='ATL03',\n", + " version=earthdata.__get_version(\"ATL03\")\n", + ")\n", "# save to file in format (HDF5 or netCDF)\n", "io.to_file(gdf, SRwidgets.file,\n", " format=SRwidgets.format,\n", @@ -474,7 +478,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.14" } }, "nbformat": 4, diff --git a/examples/atl03_widgets_demo.ipynb b/examples/atl03_widgets_demo.ipynb index a9de334..1eb239e 100644 --- a/examples/atl03_widgets_demo.ipynb +++ b/examples/atl03_widgets_demo.ipynb @@ -39,7 +39,7 @@ "# turn off warnings for demo\n", "warnings.filterwarnings('ignore')# autoreload\n", "\n", - "from sliderule import icesat2, ipysliderule, sliderule, io, earthdata\n", + "from sliderule import earthdata, icesat2, ipysliderule, io, sliderule\n", "import geopandas\n", "import logging\n", "\n", @@ -404,6 +404,10 @@ "version = sliderule.get_version()\n", "parms['version'] = version['icesat2']['version']\n", "parms['commit'] = version['icesat2']['commit']\n", + "parms['lineage'] = dict(\n", + " short_name='ATL03',\n", + " version=earthdata.__get_version(\"ATL03\")\n", + ")\n", "# save to file in format (HDF5 or netCDF)\n", "io.to_file(gdf, SRwidgets.file,\n", " format=SRwidgets.format,\n", @@ -509,7 +513,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.14" } }, "nbformat": 4, diff --git a/examples/atl06_atl08_demo.ipynb b/examples/atl06_atl08_demo.ipynb new file mode 100644 index 0000000..bb503c1 --- /dev/null +++ b/examples/atl06_atl08_demo.ipynb @@ -0,0 +1,333 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SlideRule: Interactive Tutorial\n", + "## Combining ICESat-2 ATL06 and ATL08 with Landsat outputs\n", + "\n", + "Demonstrates the use of the SlideRule science data processing service for ICESat-2 applications\n", + "\n", + "### See examples, documentation and more at [slideruleearth.io](https://slideruleearth.io/)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SlideRule APIs\n", + "\n", + "- `sliderule` - the core module\n", + "- `earthdata` - functions that access CMR (NASA's Common Metadata Repository)\n", + "- `h5` - APIs for directly reading HDF5 and NetCD4 data\n", + "- `raster` - APIs for sampling supported raster datasets\n", + "- `icesat2` - APIs for processing ICESat-2 data\n", + "- `gedi` - APIs for processing GEDI data\n", + "- `io` - functions for reading and writing local files with SlideRule results\n", + "- `ipysliderule` - functions for building interactive Jupyter notebooks that interface to SlideRule\n", + "\n", + "### Import and Configure SlideRule" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import warnings\n", + "import geopandas\n", + "import ipyleaflet\n", + "import matplotlib.pyplot as plt\n", + "from sliderule import earthdata, icesat2, ipysliderule, io, sliderule\n", + "warnings.filterwarnings('ignore') # turn off warnings for demo\n", + "\n", + "# set the url for the sliderule service\n", + "icesat2.init(\"slideruleearth.io\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set options for making science data processing requests to SlideRule\n", + "\n", + "SlideRule ICESat-2 APIs follow streamlined versions of the [ATL06 land ice height](https://nsidc.org/sites/default/files/documents/technical-reference/icesat2_atl03_atbd_v006.pdf) and [ATL08 land and vegetation height](https://nsidc.org/sites/default/files/documents/technical-reference/icesat2_atl08_atbd_v006_0.pdf) algorithms.\n", + "\n", + "SlideRule can use different sources for photon classification before calculating the average segment height. \n", + "This is useful for example, in cases where there may be a vegetated canopy affecting the spread of the photon returns.\n", + "- ATL03 photon confidence values, based on algorithm-specific classification types for land, ocean, sea-ice, land-ice, or inland water\n", + "- [ATL08 land and vegetation](https://nsidc.org/data/atl08) photon classification\n", + "- YAPC (Yet Another Photon Classification) photon-density-based classification" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# display widgets for setting SlideRule parameters\n", + "SRwidgets = ipysliderule.widgets()\n", + "SRwidgets.set_atl08_defaults()\n", + "SRwidgets.VBox(SRwidgets.atl08())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Select regions of interest for submitting to SlideRule\n", + "\n", + "Create polygons or bounding boxes to set our regions of interest. \n", + "This map is also our viewer for inspecting our SlideRule ICESat-2 data returns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# create ipyleaflet map in specified projection\n", + "m = ipysliderule.leaflet('Global', center=(39, -108), zoom=11)\n", + "SRwidgets.variable.options = ['h_mean', 'h_sigma', 'h_canopy',\n", + " 'h_min_canopy', 'h_mean_canopy', 'h_max_canopy',\n", + " 'canopy_openness', 'h_te_median',\n", + " 'landcover', 'snowcover', 'solar_elevation',\n", + " 'dh_fit_dx', 'dh_fit_dy', 'rms_misfit',\n", + " 'w_surface_window_final', 'landsat.value',\n", + " 'delta_time', 'cycle', 'rgt']\n", + "plot_controls = SRwidgets.VBox([SRwidgets.variable,SRwidgets.cmap,SRwidgets.reverse])\n", + "widget_control = ipyleaflet.WidgetControl(widget=plot_controls, position='bottomright')\n", + "m.map.add(widget_control)\n", + "m.map" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Build and transmit requests to SlideRule\n", + "\n", + "- SlideRule will query the [NASA Common Metadata Repository (CMR)](https://cmr.earthdata.nasa.gov/) for ATL03 data within our region of interest\n", + "- ICESat-2 ATL03 data are then accessed from the NSIDC AWS s3 bucket in `us-west-2`\n", + "- The ATL03 granules is spatially subset within SlideRule to our exact region of interest\n", + "- SlideRule then uses our specified parameters to calculate average height segments from the ATL03 data in parallel\n", + "- The completed data is streamed concurrently back and combined into a `geopandas` `GeoDataFrame` within the Python client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%time\n", + "# build sliderule parameters using latest values from widget\n", + "atl08_parms = SRwidgets.build_atl08()\n", + "\n", + "# add a sampling request to access Landsat data\n", + "samples = {\n", + " \"landsat\": {\n", + " \"asset\": \"landsat-hls\",\n", + " \"closest_time\": \"2022-01-05T00:00:00Z\", \n", + " \"bands\": [\"NDVI\"]\n", + " }, \n", + "}\n", + "\n", + "# clear existing geodataframe results\n", + "elevations = [sliderule.emptyframe()]\n", + "\n", + "# for each region of interest\n", + "sliderule.logger.warning('No valid regions to run') if not m.regions else None\n", + "for poly in m.regions:\n", + " # add polygon from map to sliderule parameters\n", + " atl08_parms[\"poly\"] = poly\n", + " # get the HLS catalog for the region\n", + " catalog = earthdata.stac(short_name=\"HLS\", polygon=poly,\n", + " time_start=\"2022-01-01T00:00:00Z\", time_end=\"2022-03-01T00:00:00Z\",\n", + " as_str=True)\n", + " # make the request to the SlideRule (ATL08-SR and ATL06-SR) endpoint\n", + " # and pass it the request parameters to request ATL03 Data\n", + " # keep_id is set to True to keep track of the extent_id\n", + " atl08 = icesat2.atl08p(atl08_parms, keep_id=True)\n", + " # make the request to the SlideRule (ATL08-SR and ATL06-SR) endpoint\n", + " atl06_parms = atl08_parms.copy()\n", + " atl06_parms[\"samples\"] = samples\n", + " atl06 = icesat2.atl06p(atl06_parms, keep_id=True).drop(columns=['geometry'])\n", + " # skip iteration if there is an empty dataframe\n", + " if atl06.empty or atl08.empty:\n", + " continue\n", + " # merge dataframes if there are valid values\n", + " merged = geopandas.pd.merge(atl08, atl06, on='extent_id',\n", + " how='left', suffixes=(None,'.atl06')).set_axis(atl08.index)\n", + " elevations.append(merged)\n", + "# concatenate the results into a single geodataframe\n", + "gdf = geopandas.pd.concat(elevations).dropna()\n", + "# clear elevations variable to save memory\n", + "elevations = None\n", + "\n", + "# add the results to the map\n", + "%matplotlib inline\n", + "if gdf.empty:\n", + " sliderule.logger.warning('No valid SlideRule returns')\n", + "else:\n", + " # ATL06-SR and ATL08-SR fields for hover tooltip\n", + " fields = ['h_mean', 'h_sigma', 'h_canopy',\n", + " 'h_min_canopy', 'h_mean_canopy', 'h_max_canopy',\n", + " 'landsat.value']\n", + " gdf.leaflet.GeoData(m.map, column_name=SRwidgets.variable.value,\n", + " cmap=SRwidgets.colormap, max_plot_points=10000,\n", + " tooltip=True, colorbar=True, fields=fields)\n", + " # install handlers and callbacks\n", + " gdf.leaflet.set_observables(SRwidgets)\n", + " gdf.leaflet.add_selected_callback(SRwidgets.atl06_click_handler)\n", + " m.add_region_callback(gdf.leaflet.handle_region)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Review tabular output\n", + "Can inspect the columns, number of returns and returns at the top of the `GeoDataFrame`.\n", + "\n", + "See the [SlideRule ICESat-2 API documentation](https://slideruleearth.io/rtd/user_guide/ICESat-2.html#elevations) for descriptions of each column" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f'Returned {gdf.shape[0]} records')\n", + "gdf.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create plots for a single track\n", + "Select a track from the leaflet plot above by clicking on one of the plotted elevations: RGT and Cycle will automatically get populated below" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "SRwidgets.VBox([\n", + " SRwidgets.rgt,\n", + " SRwidgets.ground_track,\n", + " SRwidgets.cycle,\n", + "])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib widget\n", + "fig, ax = plt.subplots(nrows=2, sharex=True)\n", + "# create plots\n", + "plot_kwargs = SRwidgets.plot_kwargs.copy()\n", + "plot_kwargs.pop('column_name')\n", + "gdf.icesat2.plot(ax=ax[0], kind='scatter',\n", + " data_type='atl06', column_name='h_mean', **plot_kwargs)\n", + "gdf.icesat2.plot(ax=ax[1], kind='scatter',\n", + " data_type='atl08', column_name='h_canopy', **plot_kwargs)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "### Save data to output file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "display(SRwidgets.filesaver)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# append sliderule api version to attributes\n", + "version = sliderule.get_version()\n", + "atl08_parms['version'] = version['icesat2']['version']\n", + "atl08_parms['commit'] = version['icesat2']['commit']\n", + "atl08_parms['lineage'] = dict(short_name='ATL03', version=earthdata.__get_version(\"ATL03\"))\n", + "# save to file in format (HDF5 or netCDF)\n", + "io.to_file(gdf, SRwidgets.file,\n", + " format=SRwidgets.format,\n", + " parameters=atl08_parms,\n", + " regions=m.regions,\n", + " verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "interpreter": { + "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" + }, + "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.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}