Skip to main content

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

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:

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]])
Output
    --- 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)
Output
    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)
Output
    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

Output
    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")
Output
    Nothing to delete