Publish PlanetScope Imagery to ArcGIS Image for ArcGIS Online
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:
- Use the imagery in analytics workflows using raster functions or raster analytics
- Access full bit-depth imagery for custom stretching or band combinations performed on the fly
- Securely share imagery with your end-users since it is hosted inside of ArcGIS Online
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.
Prerequisites
- Access to the Planet API's (Don't have access? (Sign up for our Developer Trial to get access!)
- Access to ArcGIS Online with an ArcGIS Image for ArcGIS Online license
- A previously placed order for PlanetScope 8-band imagery, either through our Order's API, ArcGIS Pro Integration, or Explorer
- Edit the config.py file in this notebooks folder which is used to store credentials for ArcGIS Online and the Planet platform.
from arcgis.raster.analytics import copy_raster
from zipfile import ZipFile
import azure.storage.blob
import datetime
import shutil
import arcgis
import planet
import config #local python file which is storing credentials to ArcGIS Online and Planet's Platform
import glob
import os
arcgis.env.verbose = True
planet_auth = planet.auth.APIKeyAuth(config.planet_api_key)
First, you need to provide an order ID to publish. You could get the order ID from:
- Manually from Planet Explorer or your planet.com account orders page
- Or programatically from Planet's Orders API
For example, a script could be used to search for orders from the last 24 hours to be published to ArcGIS Online.
For this script, choose an order for the asset type analytic_8b_sr_udm2
and which was not delivered to hosted data.
# Provide an order ID.
my_order_id = "INSERT ORDER ID HERE"
async def get_images(my_order_id):
"""This function returns either a list tiff URLs or file paths for an order id that is provided as input.
The input order is limited to PlanetScope 8-band assets."""
#create an async Planet API session
async with planet.Session() as ps:
#create a Planet API client
client = planet.OrdersClient(ps)
#get the orders details and name
order_details = await client.get_order(order_id=my_order_id)
order_name = order_details['name']
#check if the order has been successfully completed, if not, exit the function
if order_details['state'] != 'success':
print("Order isn't completed yet")
return
#also check that this is 8 band PlanetScope imagery
if order_details["products"][0]["product_bundle"] != "analytic_8b_sr_udm2":
print("Order is not 8 band PlanetScope Imagery")
return
#check if the order is zip archives or tiffs
#zip archives are the default delivery option for orders placed from GIS integrations and Explorer
zip_archives = [r['name'] for r in order_details['_links']['results'] if r['name'].endswith(".zip")]
#if order is zip archives:
# extract zips and get list of tiff file paths
#else:
# get list tiff urls
if len(zip_archives)>0:
#download the assets, including zip archives
download_results = await client.download_order(my_order_id)
#create a list of all zip files that were downloaded
zip_files = [x for x in download_results if x.suffix == ".zip" ]
#for each zip file downloaded, extract the files
for zip_file in zip_files:
#get the folder
folder = zip_file.parent.__str__()
z = ZipFile(zip_file)
z.extractall(os.path.join(os.getcwd(), folder))
#create a list of file paths to the downloaded and unzipped tiff file
#looking for tiff files using search term *_SR_*.tif to exclude UDMs and metadata files
tiff_paths = glob.glob(os.path.join(os.getcwd(), "**\\*_SR_*.tif"), recursive = True)
#return the order name and list of file paths to tiffs
return [order_name, tiff_paths]
#if the order is not zip archives, but just tiffs
#this is the default option when ordering through Planet's API
else:
#create a list of the URLs directly to tiffs
tiff_urls = [r['location'] for r in order_details['_links']['results'] if '_SR_' in r['name']]
#return the order name and list of URLs to tiffs
return [order_name, tiff_urls]
# From the above function, we get the order name and the paths to our TIFF files to publish
tiffs = await get_images(my_order_id)
print("--- Order Name ---")
print(tiffs[0])
print("\n--- TIFF Paths ---")
print([path[:50] for path in tiffs[1]])
--- Order Name ---
wildfire - AGOL Jupyter Notebook test
--- TIFF Paths ---
['https://api.planet.com/compute/ops/download/?token', 'https://api.planet.com/compute/ops/download/?token']
Publish to ArcGIS Online
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.
# Connect to ArcGIS Online
gis = arcgis.gis.GIS(url="https://www.arcgis.com", username = config.arcgis_online_username, password = config.arcgis_online_password)
gis
GIS @ https://PlanetLabs.maps.arcgis.com
# Create a unique timestamp
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
# Use timestamp and order name to create a unique name for the image service
layer_name = "PlanetLabs_" + str(tiffs[0])[:8] + "_" + timestamp
print(layer_name)
PlanetLabs_Queretaro_20220613203606
# Publish your images as an image service to ArcGIS Online
# Note that this can take significant time with large datasets.
published_rasters = copy_raster(input_raster=tiffs[1],
outpute_cellsize = {"distance":3.5,"units":"meters"},
output_name=layer_name,
raster_type_name="Raster Dataset",
context={"outSR":{"wkid":3857},
"resamplingMethod":"BILINEAR",
"compression":"LERC 0",
"bandMapping":[{"bandName":"coastal_blue","wavelengthMin":431,"wavelengthMax":452},
{"bandName":"blue","wavelengthMin":465,"wavelengthMax":515},
{"bandName":"green_i","wavelengthMin":513,"wavelengthMax":549},
{"bandName":"green","wavelengthMin":547,"wavelengthMax":583},
{"bandName":"yellow","wavelengthMin":600,"wavelengthMax":620},
{"bandName":"red","wavelengthMin":650,"wavelengthMax":680},
{"bandName":"rededge","wavelengthMin":697,"wavelengthMax":713},
{"bandName":"nir","wavelengthMin":845,"wavelengthMax":885}
],
"buildFootprints":False,
"defineNodata":True,
"noDataArguments":{"noDataValues":[0],
"compositeValue":True}
},
gis=gis)
Submitted.
Executing...
Start Time: Tuesday, June 14, 2022 1:36:20 AM
Hosted Imagery Privilege Check: OK
Image service {'name': 'PlanetLabs_Queretaro_20220613203606', 'serviceUrl': 'https://tiledimageservices8.arcgis.com/12345/arcgis/rest/services/PlanetLabs_Queretaro_20220613203606/ImageServer'} already existed.
Output item id is: 123432542351
Output image service url is: https://tiledimageservices8.arcgis.com/12345/arcgis/rest/services/PlanetLabs_Queretaro_20220613203606/ImageServer
Output cloud raster name is: PlanetLabs_Queretaro_20220613203606
Input raster is: []
Org ID is:
Org ID is:
Hosted data folder is: /cloudStores//
Finished creating empty mosaic dataset.
Create empty image collection successfully.
Add image data to mosaic dataset.
Define Nodata pixels...
Finished define nodata value.
Set mosaic dataset default properties.
Publishing Raster...
/cloudStores/12345/123432542351/PlanetLabs_Queretaro_20220613203606.crf
Updating image service...
Updating service with data store URI.
Getting image service info...
Updating service: https://tiledimageservices8.arcgis.com/12345/arcgis/rest/admin/services/PlanetLabs_Queretaro_20220613203606/ImageServer/edit
{'success': True}
Portal item refreshed.
CopyRaster GP Job: 1234142412 finished successfully.
View the New Image Service and Clean Up Folders
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.
Want to see the data now? Check out this map here.
# view the new imagery layer on an arcgis map
# this layer can now be added to other maps, analyzed with ArcGIS Raster Analytics tools, and more
my_map = gis.map(location = published_rasters.extent, zoomlevel = 11)
my_map.basemap = "imagery"
my_map.add_layer(published_rasters)
my_map
MapView(layout=Layout(height='400px', width='100%'))
# deletes the folder and contents created when downloading order and unzipping
if os.path.exists(my_order_id) and os.path.isdir(my_order_id):
shutil.rmtree(my_order_id)
else:
print("Nothing to delete")
Nothing to delete