Skip to content

Commit d5bfaa5

Browse files
authored
Merge pull request planetlabs#25 from planetlabs/quickstart
Add a "search & download" quickstart notebook
2 parents 8a65552 + 3e3137f commit d5bfaa5

File tree

3 files changed

+367
-0
lines changed

3 files changed

+367
-0
lines changed
189 KB
Loading
61.9 KB
Loading
Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Getting started with the Data API"
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"### **Let's search & download some imagery of farmland near Stockton, CA. Here are the steps we'll follow:**\n",
15+
"\n",
16+
"1. Define an Area of Interest (AOI)\n",
17+
"2. Save our AOI's coordinates to GeoJSON format\n",
18+
"3. Create a few search filters\n",
19+
"4. Search for imagery using those filters\n",
20+
"5. Activate an image for downloading\n",
21+
"6. Download an image"
22+
]
23+
},
24+
{
25+
"cell_type": "markdown",
26+
"metadata": {},
27+
"source": [
28+
"### Requirements\n",
29+
"- Python 2.7 or 3+\n",
30+
"- requests\n",
31+
"- A [Planet API Key](https://www.planet.com/account/#/)"
32+
]
33+
},
34+
{
35+
"cell_type": "markdown",
36+
"metadata": {},
37+
"source": [
38+
"## Define an Area of Interest"
39+
]
40+
},
41+
{
42+
"cell_type": "markdown",
43+
"metadata": {},
44+
"source": [
45+
"An **Area of Interest** (or *AOI*) is how we define the geographic \"window\" out of which we want to get data.\n",
46+
"\n",
47+
"For the Data API, this could be a simple bounding box with four corners, or a more complex shape, as long as the definition is in [GeoJSON](http://geojson.org/) format. \n",
48+
"\n",
49+
"For this example, let's just use a simple box. To make it easy, I'll use [geojson.io](http://geojson.io/) to quickly draw a shape & generate GeoJSON output for our box:"
50+
]
51+
},
52+
{
53+
"cell_type": "markdown",
54+
"metadata": {},
55+
"source": [
56+
"![geojsonio.png](images/geojsonio.png)"
57+
]
58+
},
59+
{
60+
"cell_type": "markdown",
61+
"metadata": {},
62+
"source": [
63+
"We only need the \"geometry\" object for our Data API request:"
64+
]
65+
},
66+
{
67+
"cell_type": "code",
68+
"execution_count": null,
69+
"metadata": {
70+
"scrolled": true
71+
},
72+
"outputs": [],
73+
"source": [
74+
"# Stockton, CA bounding box (created via geojson.io) \n",
75+
"geojson_geometry = {\n",
76+
" \"type\": \"Polygon\",\n",
77+
" \"coordinates\": [\n",
78+
" [ \n",
79+
" [-121.59290313720705, 37.93444993515032],\n",
80+
" [-121.27017974853516, 37.93444993515032],\n",
81+
" [-121.27017974853516, 38.065932950547484],\n",
82+
" [-121.59290313720705, 38.065932950547484],\n",
83+
" [-121.59290313720705, 37.93444993515032]\n",
84+
" ]\n",
85+
" ]\n",
86+
"}"
87+
]
88+
},
89+
{
90+
"cell_type": "markdown",
91+
"metadata": {},
92+
"source": [
93+
"## Create Filters"
94+
]
95+
},
96+
{
97+
"cell_type": "markdown",
98+
"metadata": {},
99+
"source": [
100+
"Now let's set up some **filters** to further constrain our Data API search:"
101+
]
102+
},
103+
{
104+
"cell_type": "code",
105+
"execution_count": null,
106+
"metadata": {},
107+
"outputs": [],
108+
"source": [
109+
"# get images that overlap with our AOI \n",
110+
"geometry_filter = {\n",
111+
" \"type\": \"GeometryFilter\",\n",
112+
" \"field_name\": \"geometry\",\n",
113+
" \"config\": geojson_geometry\n",
114+
"}\n",
115+
"\n",
116+
"# get images acquired within a date range\n",
117+
"date_range_filter = {\n",
118+
" \"type\": \"DateRangeFilter\",\n",
119+
" \"field_name\": \"acquired\",\n",
120+
" \"config\": {\n",
121+
" \"gte\": \"2016-08-31T00:00:00.000Z\",\n",
122+
" \"lte\": \"2016-09-01T00:00:00.000Z\"\n",
123+
" }\n",
124+
"}\n",
125+
"\n",
126+
"# only get images which have <50% cloud coverage\n",
127+
"cloud_cover_filter = {\n",
128+
" \"type\": \"RangeFilter\",\n",
129+
" \"field_name\": \"cloud_cover\",\n",
130+
" \"config\": {\n",
131+
" \"lte\": 0.5\n",
132+
" }\n",
133+
"}\n",
134+
"\n",
135+
"# combine our geo, date, cloud filters\n",
136+
"combined_filter = {\n",
137+
" \"type\": \"AndFilter\",\n",
138+
" \"config\": [geometry_filter, date_range_filter, cloud_cover_filter]\n",
139+
"}\n"
140+
]
141+
},
142+
{
143+
"cell_type": "markdown",
144+
"metadata": {},
145+
"source": [
146+
"## Searching: Items and Assets"
147+
]
148+
},
149+
{
150+
"cell_type": "markdown",
151+
"metadata": {},
152+
"source": [
153+
"Planet's products are categorized as **items** and **assets**: an item is a single picture taken by a satellite at a certain time. Items have multiple asset types including the image in different formats, along with supporting metadata files.\n",
154+
"\n",
155+
"For this demonstration, let's get a satellite image that is best suited for visual applications; e.g., basemaps or visual analysis. Since we're not doing any spectral analysis outside of the visual range, we only need a 3-band (RGB) image. To get the image we want, we will specify an item type of `PSScene3Band`, and asset type `visual`.\n",
156+
"\n",
157+
"You can learn more about item & asset types in Planet's Data API [here](https://planet.com/docs/reference/data-api/items-assets/).\n",
158+
"\n",
159+
"Now let's search for all the items that match our filters:"
160+
]
161+
},
162+
{
163+
"cell_type": "code",
164+
"execution_count": null,
165+
"metadata": {},
166+
"outputs": [],
167+
"source": [
168+
"import os\n",
169+
"import json\n",
170+
"import requests\n",
171+
"from requests.auth import HTTPBasicAuth\n",
172+
"\n",
173+
"# API Key stored as an env variable\n",
174+
"PLANET_API_KEY = os.getenv('PL_API_KEY')\n",
175+
"\n",
176+
"item_type = \"PSScene3Band\"\n",
177+
"\n",
178+
"# API request object\n",
179+
"search_request = {\n",
180+
" \"interval\": \"day\",\n",
181+
" \"item_types\": [item_type], \n",
182+
" \"filter\": combined_filter\n",
183+
"}\n",
184+
"\n",
185+
"# fire off the POST request\n",
186+
"search_result = \\\n",
187+
" requests.post(\n",
188+
" 'https://api.planet.com/data/v1/quick-search',\n",
189+
" auth=HTTPBasicAuth(PLANET_API_KEY, ''),\n",
190+
" json=search_request)\n",
191+
"\n",
192+
"print(json.dumps(search_result.json(), indent=1))"
193+
]
194+
},
195+
{
196+
"cell_type": "markdown",
197+
"metadata": {},
198+
"source": [
199+
"Our search returns metadata for all of the images within our AOI that match our date range and cloud coverage filters. It looks like there are multiple images here; let's extract a list of just those image IDs:"
200+
]
201+
},
202+
{
203+
"cell_type": "code",
204+
"execution_count": null,
205+
"metadata": {},
206+
"outputs": [],
207+
"source": [
208+
"# extract image IDs only\n",
209+
"image_ids = [feature['id'] for feature in search_result.json()['features']]\n",
210+
"print(image_ids)"
211+
]
212+
},
213+
{
214+
"cell_type": "markdown",
215+
"metadata": {},
216+
"source": [
217+
"Since we just want a single image, and this is only a demonstration, for our purposes here we can arbitrarily select the first image in that list. Let's do that, and get the `asset` list available for that image:"
218+
]
219+
},
220+
{
221+
"cell_type": "code",
222+
"execution_count": null,
223+
"metadata": {},
224+
"outputs": [],
225+
"source": [
226+
"# For demo purposes, just grab the first image ID\n",
227+
"id0 = image_ids[0]\n",
228+
"id0_url = 'https://api.planet.com/data/v1/item-types/{}/items/{}/assets'.format(item_type, id0)\n",
229+
"\n",
230+
"# Returns JSON metadata for assets in this ID. Learn more: planet.com/docs/reference/data-api/items-assets/#asset\n",
231+
"result = \\\n",
232+
" requests.get(\n",
233+
" id0_url,\n",
234+
" auth=HTTPBasicAuth(PLANET_API_KEY, '')\n",
235+
" )\n",
236+
"\n",
237+
"# List of asset types available for this particular satellite image\n",
238+
"print(result.json().keys())\n"
239+
]
240+
},
241+
{
242+
"cell_type": "markdown",
243+
"metadata": {},
244+
"source": [
245+
" ## Activation and Downloading\n",
246+
" \n",
247+
"The Data API does not pre-generate assets, so they are not always immediately availiable to download. In order to download an asset, we first have to **activate** it.\n",
248+
"\n",
249+
"Remember, earlier we decided we wanted a color-corrected image best suited for *visual* applications. We can check the status of the visual asset we want to download like so:\n",
250+
" "
251+
]
252+
},
253+
{
254+
"cell_type": "code",
255+
"execution_count": null,
256+
"metadata": {},
257+
"outputs": [],
258+
"source": [
259+
"# This is \"inactive\" if the \"visual\" asset has not yet been activated; otherwise 'active'\n",
260+
"print(result.json()['visual']['status'])"
261+
]
262+
},
263+
{
264+
"cell_type": "markdown",
265+
"metadata": {},
266+
"source": [
267+
"Let's now go ahead and **activate** that asset for download:"
268+
]
269+
},
270+
{
271+
"cell_type": "code",
272+
"execution_count": null,
273+
"metadata": {},
274+
"outputs": [],
275+
"source": [
276+
"# Parse out useful links\n",
277+
"links = result.json()[u\"visual\"][\"_links\"]\n",
278+
"self_link = links[\"_self\"]\n",
279+
"activation_link = links[\"activate\"]\n",
280+
"\n",
281+
"# Request activation of the 'visual' asset:\n",
282+
"activate_result = \\\n",
283+
" requests.get(\n",
284+
" activation_link,\n",
285+
" auth=HTTPBasicAuth(PLANET_API_KEY, '')\n",
286+
" )"
287+
]
288+
},
289+
{
290+
"cell_type": "markdown",
291+
"metadata": {},
292+
"source": [
293+
"At this point, we wait for the activation status for the asset we are requesting to change from `inactive` to `active`. We can monitor this by polling the \"status\" of the asset:"
294+
]
295+
},
296+
{
297+
"cell_type": "code",
298+
"execution_count": null,
299+
"metadata": {},
300+
"outputs": [],
301+
"source": [
302+
"activation_status_result = \\\n",
303+
" requests.get(\n",
304+
" self_link,\n",
305+
" auth=HTTPBasicAuth(PLANET_API_KEY, '')\n",
306+
" )\n",
307+
" \n",
308+
"print(activation_status_result.json()[\"status\"])"
309+
]
310+
},
311+
{
312+
"cell_type": "markdown",
313+
"metadata": {},
314+
"source": [
315+
"Once the asset has finished activating (status is \"active\"), we can download it. \n",
316+
"\n",
317+
"*Note: the download link on an active asset is temporary*"
318+
]
319+
},
320+
{
321+
"cell_type": "code",
322+
"execution_count": null,
323+
"metadata": {},
324+
"outputs": [],
325+
"source": [
326+
"# Image can be downloaded by making a GET with your Planet API key, from here:\n",
327+
"download_link = activation_status_result.json()[\"location\"]\n",
328+
"print(download_link)"
329+
]
330+
},
331+
{
332+
"cell_type": "markdown",
333+
"metadata": {},
334+
"source": [
335+
"![stockton_thumb.png](images/stockton_thumb.png)"
336+
]
337+
},
338+
{
339+
"cell_type": "markdown",
340+
"metadata": {},
341+
"source": [
342+
" "
343+
]
344+
}
345+
],
346+
"metadata": {
347+
"kernelspec": {
348+
"display_name": "Python 2",
349+
"language": "python",
350+
"name": "python2"
351+
},
352+
"language_info": {
353+
"codemirror_mode": {
354+
"name": "ipython",
355+
"version": 2
356+
},
357+
"file_extension": ".py",
358+
"mimetype": "text/x-python",
359+
"name": "python",
360+
"nbconvert_exporter": "python",
361+
"pygments_lexer": "ipython2",
362+
"version": "2.7.14"
363+
}
364+
},
365+
"nbformat": 4,
366+
"nbformat_minor": 1
367+
}

0 commit comments

Comments
 (0)