{ "cells": [ { "cell_type": "markdown", "id": "a23add21-c317-473e-b8c5-bc438ad60d3c", "metadata": {}, "source": [ "# Visualizing Rasters with MosaicJSON using stac_ipyleaflet\n", "\n", "Authors: Grace Llewellyn (JPL), Emma Paz (Development Seed)\n", "\n", "Date: September 26, 2023\n", "\n", "Description: In this notebook, we create a mosaic from file URLs using MosaicJSON, upload it to MAAP's TiTiler, and then visualize the mosaic using stac_ipyleaflet." ] }, { "cell_type": "markdown", "id": "22a602a9", "metadata": {}, "source": [ "## Run This Notebook\n", "To access and run this tutorial within MAAP's Algorithm Development Environment (ADE), please refer to the [\"Getting started with the MAAP\"](https://docs.maap-project.org/en/latest/getting_started/getting_started.html) section of our documentation.\n", "\n", "Disclaimer: Currently, the stac_ipyleaflet works within [this Algorithm Development Environment](https://ade.maap-project.org/). It is recommended to run this tutorial within MAAP's ADE **Pangeo workspace**, which already includes the stac_ipyleaflet package." ] }, { "cell_type": "markdown", "id": "9197b116-6f5e-4c17-a8ce-29f72539f4dc", "metadata": {}, "source": [ "## Additional Resources\n", "- stac_ipyleaflet DOI: [10.5281/zenodo.10015863](https://doi.org/10.5281/zenodo.10015863)\n", "- [TiTiler API Documentation - MosaicJSON](https://titiler.maap-project.org/docs#/MosaicJSON)\n", "- [Github - MosaicJSON](https://github.com/developmentseed/mosaicjson-spec)" ] }, { "cell_type": "markdown", "id": "32340fe6-9eeb-4db2-b754-67cf1ec5ca40", "metadata": {}, "source": [ "## Import and Install Packages\n", "To import and install the packages necessary for the notebook, run the following cell." ] }, { "cell_type": "code", "execution_count": null, "id": "51474524", "metadata": { "tags": [] }, "outputs": [], "source": [ "%pip install cogeo_mosaic --quiet\n", "\n", "import os\n", "import boto3\n", "import httpx\n", "from cogeo_mosaic.mosaic import MosaicJSON\n", "from shapely.geometry import box\n", "from ipyleaflet import TileLayer\n", "import stac_ipyleaflet" ] }, { "cell_type": "markdown", "id": "3b54a52e-c237-4aad-8d8e-a1e41c24ba01", "metadata": {}, "source": [ "## Setup\n", "First, we'll set variables and helper functions, including setting the MAAP TiTiler endpoint." ] }, { "cell_type": "code", "execution_count": 2, "id": "c01bd236-1fd7-4fd1-8d26-1a8cef645934", "metadata": { "tags": [] }, "outputs": [], "source": [ "titiler_url = \"https://titiler.maap-project.org\"\n", "min_zoom = 0\n", "max_zoom = 0\n", "band_min = 0\n", "band_max = 4000\n", "user = os.getenv('CHE_WORKSPACE_NAMESPACE')\n", "bucket = \"maap-ops-workspace\"\n", "color_map = \"gist_earth_r\"" ] }, { "cell_type": "code", "execution_count": 3, "id": "0cffd02e-87bb-46af-be38-3874d38161b3", "metadata": { "tags": [] }, "outputs": [], "source": [ "def checkFilePath(file_path):\n", " result = s3.list_objects(Bucket=bucket, Prefix=file_path)\n", " exists = True if 'Contents' in result else False\n", " if exists:\n", " print('PATH EXISTS')\n", " return [i for i in result['Contents'] if i[\"Key\"].endswith('.tif')]\n", " return exists" ] }, { "cell_type": "markdown", "id": "9b2fe552-77c1-41cf-9a72-08b2ccbe7356", "metadata": {}, "source": [ "## Find Data Location\n", "Next, we'll find the location for our data. Sample files are provided in an S3 bucket, but code is also provided for you to uncomment if you will be using your own files." ] }, { "cell_type": "code", "execution_count": 4, "id": "caecfc5d-8066-4a4f-a2ad-7d25d44eb1e8", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['s3://maap-ops-workspace/shared/alexdevseed/landsat8/viz/Landsat8_30542_comp_cog_2015-2020_dps.tif', 's3://maap-ops-workspace/shared/alexdevseed/landsat8/viz/Landsat8_30543_comp_cog_2015-2020_dps.tif']\n" ] } ], "source": [ "path = \"/local/path/to/files/\" # \"Path to directory with rasters:\", Right-click on the directory and select option to Copy Path\n", "\n", "s3 = boto3.client('s3')\n", "file_name = path.split('/', 1)[-1]\n", "if 'shared-buckets' in path:\n", " file_path = f'shared/{file_name}'\n", "if 'my-private-bucket' in path:\n", " file_path = f'{user}/{file_name}'\n", "if 'my-public-bucket' in path:\n", " file_path = f'shared/{user}/{file_name}'\n", "\n", "\n", "# If using your files \n", "#if file_path:\n", "# paths = checkFilePath(file_path)\n", "# files = [ i['Key'] for i in paths ]\n", "\n", "# Our sample files \n", "files = ['s3://maap-ops-workspace/shared/alexdevseed/landsat8/viz/Landsat8_30542_comp_cog_2015-2020_dps.tif', 's3://maap-ops-workspace/shared/alexdevseed/landsat8/viz/Landsat8_30543_comp_cog_2015-2020_dps.tif']\n", "\n", "print(files)" ] }, { "cell_type": "markdown", "id": "0b5471ea-d325-41dd-aba7-4ea43c77e44a", "metadata": { "tags": [] }, "source": [ "## Get Raster Info\n", "Let's go ahead and get our raster information which will consist of bounds, zoom, data type, and bands. This information will be pulled from the first file in an array of files." ] }, { "cell_type": "code", "execution_count": 5, "id": "59e24eea-fb64-448a-8c92-6add5cf56539", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bounds: [-117.10749852280769, 50.78795362739066, -116.50936927974429, 51.16389512140189]\n", "Zoom: 9 min = 9 max = 12\n", "Data type: float32\n", "Bands: [['b1', {}], ['b2', {}], ['b3', {}], ['b4', {}], ['b5', {}], ['b6', {}], ['b7', {}], ['b8', {}], ['b9', {}], ['b10', {}], ['b11', {}], ['b12', {}], ['b13', {}], ['b14', {}], ['b15', {}], ['b16', {}], ['b17', {}], ['b18', {}]]\n" ] } ], "source": [ "url = files[0]\n", "\n", "r = httpx.get(\n", " f\"{titiler_url}/cog/info\",\n", " params = {\n", " \"url\": url,\n", " }\n", ").json()\n", "\n", "if r.get('count') > 0:\n", " bounds = r.get(\"bounds\")\n", " min_zoom = r.get(\"minzoom\")\n", " max_zoom = r.get(\"maxzoom\")\n", " zoom = min_zoom + 1 if min_zoom == 0 else min_zoom\n", " bands = r.get(\"band_metadata\")\n", "\n", " print(\"Bounds:\", bounds)\n", " print(\"Zoom:\", zoom, \"min =\", min_zoom, \"max =\", max_zoom)\n", " print(\"Data type:\", r.get(\"dtype\"))\n", " print(\"Bands:\", bands)" ] }, { "cell_type": "markdown", "id": "497dcdce-a926-44d6-8c26-7ea68a87b8b0", "metadata": { "tags": [] }, "source": [ "## Create Mosaic\n", "Using MosaicJSON, we'll create a mosaic from the file urls." ] }, { "cell_type": "code", "execution_count": 6, "id": "0ab5f576-cdf8-4494-a7ae-8110d9f583d3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Mosaic created!\n" ] } ], "source": [ "if min_zoom == 0 and max_zoom == 0:\n", " print(\"Warning: missing zoom attributes!\")\n", " \n", "mosaicdata = MosaicJSON.from_urls(files, minzoom=min_zoom, maxzoom=max_zoom)\n", "print(\"Mosaic created!\")" ] }, { "cell_type": "markdown", "id": "3493ad6e-2d4b-4835-b3b3-32c11cfce6ea", "metadata": { "tags": [] }, "source": [ "## Upload the MosaicJSON \n", "Next, let's upload the MosaicJSON to the TiTiler..." ] }, { "cell_type": "code", "execution_count": 7, "id": "3bf7f0b2-e610-4568-86d0-1671e8a241c4", "metadata": {}, "outputs": [], "source": [ "mosaic_links = httpx.post(\n", " url=f\"{titiler_url}/mosaics\",\n", " headers={\n", " \"Content-Type\": \"application/vnd.titiler.mosaicjson+json\",\n", " },\n", " json=mosaicdata.model_dump(exclude_none=True),\n", ").json()\n", "# print(mosaic_links)" ] }, { "cell_type": "markdown", "id": "63d9564e-477c-45fd-823d-f72f6c3fe3ae", "metadata": {}, "source": [ "... and then filter for tilejson results so we get the tilejson endpoint." ] }, { "cell_type": "code", "execution_count": 8, "id": "81fc3e8d-2bcb-4bde-9382-626c5bfd756d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'https://titiler.maap-project.org/mosaics/9e6ff907-589b-42b3-9e5b-46d455af5afe/tilejson.json'" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tilejson_object = list(\n", " filter(lambda x: x.get(\"rel\") == \"tilejson\", dict(mosaic_links)[\"links\"])\n", ")\n", "tilejson_url = tilejson_object[0][\"href\"]\n", "tilejson_url" ] }, { "cell_type": "markdown", "id": "f4005232-2561-422a-be0f-b19588d00c25", "metadata": { "tags": [] }, "source": [ "## Calculate Raster Center \n", "For this next step, we'll calculate the raster center for map placement." ] }, { "cell_type": "code", "execution_count": 9, "id": "20a4dea3-9800-4e17-8f01-0eced8187f0d", "metadata": {}, "outputs": [], "source": [ "r = httpx.get(tilejson_url).json()\n", "center_data = r.get('center')\n", "center = (center_data[1], center_data[0])\n", "zoom = center_data[2]" ] }, { "cell_type": "code", "execution_count": 10, "id": "8594aa55-19b6-40d1-a0c3-458db8ed6c95", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Center: (50.97592437439628, -116.80843390127599)\n" ] } ], "source": [ "polygon = box(*bounds)\n", "center = (polygon.centroid.y, polygon.centroid.x)\n", "print(\"Center:\", center)" ] }, { "cell_type": "markdown", "id": "ea64f4f8-a969-4f18-b7e1-3e1328094f57", "metadata": { "tags": [] }, "source": [ "## Create the TileLayer\n", "Before visualization, we'll set our parameters and create our tile layer." ] }, { "cell_type": "code", "execution_count": 11, "id": "28aa16b6-169e-4e18-9974-3c7adc3d4dbe", "metadata": {}, "outputs": [], "source": [ "params = {\n", " \"return_mask\": \"true\",\n", " \"rescale\": f\"{band_min}, {band_max}\",\n", " \"bidx\": \"1\",\n", " \"colormap_name\": \"viridis\"\n", "}\n", "\n", "r = httpx.get(tilejson_url, params=params).json()\n", "\n", "layer_url = r['tiles'][0]\n", "custom_layer = TileLayer(url=layer_url, show_loading=True, transparent=True)" ] }, { "cell_type": "markdown", "id": "6e62e0c8-c2e7-4206-9ec6-3ba0f3cb394b", "metadata": {}, "source": [ "## Add the Mosaic Tile Layer to the Map\n", "Finally, using stac_ipyleaflet, we'll set our map center and zoom and then add our mosaic tile layer. " ] }, { "cell_type": "code", "execution_count": null, "id": "5baf3795-3ba2-4886-beea-9caba02e851f", "metadata": { "tags": [] }, "outputs": [], "source": [ "m = stac_ipyleaflet.StacIpyleaflet(zoom=zoom, center=center)\n", "m.add_layer(custom_layer)\n", "m" ] }, { "cell_type": "markdown", "id": "68fa2dec", "metadata": {}, "source": [ "![stac_ipyleaflet added mosaic](../../_static/stac_ipyleaflet_add_mosaic.png)" ] } ], "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.10.13" }, "vscode": { "interpreter": { "hash": "5c7b89af1651d0b8571dde13640ecdccf7d5a6204171d6ab33e7c296e100e08a" } } }, "nbformat": 4, "nbformat_minor": 5 }