import os

# Configure Auth and Base URL

# Planet Analytics API Base URL
PAA_BASE_URL = "https://api.planet.com/analytics/"

# API Key Config
API_KEY = os.environ['PL_API_KEY']

# Alternatively, you can just set your API key directly as a string variable:
# API_KEY = "YOUR_PLANET_API_KEY_HERE"

# Setup Auth
BASIC_AUTH = (API_KEY, '')

"""
Explanation: Planet Analytics API Tutorial
<h1 style="margin-top:10px;">Planet Analytic Feeds Results</h1>

Overview

Introduction 


Introduction to Analytic Feed Results types: Object Detection vs Segmentation


Getting Results 


Get Results for a Subscription from the API


Working with Planet Analytics Results 


Interpret and visualize Results with source satellite imagery


Segmentation Results


Visualizing Segmentation masks

1. Introduction
In the previous notebook, we explored the Planet Analytics Feeds API mechanics and core concepts of Feeds and Subscriptions. We worked through making requests to the API, understanding the response data, and showed how to create some basic visualizations with the data. It's encouraged to work through the first notebook before continuing.
In this notebook, we'll focus on Planet Analytic Feeds Results. Results represent the "detections" made in source Planet satellite imagery scenes for different types of objects visible on the ground. 
Analytic Feed Results
Results on the Planet Analytics API represent the output or "detections" of our machine learning models. Results are created for each Subscription, and each Subscription is derived from a Feed:
Feed → Subscription → Results 

When new Planet imagery is published that intersects a Subscription's AOI and TOI, Planet’s computer vision models process the imagery and the output is added to a "collection" (WFS 3) of Results associated with a Subscription.

Feed / Result Types
As we've seen, several types of Feeds exist, and Results for Feed Subscriptions can be categorized as one of three types: Object Detection, Segmentation, and Change Detection. This notebook covers the Object Detection and Segmentation feed types, while the next notebook covers Change Detection feeds.
Types of Feeds + Result Output Format
| Feed Type | Results Type | Results Format | Results Access |
| --- | --- | --- | --- |
| Vessel Detection | Object Detection | Detection Features (Polygons) | WFS 3 (Detection Features) |
| Building Detection | Segmentation (Classification) | Raster Mask / Basemaps | WFS 3 (Quads) / XYZ Tiles |
| Road Detection | Segmentation (Classification) | Raster Mask / Basemaps | WFS 3 (Quads) / XYZ Tiles |
| Building Construction Detection | Change Detection | Raster Mask / Basemaps | WFS 3 (Quads) / XYZ Tiles |
| Road Construction Detection | Change Detection | Raster Mask / Basemaps | WFS 3 (Quads) / XYZ Tiles |
Outputs for all Results (of any Feed type) are delivered via WFS 3 compliant endpoint on the API. The "shape" of the data representing Results for each Subscription are specific to the Subscription's Feed. 
Object Detection* type Feeds Results output are returned as GeoJSON Features with polygonal geometries representing bounding boxes around detections made by the machine learning model. For example, a box around a ship. 
Segmentation type Feeds Results output are GeoJSON Features that describe "Quads" (individual raster files) that make up a larger segmentation / classification "mask". The Planet Analytics API lists links to these segmentation quads on the Planet Mosaics API where the raster files are stored, but also links to XYZ basemap tiles that can be used to view the raster data. 
Change Detection feeds are discussed in the following notebook.
WFS 3
<img src="http://www.opengeospatial.org/pub/www/files/OGC_Logo_2D_Blue_x_0_0.png" align="right" style="margin-left: 20px" width="100"></img>
The Planet Analytics API's Results (collections and items) endpoints follow the Open Geospatial Consortium's (OGC) Web Feature Service 3.0 (WFS) specification.

A Web Feature Service (WFS) is a standard API that represents collections of geospatial data.

2. Getting Results
Let's get started by accessing one of our Subscriptions. We'll see how we can get it's associated Results and we'll explore and visualize the data!
First we'll setup our API base url and authentication so we can make our request, just like in the previous tutorial.
End of explanation
"""


# Pretty Print Helper
import pprint
pp = pprint.PrettyPrinter(indent=4)

# JSON Pretty Print Helper
import json

def jpp(data):
    print(json.dumps(data, indent=4))

"""
Explanation: We'll make a couple helper functions for "pretty printing" our responses:
End of explanation
"""


# Request a Subscription

import requests

# Define the Subscription UUID, which in this case is for Ship Detections
subscription_id = "9db92275-1d89-4d3b-a0b6-68abd2e94142"

# Construct the URL for the Subscription
subscription_url = PAA_BASE_URL + 'subscriptions/' + subscription_id

print("Request URL: {}".format(subscription_url))

# Make the GET request for Subscriptions list 
subscription = requests.get(subscription_url, auth=BASIC_AUTH).json()

"""
Explanation: Now let's use the Requests library to get the Subscription. We'll get the Subscription using its id, which is a Universally Unique Identifier (UUID) string. 
Remember, we can see a list of available Subscriptions (and their ids by making a request to the /subscriptions endpoint. See the previous tutorial for more details.
Note: If you do not have access to the Analytics Feeds API or the specific example subscription IDs used in the example code below, you may not be able to run through these examples. Contact Sales to learn more.
End of explanation
"""


# Print some data
print("{} \n{}\nSubscription Id: {}\n".format(subscription['title'], subscription['description'], subscription['id']))

# Print the subscription object
print(json.dumps(subscription, sort_keys=True, indent=4))

"""
Explanation: Let's take a look at the Subscription we just requested:
End of explanation
"""


# Construct the URL for the subscription's Results collection
subscription_results_url = PAA_BASE_URL + 'collections/' + subscription['id']

print("Request URL: {}".format(subscription_results_url))

# Get subscription results collection
subscription_results = requests.get(subscription_results_url, auth=BASIC_AUTH).json()

# Pretty Print response JSON
print(json.dumps(subscription_results, sort_keys=True, indent=4))

"""
Explanation: The above output should look familiar from the previous tutorial. We see the title, AOI, TOI, and other metadata about the Subscription.
Now we can move on to getting the Results for this Subscription:
Subscription Results Collection
We can use the Subscription id property to get the Results collection and items for this Subscription.
We can get the collection by making a request to the /collections endpoint with the id from our Subscription:
End of explanation
"""


# Construct the URL
subscription_items_url = list(filter(lambda link: link['rel'] == 'results', subscription['links']))[0]['href']

print("Subscription Items URL:\n{}\n".format(subscription_items_url))

"""
Explanation: Subscription Results Items
Now let's get the actual Results items from the collection. Our request endpoint should be constructed like this: /collections/{ID}/items
But we don't need to construct the Results items URL manually, it's one of the links that's provided in our Subscription response. Let's use the link from our Subscription response from the previous cell:
End of explanation
"""


# Request subscription Items

# Get request Subscription features (collection items)
subscription_items = requests.get(subscription_items_url, auth=BASIC_AUTH).json()

# Pretty Print response JSON
print("Subscription Results Collection Items: (GeoJSON FeatureCollection):\n {}".format(json.dumps(subscription_items, sort_keys=True, indent=4)))


"""
Explanation: We'll use the url to make our request:
End of explanation
"""


# Print the number of Features in the subscription's collection:
print("\nNumber of Items (Features): {} \n".format(len(subscription_items['features'])))

"""
Explanation: Nice! Now we've got our first set of Results data. The Results for a Subscription come to us as a GeoJSON FeatureCollection.
Each item is a GeoJSON Feature listed in the the features property of the FeatureCollection. Let's check how many items (Features) we have so far:
End of explanation
"""


# Pretty Print the links
pp.pprint(subscription_items['links'])

"""
Explanation: The request we made to get these items is actually a paged list of results. 
In our GeoJSON Feature Collection, we see there is a links property, which conforms to the WFS 3.0 Spec in accordance with the OpenAPI spec for Links. 
Let's look at the links property to see how we can get all the Results items for our Subscription:
End of explanation
"""


# Helper to get the "next" link
def get_next_link(links):
    for link in links:
        if link['rel'] == 'next':
            return link['href']

"""
Explanation: The "rel" : "next" property's href should contain a URL with a query string ?before={ITEM_ID} where the ITEM_ID is the UUID identifier.
We can use the link with the rel value "next" to get the next page of data, and follow each subsequent next link to request all the pages of data and get the entire FeatureCollection for this subscription's items. 
Let's write a helper function to do this:
End of explanation
"""


# Get the first "next" link
next_link = get_next_link(subscription_items['links'])
page_number = 2   

print(next_link)

"""
Explanation: We can use our helper function to get the first "next" link:
End of explanation
"""


# Loop to get all pages of features
while next_link:
    print("Getting page number {} at {}".format(page_number, next_link))
    
    # Use "next" link to request next page
    next_collection = requests.get(next_link, auth=BASIC_AUTH).json()
        
    print("Found {} additional Features \n".format(len(next_collection['features'])))
    
    # Get the next "next" link
    next_link = get_next_link(next_collection["links"])

    # Add features from new page to our original list of features
    subscription_items['features'].extend(next_collection['features'])
    
    # increment the page number
    page_number += 1
    
print("Total Subscription Items: {}".format(len(subscription_items['features'])))  

"""
Explanation: Now let's iterate through our data and follow each "next" link to get all the data associated with our Subscription's Results Collection.
End of explanation
"""


# Now we have our complete GeoJSON Feature Collection
subscription_items

"""
Explanation: We can also increase the number of results returned on each page (default 250, max 10000) 
by changing the limit query parameter on the number of results that are returned:
requests.get(subscription_items_url, params={"limit": 500} )
Here's our final Results FeatureCollection:
End of explanation
"""


import geopandas as gpd
import pandas as pd

# Make a GeoPandas data frame from our collection items
# Since we're importing a GeoJSON file, we can use the `from_features` method
gdf = gpd.GeoDataFrame.from_features(subscription_items['features'])

# Show the first five results from our GeoDataFrame
gdf.head()

"""
Explanation: 3. Working with Planet Analytics Results
Now that we've got our Results let's use use GeoPandas to explore our data like we did in the previous tutorial.
End of explanation
"""


# List the item properties (columns)
for column in gdf.columns.values:
    print(column)

"""
Explanation: When we created the DataFrame with the .from_features() method, GeoPandas automatically converted our GeoJSON geometries to Shapely shapes.
Result Item Properties
Let's take a look at the rest of the columns available to us:
End of explanation
"""


# Convert observed column into a datetime datatype
gdf["observed"] = pd.to_datetime(gdf["observed"])

"""
Explanation: Most of these properties should be self-explanatory, and different Feed types may include different properties.
Source Properties
The source_ properties all relate to the source satellite imagery that the detections were made against. We'll see how we can use these later on.
Detection Object Properties
The object_ properties describe some meta-data about each detection, like length and diagonal. 
Also relevant are score (the score assigned by the machine learning model), geometry (the "footprint" of the detection), and observed.
Let's convert our observed column data to a datetime. The observed column represents the date that a detection was made, based on when the source satellite image was taken.
End of explanation
"""


# Simple GeoPandas Plot of features
gdf.plot();

"""
Explanation: Let's visualize our Results. For starters, we can create a simple plot using GeoPandas built in plot method. Later on we'll make some more interesting interactive visualizations.
End of explanation
"""


# Select the first detection by index
first_detection = gdf.iloc[0]
first_detection

"""
Explanation: Inspecting a single detection
We can use our GeoPandas GeoDataFrame to look at the first detection item.
End of explanation
"""


# Use shapely to show the first geometry
first_detection['geometry']

"""
Explanation: Lets look at the first detection's geometry visually. When we created our GeoDataFrame from our collection items data, the features were automatically converted to Shapely Geometries:
End of explanation
"""


# Import GeoViews
import geoviews as gv

# Import HoloViews
import holoviews as hv
from holoviews import opts as hvOpts

# Set rendering backends
gv.extension('bokeh')
hv.extension('bokeh')

# Create a basemap to use in our visualizations
basemap = gv.tile_sources.CartoLight

# List built in GeoViews tile sources 
# help(gv.tile_sources)

"""
Explanation: Visualizing Results with GeoViews
That's simple enough, but doesn't really give us much context. Let's use GeoViews to make some more interesting interactive visualizations!
<img src="https://geoviews.org/_static/logo_horizontal.png" align="right" style="margin-left: 20px" width="100"></img>
GeoViews is built on HoloViews and uses Cartopy, matplotlib and Bokeh for visualizations. It is released as part of the PyViz suite.

GeoViews is a Python library that makes it easy to explore and visualize geographical, meteorological, and oceanographic datasets, such as those used in weather, climate, and remote sensing research.

We can import GeoViews and it's parent library HoloViews like this:
End of explanation
"""


# Plot the detection using GeoViews Shape element with a basemap
gv.Shape(first_detection['geometry']).opts(padding=0.5, width=500, height=400) * basemap

"""
Explanation: Now let's plot the detection geometry on a basemap:
End of explanation
"""


# Get the sensor type
item_type = first_detection['source_item_type']

# Get the item id
scene_id = first_detection['source_item_id']

print("Item Type: {} \nScene Id: {}".format(item_type, scene_id))

"""
Explanation: Nice! GeoViews natively handles our Shapely Geometries and has no issue plotting our detection using an interactive map with Bokeh. 
If we zoom out a bit, we can start to get some more context by seeing where the detection occurred on the map.
In the next section, we'll take a look at combining Planet Analytics Data with Planet Imagery
Analytics Detections and  Source Imagery
The metadata associated with a single detection contains some interesting information, but of course we're here because we want to see the detection with our own eyes too! 
We can easily access the satellite imagery data associated with each detection via several of Planet's Data API and Tile services, and we'll be able to view metadata, and imagery previews if we like, as well as download the imagery assets. 
In order to find the image, we'll need the scene id and item_type (sensor type) for the satellite image used to observe the detection.
In our analytics detections data, sensor type is stored as the source_item_type property, and scene id is stored as the source_item_id. 
Let's get this information for a single detection from our subscription data:
End of explanation
"""


# Import the Planet Data API module
from planet import api as PlanetAPI

# Create a client for the API
planet_client = PlanetAPI.ClientV1(api_key=API_KEY)

# Get data for our item
source_scene = planet_client.get_item(item_type, scene_id).get()

# Pretty Print our scene info
jpp(source_scene)

"""
Explanation: Getting observation source imagery data
Using just these two peices of information, we can access everything we need to know about the satellite imagery used in the observation by getting it from Planet's Data API! 
We'll use the Planet Python Client in this example, but you can also make a request to the API directly with your preferred approach by constructing a URL that includes the item_id and item_type:
https://api.planet.com/data/v1/item-types/{ITEM TYPE}/items/{ITEM ID}
For more in depth guides on working with the Planet Data API, visit our Planet School section on the Planet Developer Resource Center website.
We can get information on our source satellite imagery scene by using the get_item method from the Planet Python Client:
End of explanation
"""


from IPython.display import Image

# Get the thumbnail URL
# Append our api key to authenticate
thumb_url = source_scene['_links']['thumbnail'] + '?api_key=' + API_KEY

# Render the preview image with IPython.display
Image(url=thumb_url)

"""
Explanation: The result should be a GeoJSON Feature with the following noteable fields: 

The scene metadata in properties
The scene footprint in geometry
Links to the scene imagery assets and previews in _links 

Source Imagery Previews and Visualizations
Let's actually view the scene viually by using the thumbnail link to preview our scene imagery with IPython:
End of explanation
"""


# Get source imagery helper utility
# Takes type xyz or thumb
def get_source_imagery_url(scene_id, scene_sensor, preview_type="xyz"):
    
    # Determine parts of URL
    prefix = "" if preview_type == "xyz" else "item-types/"
    middle = "/" if preview_type == "xyz" else "/items/" 
    suffix = "/{Z}/{X}/{Y}.png" if preview_type == "xyz" else "/thumb"
    
    # Construct the URL
    source_imagery_url = "https://tiles.planet.com/data/v1/" + prefix + scene_sensor + \
                         middle + scene_id + suffix + "?api_key=" + API_KEY
    
    return source_imagery_url

"""
Explanation: There's a nice preview of our image, so we can take a peek without having to download the entire asset!
But we won't need to get all the scene meta data available on the Planet Data API just to see the preview. We only really need the item_type and scene_id to construct URLs for the scene image preview thumbnail, as well as (xyz) tiles we can use in a web map.
The URLs to these imagery previews look like:

Thumbnails: https://tiles.planet.com/data/v1/item-types/{ITEM TYPE}/items/{ITEM ID}/thumb
XYZ Tiles: https://tiles.planet.com/data/v1/{ITEM TYPE}/{ITEM ID}/{Z}/{X}/{Y}.png

The backend webmap client will use this template XYZ tiles URL in our visualizations below.
Let's write a helper function that will let us grab the source imagery that our detections were observed in:
End of explanation
"""


from IPython.core.display import HTML

# Get the scene thumbnail URL
thumb_url = get_source_imagery_url(scene_id, item_type, 'thumb')

# Get the scene tiles URL
tiles_url = get_source_imagery_url(scene_id, item_type, 'xyz')

# Print the thumbnail link
print("Thumbnail URL:")
HTML('<a href="{}" target="_blank">Thumbnail URL</a>'.format(thumb_url))


"""
Explanation: Using our helper function, we can construct the preview URLs:
End of explanation
"""


# Plot the detection geometry
single_detection = gv.Shape(first_detection['geometry'], label="Detection in {} - {}".format(item_type, scene_id)).opts(
    fill_alpha=0, 
    line_color="red", 
    width=600, height=500, padding=3,
)

# Plot with web map tiles
single_detection * basemap * gv.WMTS(data=tiles_url)

"""
Explanation: View a single detection with it's source Planet Imagery
Let's put our preview images to work by overlaying a detection on a web map with the observation imagery xyz tiles:
End of explanation
"""


# Helper function to render a thumbnail for an item
# Takes an index (row) from DataFrame
def render_item_thumb(index):
    # Get the item row from DataFrame
    item = gdf.iloc[index]
    
    # Construct tile URL
    tile_url = get_source_imagery_url(item['source_item_id'], item['source_item_type'], 'xyz')   
    
    # Create detection plot
    detection = gv.Shape(item['geometry']).opts(
        fill_alpha=0, 
        line_color="red", 
        width=200, height=200,
        default_tools=[],
        xaxis=None,
        yaxis=None
    ) 
    
    return (detection * basemap * gv.WMTS(data=tile_url))

"""
Explanation: We now have an interactive map with our detection geometry and Planet source imagery. Pan around the map to see more of the scene!
Visualize All Detections
Detection Thumbnails
Let's use this same method to create thumbnails to view more detections with their source imagery. Let's create a function to render these visualizations:
End of explanation
"""


# Iterate through the first 20 thumbs
thumbs_list = [render_item_thumb(i) for i in list(gdf.index[0:20])]

# Create a Layout visualization
layout = hv.Layout(thumbs_list).opts(shared_axes=False).cols(4)
layout

"""
Explanation: Now let's use our helper function to render a Holoviews Layout. We'll render the first twenty detections:
End of explanation
"""


# Plot all features on a map

# Pick the properties (dimensions) we want to include
items_dimensions = ['score', 'object_diagonal_m', 'observed']

# Plot the features with GeoViews
all_features_plot = gv.Polygons(gdf, vdims=items_dimensions).opts(
    colorbar_position="bottom", colorbar_opts={"title": "score"},
    cmap="RdYlBu_r", 
    width=600, height=500,
    colorbar=True,
    title="All Detections for {}".format(subscription['title']))

# Plot the features map with a histogram and basemap 
all_features_plot.hist() * basemap

"""
Explanation: Interactive Detections Plot
Using GeoViews, we can also plot all our detections on a single interactive map and color our detections based on their score values. 
We can include a histogram using the .hist method on our GeoViews Polygons element to show a break down of overall scores.
End of explanation
"""


# Bucket the data by time interval

# Set the interval
interval = "D" # W = Week, D = Day, Month = M

time_bucketed_data = gdf.set_index('observed')[[
    "source_item_id", 
    "geometry", 
    "source_cloud_cover", 
    "score"]].groupby([pd.Grouper(freq=interval)]).agg({
    'geometry': 'count', 
    'source_item_id': 'nunique', 
    'source_cloud_cover': "mean", 
    'score':'mean'
}).fillna(0).rename(index=str, columns={"geometry": "detection_count", "source_item_id": "imagery_count", "source_cloud_cover":"mean_cloud_cover", "score":"mean_score"})

# Remove rows without detections
time_bucketed_data = time_bucketed_data[time_bucketed_data["detection_count"] != 0]

"""
Explanation: Visualize by Time Dimension
We can also make a visualization that let's us interactively view detections for certain days using a HoloMap. First let's organize our data by day:
End of explanation
"""


from datetime import timedelta

# Render Features for a date
def plot_features_for_date(start_date):
    
    # Increment start date by one day
    end_date = start_date + timedelta(days=1)

    # Slice our data to only include features between start and end date
    mask = (gdf["observed"] > start_date) & (gdf["observed"] <= end_date)
    sliced_data = gdf.loc[mask]
    
    # Create the Polygons Plot
    return gv.Polygons(sliced_data, vdims=['score']).opts(fill_alpha=0, nonselection_line_alpha=0.1, line_color="score",  selection_line_color="green", cmap="RdYlBu_r", width=600, height=400, tools=["hover", "tap"], title="Detections for {} on {}".format(subscription['title'], start_date))


"""
Explanation: Next let's define the render function to use for each day:
End of explanation
"""


# Render HoloViews HoloMap

# Define the date dimension
kdims = [hv.Dimension(('start_date', 'Observation Date'))]

# Create all the plots
features_for_date = {(date): plot_features_for_date(date) for date in list(pd.to_datetime(time_bucketed_data.index))}

# Create the HoloMap 
features_by_day_plot = hv.HoloMap(features_for_date, kdims=kdims)

# Add a basemap to the features plot and render
features_by_day_plot * basemap

"""
Explanation: Finally let's create our plot:
End of explanation
"""


# Define the Subscription UUID, which in this case is for Monthly Road Detections
subscription_id = 'e0c33581-5080-4e81-98a1-2d555e91b41e'

# Construct the URL for a Segmentation Feed Subscription 
seg_subscription_url = PAA_BASE_URL + 'subscriptions/' + subscription_id

print("Request URL: {}".format(subscription_url))

# Make the GET request for Subscription
seg_subscription = requests.get(seg_subscription_url, auth=BASIC_AUTH).json()

seg_subscription

"""
Explanation: 4. Segmentation Results
So far we've looked at an Object Detection Feed's Results. Let's switch over to a Segmentation Feed and see how we can work with the raster mask data. 
Since the output data for a Segmentation Feed is comprised of mosaics for each interval that we run our model, we can use the mosaics to visualize and explore our data.
We'll use the Subscription Information, and subsequently the Feed information to determine where the Raster Result basemaps are available using the Planet Mosaics API. Later we'll see how we can get the actual raster data as well.
First let's get a new Subscription that uses a Segmentation Feed type using a Subscription Id:
Note: If you do not have access to the Analytics Feeds API or the specific example subscription IDs used in the example code below, you may not be able to run through these examples. Contact Sales to learn more.
End of explanation
"""


# We only need the year, month, and day for monthly Basemaps
start_time = seg_subscription['startTime'].split('T')[0]
end_time = seg_subscription['endTime'].split('T')[0]

print('Subscription startTime: {}'.format(start_time))
print('Subscription endTime: {}'.format(end_time))

"""
Explanation: Save startTime and endTime as variables to filter our results below.
End of explanation
"""


# Construct the Feed request url from the Subscription link
seg_subscription_feed_url = list(filter(lambda link: link['rel'] == 'feed', seg_subscription['links']))[0]['href']
print(seg_subscription_feed_url)

# Make the request for the Subscription's Feed
seg_subscription_feed = requests.get(seg_subscription_feed_url, auth=BASIC_AUTH).json()
seg_subscription_feed

"""
Explanation: We'll use the links property to get the link to the Subscription's Feed and then request the Feed from the API:
End of explanation
"""


# Get the mosaics series id
mosaic_target_series_id = seg_subscription_feed['target']['config']['series_id']
mosaic_target_series_id

"""
Explanation: Now that we have our Feed data for the Subscription, let's use the target property to determine our mosaic series:
End of explanation
"""


# Construct the mosaics series url
mosaic_target_series_url = "https://api.planet.com/basemaps/v1/series/" + mosaic_target_series_id
print(mosaic_target_series_url)

# Make a request to the Mosaics API
target_mosaic_series = requests.get(mosaic_target_series_url, auth=BASIC_AUTH).json()
target_mosaic_series

"""
Explanation: Using the Mosaic Series Id, we can make a request to the Planet Mosaics API using the following endpoint https://api.planet.com/basemaps/v1/series/{series_id}:
End of explanation
"""


# Make a request to get the subscription's mosaics
target_mosaics = requests.get(target_mosaic_series['_links']['mosaics'], auth=BASIC_AUTH).json()['mosaics']

# Filter the target mosaics to return only those that fit within our subscription's startTime and endTime
subscription_mosaics = [m for m in target_mosaics if m['first_acquired'] >= start_time and m['last_acquired'] <= end_time]

jpp(subscription_mosaics)

"""
Explanation: We can see some metadata here about our series, including the first_acquired and last_acquired dates, the interval at which a new mosaic gets created. 
Now that we have the Mosaic series, we can see that there's a link to mosaics under the _links property. These are the individual raster segmentation mask "mosaics" made up of several smaller image files called "quads". The mosaics themselves are also available as xyz tile basemaps, served by Planet's tile servers. Let's make a final request to the mosaics link which will give us the urls we need to make a visualization of the Subscription's results!
End of explanation
"""


from pandas import json_normalize

# Create a dataframe from list of mosaics (normalize json to break out individual links)
mosaics_df = pd.DataFrame(json_normalize(subscription_mosaics))
mosaics_df.head()

"""
Explanation: The response from our last request includes a mosaics property that contains an array/list of individual mosaics available for the series. 
Among the properties available for each mosaic, there is the familiar _links property with a tiles link that provides us an xyz tile url that we can use in a web map.
Let's make a new pandas DataFrame with our mosaics:
End of explanation
"""


# Create a new DataFrame with limited columns
target_tiles_df = mosaics_df.loc[:, ['_links.tiles', 'last_acquired']]

# Rename the tiles column
target_tiles_df.rename(columns={'_links.tiles':'target_tileurl', 'last_acquired':'date'}, inplace=True)

# Only use year/month for date
target_tiles_df['date'] = pd.to_datetime(target_tiles_df["date"])
target_tiles_df['date'] = target_tiles_df['date'].dt.strftime('%Y/%m')

target_tiles_df.head()

"""
Explanation: Let's get the two peices of information that are most useful for us here, the tiles links and the last_acquired date:
End of explanation
"""


# Helper to format target tile urls for HoloViews
def format_target_mosaic_url(in_url):
    url = in_url.replace("{x}", "{X}").replace("{y}", "{Y}").replace("{z}", "{Z}")
    return url

"""
Explanation: Here's a small function to help us format our tile urls for HoloViews.
End of explanation
"""


from shapely.geometry import shape

# Get the extent of our subscription
seg_subscription_bounds = shape(seg_subscription['geometry']).bounds

# Style the target mosaic
target_style = "&exp=bincat:0|a50f15"

# Create data for HoloMap
mosaics = {(mosaic['date']):gv.WMTS(format_target_mosaic_url(mosaic['target_tileurl']) + target_style, extents=seg_subscription_bounds) for index, mosaic in target_tiles_df.iterrows() }

# Create the visualization
subscription_mosaics_plot = hv.HoloMap(mosaics, kdims = [hv.Dimension(('date', 'Date'))]).opts(width=600, height=500)
basemap * subscription_mosaics_plot 

"""
Explanation: Now we can make an interactive visualization of each interval's tiles using the HoloMap. We'll also add a query parameter to the target tiles url that allow us to style the target tiles:
End of explanation
"""


# Get the source mosaics series id
mosaic_source_series_id = seg_subscription_feed['source'][0]['config']['series_id']
print("Source Mosaic Series Id: {}\n".format(mosaic_source_series_id))

# Construct the source mosaics series url
mosaic_source_series_url = "https://api.planet.com/basemaps/v1/series/" + mosaic_source_series_id
print("Source Mosaic Series Url: {}\n".format(mosaic_source_series_url))

# Make a request to the Mosaics API for source mosaics series
source_mosaic_series = requests.get(mosaic_source_series_url, auth=BASIC_AUTH).json()

# Make a request to the Mosaics API for source mosaics
source_mosaics = requests.get(source_mosaic_series['_links']['mosaics'], auth=BASIC_AUTH).json()

"""
Explanation: We're also able to show the source imagery that the Subscription output was detected against! Remember how we got the Subscription's target mosaic series? We can do the same for the source mosaic series:
End of explanation
"""


# Create a dataframe from list of mosaics (normalize json to break out individual links)
src_mosaics_df = pd.DataFrame(json_normalize(source_mosaics['mosaics']))

# Create a new DataFrame with limited columns
src_tiles_df = src_mosaics_df.loc[:, ['_links.tiles', 'last_acquired']]

# Rename the tiles column
src_tiles_df.rename(columns={'_links.tiles':'src_tileurl', 'last_acquired':'date'}, inplace=True)

# Only use year/month for date
src_tiles_df['date'] = pd.to_datetime(src_tiles_df["date"])
src_tiles_df['date'] = src_tiles_df['date'].dt.strftime('%Y/%m')

src_tiles_df.head()

"""
Explanation: Now let's prepare our Source Mosaic data:
End of explanation
"""


# Merge src and target tile urls
combined_tiles = pd.merge(target_tiles_df, src_tiles_df, on='date')

# Set datetime index
combined_tiles['index'] = pd.to_datetime(combined_tiles['date'])
combined_tiles.set_index('index', inplace=True)

combined_tiles.head()

"""
Explanation: Let's merge the two tile url DataFrames:
End of explanation
"""


# Create data for HoloMap
mosaics = {(mosaic['date']): gv.WMTS(format_target_mosaic_url(mosaic['target_tileurl']) + target_style, extents=seg_subscription_bounds) + gv.WMTS(format_target_mosaic_url(mosaic['src_tileurl'])) for index, mosaic in combined_tiles.iterrows() }

# Create the visualization
combined_mosaics_plot = hv.HoloMap(mosaics, kdims = [hv.Dimension(('date', 'Date'))]).opts(width=600, height=500).collate()
basemap * combined_mosaics_plot 

"""
Explanation: Finally, let's create the visualization with both source and target imagery:
End of explanation
"""


seg_subscription_results_url = PAA_BASE_URL + 'collections/' + seg_subscription['id'] + '/items'

print("Request URL: {}".format(seg_subscription_results_url))

# Make the GET request for Subscription Results
seg_subscription_results = requests.get(seg_subscription_results_url, auth=BASIC_AUTH).json()

seg_subscription_results

print(len(seg_subscription_results['features']))

"""
Explanation: With the above visualization, you're able to compare the source imagery with the Subscription's output!
Segmentation Mask Data
So far we've seen how to visualize segmentation mask data using Planet's Basemap services which serve up web map tiles. We're also able to retreive the "raw" raster files that make up these basemaps via the Planet Mosaics API as well as the Planet Analytics API Results for the Subscription. The raster files are available as Cloud Optimized GeoTiff (COG) "quads" that make up a mosaic.
For a Subscription with a segmentation Feed type, the /collection/{ID}/items endpoint will provide a listing of all mosaic quads processed by our Subscription's Feed*:
* Remember, the Results .../items/ endpoint is paged. In this example, we won't get all the paged items.
End of explanation
"""


# Create a GeoDataFrame of quads
quads_gdf = gpd.GeoDataFrame.from_features(seg_subscription_results['features'])

# Plot the quads
quads_plot = gv.Polygons(quads_gdf, label="Quads").opts(fill_alpha=0.1, fill_color="yellow", show_legend=True)

# Plot the subscription AOI
subscription_aoi = gv.Shape(shape(seg_subscription['geometry']), label="AOI").opts(fill_alpha=0.5, line_color="red",  fill_color="red", show_legend=True)

# Plot our visualization
(quads_plot * subscription_aoi * basemap).opts(width=600, height=400)

"""
Explanation: Each Feature in the response FeatureCollection represents a quad, and each Feature's geometry is the footprint of that quad. 
To illustrate this, let's make a GeoDataFrame from the Results FeatureCollection for our segmentation type Subscription, and plot the quads along with the boundary of our subscription AOI:
End of explanation
"""


seg_sub_results_links = seg_subscription_results['features'][0]['links']
seg_sub_results_links

"""
Explanation: We can see that the geometries of the quads all intersect with our Subscription AOI. 
Downloading a Result Quad
If we want to download the raw quad data, for either the source or output target, we can do so via the Planet Mosaics API. To find the link to the file, we can look at the Result item's links property . Here's the first result from our Subscription Results collection:
End of explanation
"""


# Construct the URL to source quad
source_quad = list(filter(lambda link: link['rel'] == 'source-quad', seg_sub_results_links))[0]['href']

print("Source Quad URL:\n{}\n".format(source_quad))

# Construct the URL to target quad
target_quad = list(filter(lambda link: link['rel'] == 'target-quad', seg_sub_results_links))[0]['href']

print("Target (Result) Quad URL:\n{}\n".format(target_quad))

"""
Explanation: We see the links for both target-quad (the result output), and source-quad (the source imagery that was used to create the detections):
End of explanation
"""


