diff --git a/jupyter-notebooks/workflows/publish-to-arcgis-online/Planet to ArcGIS Image using Requests.ipynb b/jupyter-notebooks/workflows/publish-to-arcgis-online/Planet to ArcGIS Image using Requests.ipynb deleted file mode 100644 index 1a790851..00000000 --- a/jupyter-notebooks/workflows/publish-to-arcgis-online/Planet to ArcGIS Image using Requests.ipynb +++ /dev/null @@ -1,266 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "131cc667", - "metadata": {}, - "outputs": [], - "source": [ - "from arcgis.raster.analytics import copy_raster\n", - "from os import path, listdir, mkdir\n", - "from zipfile import ZipFile\n", - "import azure.storage.blob\n", - "import requests\n", - "import datetime\n", - "import pathlib\n", - "import shutil\n", - "import arcgis\n", - "import config #local python file which is storing credentials to ArcGIS Online and Planet's Platform\n", - "import glob\n", - "import sys\n", - "import os\n", - "\n", - "arcgis.env.verbose = True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "727d826b", - "metadata": {}, - "outputs": [], - "source": [ - "planet_api_key = config.planet_api_key" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c8abec8c", - "metadata": {}, - "outputs": [], - "source": [ - "# Provide an order ID. \n", - "my_order_id = \"INSERT ORDER ID HERE\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d350f06d", - "metadata": {}, - "outputs": [], - "source": [ - "orders_api = 'https://api.planet.com/compute/ops/orders/v2'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "34fe3f80", - "metadata": {}, - "outputs": [], - "source": [ - "def create_planet_session(planet_api_key):\n", - " \n", - " session = requests.Session()\n", - " session.auth = (planet_api_key, \"\")\n", - " \n", - " response = session.get(orders_api)\n", - " \n", - " try:\n", - " \n", - " response = session.get(orders_api)\n", - " \n", - " if response.status_code == 200:\n", - " print(\"Connected to Planet API\")\n", - " return(session)\n", - " \n", - " else:\n", - " print(\"Failed to connect to Planet API\")\n", - " return\n", - " \n", - " except:\n", - " e = sys.exc_info()[0]\n", - " print(\"Error: %s\" % e)\n", - " \n", - "\n", - "def get_order_details(session, order_id):\n", - " \n", - " order_url = orders_api + '/' + order_id\n", - " \n", - " try:\n", - " response = session.get(order_url)\n", - " return response.json()\n", - " except:\n", - " e = sys.exc_info()[0]\n", - " print(\"Error: %s\" % e)\n", - " \n", - "def validate_order(order_json):\n", - "\n", - " #check if the order has been successfully completed, if not, exit the function\n", - " if order_json['state'] != 'success':\n", - " print(\"Order isn't completed yet\")\n", - " return False\n", - " \n", - " #also check that this is 8 band PlanetScope imagery\n", - " if order_json[\"products\"][0][\"product_bundle\"] != \"analytic_8b_sr_udm2\":\n", - " print(\"Order is not 8 band PlanetScope Imagery\")\n", - " return False\n", - " \n", - " return True\n", - "\n", - "def download_results(results, overwrite=False):\n", - " results_urls = [r['location'] for r in results['_links']['results']]\n", - " results_names = [r['name'] for r in results['_links']['results']]\n", - " print('{} items to download'.format(len(results_urls)))\n", - " paths=[]\n", - " \n", - " for url, name in zip(results_urls, results_names):\n", - " path = pathlib.Path(os.path.join('home', 'PlanetDownloads', name))\n", - " \n", - " paths.append(path)\n", - " \n", - " if overwrite or not path.exists():\n", - " print('downloading {} to {}'.format(name, path))\n", - " r = requests.get(url, allow_redirects=True)\n", - " path.parent.mkdir(parents=True, exist_ok=True)\n", - " open(path, 'wb').write(r.content)\n", - " else:\n", - " print('{} already exists, skipping {}'.format(path, name))\n", - " \n", - " zip_files = [x.__str__() for x in paths if x.suffix == \".zip\" ]\n", - " \n", - " unzipped_data_directory = pathlib.Path(os.path.join('home', 'unzipped_data'))#\"/arcgis/home/unzipped_data\"\n", - " \n", - " for zip_file in zip_files:\n", - " z = ZipFile(zip_file)\n", - " z.extractall(path=unzipped_data_directory)\n", - "\n", - " tiff_files = glob.glob(os.path.join(unzipped_data_directory, \"**\\*_SR_*.tif\"), recursive = True)\n", - " return(tiff_files)\n", - " \n", - "def publish_to_arcgis_online(tiff_paths, order_name, gis):\n", - " \n", - " # Create a unique timestamp\n", - " timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')\n", - "\n", - " # Use timestamp and order name to create a unique name for the image service\n", - " layer_name = \"PlanetLabs_\" + order_name[:8] + \"_\" + timestamp\n", - " \n", - " # Publish your images as an image service to ArcGIS Online\n", - " # Note that this can take significant time with large datasets\n", - "\n", - " published_rasters = copy_raster(input_raster=tiff_paths,\n", - " outpute_cellsize = {\"distance\":3.5,\"units\":\"meters\"},\n", - " output_name=layer_name,\n", - " raster_type_name=\"Raster Dataset\",\n", - " context={\"outSR\":{\"wkid\":3857},\n", - " \"resamplingMethod\":\"BILINEAR\",\n", - " \"compression\":\"LERC 0\",\n", - " \"bandMapping\":[{\"bandName\":\"coastal_blue\",\"wavelengthMin\":431,\"wavelengthMax\":452},\n", - " {\"bandName\":\"blue\",\"wavelengthMin\":465,\"wavelengthMax\":515},\n", - " {\"bandName\":\"green_i\",\"wavelengthMin\":513,\"wavelengthMax\":549},\n", - " {\"bandName\":\"green\",\"wavelengthMin\":547,\"wavelengthMax\":583},\n", - " {\"bandName\":\"yellow\",\"wavelengthMin\":600,\"wavelengthMax\":620},\n", - " {\"bandName\":\"red\",\"wavelengthMin\":650,\"wavelengthMax\":680},\n", - " {\"bandName\":\"rededge\",\"wavelengthMin\":697,\"wavelengthMax\":713},\n", - " {\"bandName\":\"nir\",\"wavelengthMin\":845,\"wavelengthMax\":885}\n", - " ],\n", - " \"buildFootprints\":False,\n", - " \"defineNodata\":True,\n", - " \"noDataArguments\":{\"noDataValues\":[0],\n", - " \"compositeValue\":True}\n", - " },\n", - " gis=gis)\n", - " \n", - " return(published_rasters)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bfb5a5a7", - "metadata": {}, - "outputs": [], - "source": [ - "#Create a requests session with Planet API key \n", - "planet_session = create_planet_session(planet_api_key)\n", - "\n", - "#Retrieve order details as JSON\n", - "order_json = get_order_details(planet_session, my_order_id)\n", - "\n", - "#Check if there are zip archives in the order delivery\n", - "zip_archives = [r['name'] for r in order_json['_links']['results'] if r['name'].endswith(\".zip\")]\n", - "\n", - "#if the order meets criteria (8 band planetscope & a completed order)\n", - "if validate_order(order_json) is True:\n", - " \n", - " #if there are zips, unzip and extract\n", - " #then return the tiff file paths\n", - " if len(zip_archives)>0:\n", - " download_paths = download_results(order_json)\n", - " tiff_paths = extract_zip(download_paths)\n", - " \n", - " #else return the tiff paths as URLs to cloud storage\n", - " else:\n", - " tiff_paths = [r['location'] for r in order_json['_links']['results'] if '_SR_' in r['name']]\n", - "\n", - "\n", - "#publish to ArcGIS Online\n", - "gis = arcgis.gis.GIS(url=\"https://www.arcgis.com\", username = config.arcgis_online_username, password = config.arcgis_online_password)\n", - "image_service = publish_to_arcgis_online(tiff_paths, order_json['name'], gis)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "98597e4b", - "metadata": {}, - "outputs": [], - "source": [ - "# view the new imagery layer on an arcgis map\n", - "# this layer can now be added to other maps, analyzed with ArcGIS Raster Analytics tools, and more\n", - "\n", - "my_map = gis.map(location = image_service.extent, zoomlevel = 11)\n", - "my_map.basemap = \"imagery\"\n", - "my_map.add_layer(image_service)\n", - "my_map" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0ec9e1c6", - "metadata": {}, - "outputs": [], - "source": [ - "#delete files after publishing\n", - "!rm -R /home/PlanetDownloads\n", - "!rm -R /home/unzipped_data" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.13" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/jupyter-notebooks/workflows/publish-to-arcgis-online/Planet to ArcGIS Image using SDKv2.ipynb b/jupyter-notebooks/workflows/publish-to-arcgis-online/Planet to ArcGIS Image using SDKv2.ipynb deleted file mode 100644 index 6104ee86..00000000 --- a/jupyter-notebooks/workflows/publish-to-arcgis-online/Planet to ArcGIS Image using SDKv2.ipynb +++ /dev/null @@ -1,428 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "5b2ce989", - "metadata": {}, - "source": [ - "# Publish PlanetScope Imagery to ArcGIS Image for ArcGIS Online\n", - "\n", - "This script takes activated Planet orders and publishes them as Image Services with ArcGIS Online. With Planet imagery published in ArcGIS Online, you are able to:\n", - "\n", - "* Use the imagery in analytics workflows using raster functions or raster analytics\n", - "* Access full bit-depth imagery for custom stretching or band combinations performed on the fly\n", - "* Securely share imagery with your end-users since it is hosted inside of ArcGIS Online\n", - "\n", - "This script specifically works with PlanetScope 8-band analytics surface reflectance assets, but could be modified to work with additional asset types. For example, this could be extended to support Planet Basemaps or SkySat imagery.\n", - "\n", - "### Prerequisites\n", - "\n", - "* Access to the Planet API's (Don't have access? ([Sign up for our Developer Trial to get access!](https://developers.planet.com/devtrial/))\n", - "* Access to ArcGIS Online with an [ArcGIS Image for ArcGIS Online license](https://www.esri.com/en-us/arcgis/products/arcgis-image/options/arcgis-online)\n", - "* A previously placed order for PlanetScope 8-band imagery, either through our [Order's API](https://developers.planet.com/docs/orders/), [ArcGIS Pro Integration](https://developers.planet.com/docs/integrations/arcgis/), or [Explorer](https://www.planet.com/explorer)\n", - "* Edit the config.py file in this notebooks folder which is used to store credentials for ArcGIS Online and Planet's platform.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b8616126", - "metadata": {}, - "outputs": [], - "source": [ - "from arcgis.raster.analytics import copy_raster\n", - "from zipfile import ZipFile\n", - "import azure.storage.blob\n", - "import datetime\n", - "import shutil\n", - "import arcgis\n", - "import planet\n", - "import config #local python file which is storing credentials to ArcGIS Online and Planet's Platform\n", - "import glob\n", - "import os\n", - "\n", - "arcgis.env.verbose = True" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "2bc129b7", - "metadata": {}, - "outputs": [], - "source": [ - "planet_auth = planet.auth.APIKeyAuth(config.planet_api_key)" - ] - }, - { - "cell_type": "markdown", - "id": "dec189be", - "metadata": {}, - "source": [ - "First, you need to provide an order ID to publish. You could get the order ID from:\n", - "\n", - "* Manually from [Planet Explorer](https://www.planet.com/explorer) or your [planet.com account orders page](https://www.planet.com/account/#/orders)\n", - "* Or programatically from Planet's Orders API\n", - "\n", - "For example, a script could be used to search for orders from with the last 24 hours to be published to ArcGIS Online.\n", - "\n", - "For this script, choose an order for the asset type analytic_8b_sr_udm2 and which was not delivered to hosted data." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "b0fcd186", - "metadata": {}, - "outputs": [], - "source": [ - "# Provide an order ID. \n", - "my_order_id = \"INSERT ORDER ID HERE\"" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "4aa7f645", - "metadata": {}, - "outputs": [], - "source": [ - "async def get_images(my_order_id):\n", - " \"\"\"This function returns either a list tiff URLs or file paths for an order id that is provided as input.\n", - " \n", - " The input order is limited to PlanetScope 8-band assets.\"\"\"\n", - " \n", - " #create an async Planet API session\n", - " async with planet.Session() as ps:\n", - " \n", - " #create a Planet API client\n", - " client = planet.OrdersClient(ps)\n", - " \n", - " #get the orders details and name\n", - " order_details = await client.get_order(order_id=my_order_id)\n", - " order_name = order_details['name']\n", - "\n", - " #check if the order has been successfully completed, if not, exit the function\n", - " if order_details['state'] != 'success':\n", - " print(\"Order isn't completed yet\")\n", - " return\n", - " #also check that this is 8 band PlanetScope imagery\n", - " if order_details[\"products\"][0][\"product_bundle\"] != \"analytic_8b_sr_udm2\":\n", - " print(\"Order is not 8 band PlanetScope Imagery\")\n", - " return\n", - " \n", - " #check if the order is zip archives or tiffs\n", - " #zip archives are the default delivery option for orders placed from GIS integrations and Explorer\n", - " zip_archives = [r['name'] for r in order_details['_links']['results'] if r['name'].endswith(\".zip\")]\n", - " \n", - " #if order is zip archives:\n", - " # extract zips and get list of tiff file paths\n", - " #else:\n", - " # get list tiff urls\n", - " if len(zip_archives)>0:\n", - " \n", - " #download the assets, including zip archives\n", - " download_results = await client.download_order(my_order_id)\n", - " \n", - " #create a list of all zip files that were downloaded\n", - " zip_files = [x for x in download_results if x.suffix == \".zip\" ]\n", - " \n", - " #for each zip file downloaded, extract the files\n", - " for zip_file in zip_files:\n", - " #get the folder\n", - " folder = zip_file.parent.__str__()\n", - " z = ZipFile(zip_file)\n", - " z.extractall(os.path.join(os.getcwd(), folder))\n", - " \n", - " #create a list of file paths to the downloaded and unzipped tiff file\n", - " #looking for tiff files using search term *_SR_*.tif to exclude UDMs and metadata files\n", - " tiff_paths = glob.glob(os.path.join(os.getcwd(), \"**\\\\*_SR_*.tif\"), recursive = True)\n", - " \n", - " #return the order name and list of file paths to tiffs\n", - " return [order_name, tiff_paths]\n", - " \n", - " #if the order is not zip archives, but just tiffs\n", - " #this is the default option when ordering through Planet's API\n", - " else:\n", - " \n", - " #create a list of the URLs directly to tiffs\n", - " tiff_urls = [r['location'] for r in order_details['_links']['results'] if '_SR_' in r['name']]\n", - " \n", - " #return the order name and list of URLs to tiffs\n", - " return [order_name, tiff_urls]" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "4930783b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "--- Order Name ---\n", - "wildfire - AGOL Jupyter Notebook test\n", - "\n", - "--- TIFF Paths ---\n", - "['https://api.planet.com/compute/ops/download/?token', 'https://api.planet.com/compute/ops/download/?token']\n" - ] - } - ], - "source": [ - "# From the above function, we get the order name and the paths to our TIFF files to publish\n", - "tiffs = await get_images(my_order_id)\n", - "print(\"--- Order Name ---\")\n", - "print(tiffs[0])\n", - "print(\"\\n--- TIFF Paths ---\")\n", - "print([path[:50] for path in tiffs[1]])" - ] - }, - { - "cell_type": "markdown", - "id": "13a0ea08", - "metadata": {}, - "source": [ - "## Publish to ArcGIS Online\n", - "\n", - "Now the imagery can be published to ArcGIS Online! Simply authenticate to ArcGIS Online, create a unique name for your imagery layer, and publish the imagery layer." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "50703e4a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "GIS @ https://PlanetLabs.maps.arcgis.com" - ], - "text/plain": [ - "GIS @ https://PlanetLabs.maps.arcgis.com version:10.1" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Connect to ArcGIS Online\n", - "gis = arcgis.gis.GIS(url=\"https://www.arcgis.com\", username = config.arcgis_online_username, password = config.arcgis_online_password)\n", - "gis" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "4c507dd7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PlanetLabs_Queretaro_20220613203606\n" - ] - } - ], - "source": [ - "# Create a unique timestamp\n", - "timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')\n", - "\n", - "# Use timestamp and order name to create a unique name for the image service\n", - "layer_name = \"PlanetLabs_\" + str(tiffs[0])[:8] + \"_\" + timestamp\n", - "\n", - "print(layer_name)" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "45991944", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Submitted.\n", - "Executing...\n", - "Start Time: Tuesday, June 14, 2022 1:36:20 AM\n", - "Hosted Imagery Privilege Check: OK\n", - "Image service {'name': 'PlanetLabs_Queretaro_20220613203606', 'serviceUrl': 'https://tiledimageservices8.arcgis.com/12345/arcgis/rest/services/PlanetLabs_Queretaro_20220613203606/ImageServer'} already existed.\n", - "Output item id is: 123432542351\n", - "Output image service url is: https://tiledimageservices8.arcgis.com/12345/arcgis/rest/services/PlanetLabs_Queretaro_20220613203606/ImageServer\n", - "Output cloud raster name is: PlanetLabs_Queretaro_20220613203606\n", - "Input raster is: []\n", - "Org ID is: \n", - "Org ID is: \n", - "Hosted data folder is: /cloudStores//\n", - "Finished creating empty mosaic dataset.\n", - "Create empty image collection successfully.\n", - "Add image data to mosaic dataset.\n", - "Define Nodata pixels...\n", - "Finished define nodata value.\n", - "Set mosaic dataset default properties.\n", - "Publishing Raster...\n", - "/cloudStores/12345/123432542351/PlanetLabs_Queretaro_20220613203606.crf\n", - "Updating image service...\n", - "Updating service with data store URI.\n", - "Getting image service info...\n", - "Updating service: https://tiledimageservices8.arcgis.com/12345/arcgis/rest/admin/services/PlanetLabs_Queretaro_20220613203606/ImageServer/edit\n", - "{'success': True}\n", - "Portal item refreshed.\n", - "CopyRaster GP Job: 1234142412 finished successfully.\n" - ] - } - ], - "source": [ - "# Publish your images as an image service to ArcGIS Online\n", - "# Note that this can take significant time with large datasets.\n", - "\n", - "published_rasters = copy_raster(input_raster=tiffs[1],\n", - " outpute_cellsize = {\"distance\":3.5,\"units\":\"meters\"},\n", - " output_name=layer_name,\n", - " raster_type_name=\"Raster Dataset\",\n", - " context={\"outSR\":{\"wkid\":3857},\n", - " \"resamplingMethod\":\"BILINEAR\",\n", - " \"compression\":\"LERC 0\",\n", - " \"bandMapping\":[{\"bandName\":\"coastal_blue\",\"wavelengthMin\":431,\"wavelengthMax\":452},\n", - " {\"bandName\":\"blue\",\"wavelengthMin\":465,\"wavelengthMax\":515},\n", - " {\"bandName\":\"green_i\",\"wavelengthMin\":513,\"wavelengthMax\":549},\n", - " {\"bandName\":\"green\",\"wavelengthMin\":547,\"wavelengthMax\":583},\n", - " {\"bandName\":\"yellow\",\"wavelengthMin\":600,\"wavelengthMax\":620},\n", - " {\"bandName\":\"red\",\"wavelengthMin\":650,\"wavelengthMax\":680},\n", - " {\"bandName\":\"rededge\",\"wavelengthMin\":697,\"wavelengthMax\":713},\n", - " {\"bandName\":\"nir\",\"wavelengthMin\":845,\"wavelengthMax\":885}\n", - " ],\n", - " \"buildFootprints\":False,\n", - " \"defineNodata\":True,\n", - " \"noDataArguments\":{\"noDataValues\":[0],\n", - " \"compositeValue\":True}\n", - " },\n", - " gis=gis)" - ] - }, - { - "cell_type": "markdown", - "id": "07122317", - "metadata": {}, - "source": [ - "## View the New Image Service and Clean Up Folders\n", - "Now we can view the Image Service by drawing it on a map directly in this notebook! Or you can view it in your ArcGIS Online environment.\n", - "\n", - "#### Want to see the data now? [Check out this map here.](https://planetlabs.maps.arcgis.com/apps/instant/basic/index.html?appid=eaeaa5cf78694c63b9955ff7c87e479f)" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "998c0ed0", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "3c819e6cbaa64666bb64260c161b982f", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "MapView(layout=Layout(height='400px', width='100%'))" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# view the new imagery layer on an arcgis map\n", - "# this layer can now be added to other maps, analyzed with ArcGIS Raster Analytics tools, and more\n", - "\n", - "my_map = gis.map(location = published_rasters.extent, zoomlevel = 11)\n", - "my_map.basemap = \"imagery\"\n", - "my_map.add_layer(published_rasters)\n", - "my_map\n" - ] - }, - { - "cell_type": "markdown", - "id": "b9e590d2", - "metadata": {}, - "source": [ - "![PlanetScope Image Service in Jupyter Notebook](/images/PlanetImageryInNotebook.png)" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "657946f4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nothing to delete\n" - ] - } - ], - "source": [ - "# deletes the folder and contents created when downloading order and unzipping \n", - "if os.path.exists(my_order_id) and os.path.isdir(my_order_id):\n", - " shutil.rmtree(my_order_id)\n", - "else:\n", - " print(\"Nothing to delete\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.13" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/jupyter-notebooks/workflows/publish-to-arcgis-online/config.py b/jupyter-notebooks/workflows/publish-to-arcgis-online/config.py deleted file mode 100644 index 5d206e20..00000000 --- a/jupyter-notebooks/workflows/publish-to-arcgis-online/config.py +++ /dev/null @@ -1,3 +0,0 @@ -arcgis_online_username = "" -arcgis_online_password = "" -planet_api_key = "" \ No newline at end of file diff --git a/jupyter-notebooks/workflows/publish-to-arcgis-online/images/PlanetImageryInNotebook.png b/jupyter-notebooks/workflows/publish-to-arcgis-online/images/PlanetImageryInNotebook.png deleted file mode 100644 index 5110f64f..00000000 Binary files a/jupyter-notebooks/workflows/publish-to-arcgis-online/images/PlanetImageryInNotebook.png and /dev/null differ diff --git a/jupyter-notebooks/workflows/publish-to-arcgis-online/planet-to-arcgis-image.ipynb b/jupyter-notebooks/workflows/publish-to-arcgis-online/planet-to-arcgis-image.ipynb new file mode 100644 index 00000000..3530f7b9 --- /dev/null +++ b/jupyter-notebooks/workflows/publish-to-arcgis-online/planet-to-arcgis-image.ipynb @@ -0,0 +1,366 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6b92d600", + "metadata": {}, + "source": [ + "# Publish PlanetScope Imagery to ArcGIS Image for ArcGIS Online\n", + "\n", + "This script takes activated Planet orders and publishes them as Image Services with ArcGIS Online. With Planet imagery published in ArcGIS Online, you are able to:\n", + "\n", + "* Use the imagery in analytics workflows using raster functions or raster analytics\n", + "* Access full bit-depth imagery for custom stretching or band combinations performed on the fly\n", + "* Securely share imagery with your end-users since it is hosted inside of ArcGIS Online\n", + "\n", + "This script specifically works with PlanetScope 8-band analytics surface reflectance assets, but could be modified to work with additional asset types. For example, this could be extended to support Planet Basemaps or SkySat imagery.\n", + "\n", + "### Prerequisites\n", + "\n", + "* Access to ArcGIS Online with an [ArcGIS Image for ArcGIS Online license](https://www.esri.com/en-us/arcgis/products/arcgis-image/options/arcgis-online)\n", + "* A previously placed order for PlanetScope 8-band imagery, either through our [Order's API](https://developers.planet.com/docs/orders/), [ArcGIS Pro Integration](https://developers.planet.com/docs/integrations/arcgis/), or [Explorer](https://www.planet.com/explorer)\n", + "* Edit the config.py file in this notebooks folder which is used to store credentials for ArcGIS Online and Planet's platform.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e1917e19", + "metadata": {}, + "outputs": [], + "source": [ + "import arcgis\n", + "from arcgis.gis import GIS\n", + "from arcgis.raster.analytics import copy_raster, create_image_collection, list_datastore_content\n", + "import os\n", + "import planet \n", + "import asyncio\n", + "import glob\n", + "from zipfile import ZipFile\n", + "from pathlib import Path\n", + "import pandas as pd\n", + "import datetime" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Connect to Planet API\n", + "\n", + "pl_api_key = os.environ.get(\"PL_API_KEY\")\n", + "plsdk_auth = planet.Auth.from_key(key=pl_api_key)\n", + "sess = planet.Session(plsdk_auth)\n", + "pl = planet.Planet(sess)" + ] + }, + { + "cell_type": "markdown", + "id": "563bc070", + "metadata": {}, + "source": [ + "First, you need to provide an order ID to publish. You could get the order ID from:\n", + "\n", + "* Manually from [Planet Explorer](https://www.planet.com/explorer) or your [planet.com account orders page](https://www.planet.com/account/#/orders)\n", + "* Or programatically from Planet's Orders API\n", + "\n", + "For example, a script could be used to search for orders from with the last 24 hours to be published to ArcGIS Online.\n", + "\n", + "For this script, choose an order for the asset type analytic_8b_sr_udm2 and which was not delivered to hosted data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb4371ee", + "metadata": {}, + "outputs": [], + "source": [ + "# Collect all orders\n", + "all_orders = []\n", + "\n", + "# Get the generator from list_orders\n", + "orders_generator = pl.orders.list_orders()\n", + "\n", + "# Iterate through the generator to get each order\n", + "for order in orders_generator:\n", + " all_orders.append(order)\n", + "\n", + "# Extract relevant fields into a list of dictionaries\n", + "orders_data = []\n", + "for order in all_orders:\n", + " # Extract product details if available\n", + " item_type = \"N/A\"\n", + " product_bundle = \"N/A\"\n", + " if order.get('products') and len(order['products']) > 0:\n", + " item_type = order['products'][0].get('item_type', 'N/A')\n", + " product_bundle = order['products'][0].get('product_bundle', 'N/A')\n", + " \n", + " # Create a simplified dictionary with just the fields we want\n", + " order_info = {\n", + " 'ID': order.get('id', 'N/A'),\n", + " 'Created': order.get('created_on', 'N/A'),\n", + " 'Name': order.get('name', 'N/A'),\n", + " 'State': order.get('state', 'N/A'),\n", + " 'Item Type': item_type,\n", + " 'Product Bundle': product_bundle\n", + " }\n", + " orders_data.append(order_info)\n", + "\n", + "# Create and display DataFrame\n", + "orders_df = pd.DataFrame(orders_data)\n", + "orders_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "959592a8", + "metadata": {}, + "outputs": [], + "source": [ + "# Select the first order ID to use in the next steps, or specify your own\n", + "my_order_id = orders_df.iloc[0].ID\n", + "# my_order_id = \"your-planet-order-id\"\n", + "my_order_name = pl.orders.get_order(my_order_id)['name']\n", + "\n", + "print(f'Order to be published: {my_order_name} - id: {my_order_id}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89dff08b", + "metadata": {}, + "outputs": [], + "source": [ + "# Create download directory if it doesn't exist\n", + "download_dir = Path(\"./planet_downloads\")\n", + "download_dir.mkdir(exist_ok=True)\n", + "\n", + "# Create order-specific directory\n", + "order_dir = download_dir / my_order_id\n", + "order_dir.mkdir(exist_ok=True)\n", + "\n", + "# Array to store local file paths\n", + "local_tiff_files = []\n", + "\n", + "# Create an async Planet API session\n", + "async with planet.Session() as ps:\n", + " \n", + " # Create a Planet API client using the modern method\n", + " client = ps.client('orders')\n", + " \n", + " # Get the order details and name\n", + " order_details = await client.get_order(order_id=my_order_id)\n", + " order_name = order_details['name']\n", + " print(f\"Processing order: {order_name}\")\n", + " print(f\"All files will be stored in: {order_dir}\")\n", + "\n", + " # Check if the order has been successfully completed\n", + " if order_details['state'] != 'success':\n", + " print(\"Order isn't completed yet\")\n", + " raise Exception(\"Order not ready for download\")\n", + "\n", + " # Check if the order contains zip archives or direct file links\n", + " zip_archives = [r['name'] for r in order_details['_links']['results'] if r['name'].endswith(\".zip\")]\n", + " \n", + " if len(zip_archives) > 0:\n", + " print(\"Order contains zip archives - downloading and extracting...\")\n", + " \n", + " # Create subfolders for organization\n", + " raw_dir = order_dir / \"raw_downloads\"\n", + " extracted_dir = order_dir / \"extracted\"\n", + " raw_dir.mkdir(exist_ok=True)\n", + " extracted_dir.mkdir(exist_ok=True)\n", + " \n", + " # Download all assets including zip archives to the raw downloads folder within order directory\n", + " download_results = await client.download_order(\n", + " my_order_id, \n", + " directory=raw_dir, # Download to raw folder within order directory\n", + " overwrite=True,\n", + " progress_bar=True\n", + " )\n", + " \n", + " # Create a list of all zip files that were downloaded\n", + " zip_files = [x for x in download_results if x.suffix == \".zip\"]\n", + " \n", + " # Extract each zip file to the extracted folder\n", + " for zip_file in zip_files:\n", + " print(f\"Extracting {zip_file.name}...\")\n", + " with ZipFile(zip_file) as z:\n", + " z.extractall(extracted_dir) # Extract to extracted folder\n", + " \n", + " # Find all relevant tiff files in the extracted directory\n", + " local_tiff_files = [file for file in extracted_dir.glob(\"**/*.tif\") if \"udm\" not in file.name.lower()]\n", + "\n", + " else:\n", + " print(\"Order contains direct file links - downloading individual files...\")\n", + " \n", + " # Download each tiff file individually to the order directory\n", + " for result in order_details['_links']['results']:\n", + " filename = result['name']\n", + " location = result['location']\n", + " \n", + " print(f\"Downloading {filename}...\")\n", + " downloaded_file = await client.download_asset(\n", + " location=location,\n", + " filename=filename,\n", + " directory=download_dir, # Download directly to order directory\n", + " overwrite=True,\n", + " progress_bar=True\n", + " )\n", + " \n", + " # Find all relevant tiff files in the extracted directory\n", + " local_tiff_files = [file for file in order_dir.glob(\"**/*.tif\") if \"udm\" not in file.name.lower()]\n", + " \n", + "print(f\"\\nDownload complete! Found {len(local_tiff_files)} SR tiff files:\")\n", + "for file_path in local_tiff_files:\n", + " print(f\" {file_path}\")\n", + "\n", + "print(f\"\\nAll files stored in: {order_dir}\")\n", + "print(f\"File paths available in 'local_tiff_files' array\")\n", + "\n", + "# The local_tiff_files array now contains Path objects for all downloaded tiff files\n", + "# You can use these for processing and delete them later when done" + ] + }, + { + "cell_type": "markdown", + "id": "a21ed3e0", + "metadata": {}, + "source": [ + "## Publish to ArcGIS Online\n", + "\n", + "Now the imagery can be published to ArcGIS Online! Simply authenticate to ArcGIS Online, create a unique name for your imagery layer, and publish the imagery layer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee80307f", + "metadata": {}, + "outputs": [], + "source": [ + "# Connect to ArcGIS Online\n", + "gis = arcgis.gis.GIS(url=\"https://www.arcgis.com\", client_id=\"your-arcgis-client-id\")\n", + "gis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "847b1ca8", + "metadata": {}, + "outputs": [], + "source": [ + "if not local_tiff_files:\n", + " print(\"No tiff files found. Make sure to run the Planet download code first.\")\n", + "else:\n", + " # Create a unique timestamp\n", + " timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')\n", + " \n", + " # Use timestamp and order name to create a unique name for the image collection\n", + " # Extract a meaningful identifier from the first file name\n", + " first_file_stem = local_tiff_files[0].stem\n", + " scene_id = first_file_stem.split('_')[0] # Get the scene ID part\n", + " layer_name = f\"PlanetLabs_{scene_id}_{timestamp}\"\n", + " \n", + " print(f\"Creating image collection: {layer_name}\")\n", + " print(f\"Processing {len(local_tiff_files)} Planet images...\")\n", + " \n", + " # Convert Path objects to strings for the API\n", + " # Use the order directory directly since everything is now flat\n", + " order_dir = download_dir / my_order_id\n", + " input_folder = str(order_dir) # Use the order-specific folder containing all images\n", + " \n", + " # Create the image collection using Planet imagery\n", + " # Note: This can take significant time with large datasets\n", + " try:\n", + " published_imagery_layer = create_image_collection(\n", + " image_collection=layer_name,\n", + " input_rasters=input_folder,\n", + " raster_type_name=\"Raster Dataset\", # Generic raster type - ArcGIS will detect metadata\n", + " context={\n", + " \"outSR\": {\"wkid\": 3857}, # Web Mercator projection\n", + " \"defineNodata\": True,\n", + " \"noDataArguments\": {\n", + " \"noDataValues\": [0], # Planet uses 0 for nodata\n", + " \"compositeValue\": True\n", + " },\n", + " \"buildFootprints\": True, # Enable footprints for better mosaicking\n", + " \"buildOverview\": True,\n", + " \"image_collection_properties\": {\n", + " \"imageCollectionType\": \"Satellite\"\n", + " }\n", + " },\n", + " gis=gis\n", + " )\n", + " \n", + " print(f\"✅ Successfully created image collection: {layer_name}\")\n", + " print(f\"📍 Image service URL: {published_imagery_layer.url}\")\n", + " print(f\"🆔 Item ID: {published_imagery_layer.itemid}\")\n", + " \n", + " # Store the result for later use\n", + " planet_image_collection = published_imagery_layer\n", + " \n", + " except Exception as e:\n", + " print(f\"❌ Error creating image collection: {str(e)}\")\n", + " print(\"This might be due to:\")\n", + " print(\"- Insufficient ArcGIS Online privileges\")\n", + " print(\"- Raster analysis not configured properly\")\n", + " print(\"- Large dataset size (try with fewer images first)\")" + ] + }, + { + "cell_type": "markdown", + "id": "9839139d", + "metadata": {}, + "source": [ + "## View the New Image Service and Clean Up Folders\n", + "Now we can view the Image Service by drawing it on a map directly in this notebook! Or you can view it in your ArcGIS Online environment.\n", + "\n", + "#### Want to see the data now? [Check out this map here.](https://planetlabs.maps.arcgis.com/apps/instant/basic/index.html?appid=eaeaa5cf78694c63b9955ff7c87e479f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8a2a7608", + "metadata": {}, + "outputs": [], + "source": [ + "# view the new imagery layer on an arcgis map\n", + "# this layer can now be added to other maps, analyzed with ArcGIS Raster Analytics tools, and more\n", + "\n", + "my_map = gis.map(location = planet_image_collection.extent, zoomlevel = 11)\n", + "my_map.basemap = \"imagery\"\n", + "my_map.add_layer(planet_image_collection)\n", + "my_map" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sentinel-hub", + "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.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/jupyter-notebooks/workflows/publish-to-arcgis-online/readme.md b/jupyter-notebooks/workflows/publish-to-arcgis-online/readme.md index 254791e0..7be6b7db 100644 --- a/jupyter-notebooks/workflows/publish-to-arcgis-online/readme.md +++ b/jupyter-notebooks/workflows/publish-to-arcgis-online/readme.md @@ -6,16 +6,8 @@ These notebooks contain samples for publishing PlanetScope imagery as image serv * Access full bit-depth imagery for custom stretching or band combinations performed on the fly * Securely share imagery with your end-users using the ArcGIS identity, named users, in ArcGIS Online -These scripts specifically work with PlanetScope 8-band surface reflectance analytic assets, but could be modified to work with additional asset types and sensors. For example, this could be extended to support Planet Basemaps or SkySat imagery. - ### Prerequisites -* An account to access Planet APIs (Don't have access? [Contact Us](https://www.planet.com/contact-sales/)) * Access to ArcGIS Online with an [ArcGIS Image for ArcGIS Online license](https://www.esri.com/en-us/arcgis/products/arcgis-image/options/arcgis-online) -* A previously placed order for PlanetScope 8-band imagery, either through our [Order's API](https://developers.planet.com/docs/orders/), [ArcGIS Pro Integration](https://developers.planet.com/docs/integrations/arcgis/), or [Explorer](https://www.planet.com/explorer) -* Edit the config.py file in this notebooks folder which is used to store credentials for ArcGIS Online and Planet's platform. - -### Multiple Notebook Versions +* A previously placed order for PlanetScope imagery, either through our [Order's API](https://developers.planet.com/docs/orders/), [ArcGIS Pro Integration](https://developers.planet.com/docs/integrations/arcgis/), or [Explorer](https://www.planet.com/explorer) -* Planet to ArcGIS Image using SDKv2 - This notebook uses Planet's new Python SDK for faster and easier access to our API's. In order to use this, you need to install the SDK: [How to install the Planet SDK for Python v2](https://planet-sdk-for-python-v2.readthedocs.io/en/latest/python/sdk-guide/). -* Planet to ArcGIS Image using Requests - This notebook uses the python library Requests to make the API calls to Planet's platform. It will work when run inside of ArcGIS Notebooks, a hosted Jupyter notebook in ArcGIS Online.