-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathplanet_client.py
More file actions
128 lines (108 loc) · 4.25 KB
/
Copy pathplanet_client.py
File metadata and controls
128 lines (108 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import logging
import os
from datetime import datetime
import requests
from requests.auth import HTTPBasicAuth
BASE_URL = "https://api.planet.com/data/v1"
ITEM_TYPE_PRIORITY = ["SkySat-Collect", "PSScene", "Sentinel2L1C", "Landsat8L1T"]
CLOUD_COVER_TYPES = {"PSScene", "Sentinel2L1C"}
def _auth():
return HTTPBasicAuth(os.environ["PLANET_API_KEY"], "")
def get_available_item_types():
"""Return item types from ITEM_TYPE_PRIORITY that exist in this account."""
resp = requests.get(f"{BASE_URL}/item-types", auth=_auth())
resp.raise_for_status()
available = {t["id"] for t in resp.json().get("item_types", [])}
return [t for t in ITEM_TYPE_PRIORITY if t in available]
def build_filter(geometry, date_from, date_to, cloud_max, item_type):
"""Build a Planet AndFilter for a single item type search."""
filters = [
{
"type": "GeometryFilter",
"field_name": "geometry",
"config": geometry,
},
{
"type": "DateRangeFilter",
"field_name": "acquired",
"config": {"gte": date_from, "lte": date_to},
},
]
if item_type in CLOUD_COVER_TYPES:
filters.append(
{
"type": "RangeFilter",
"field_name": "cloud_cover",
"config": {"lte": cloud_max},
}
)
return {"type": "AndFilter", "config": filters}
def sort_results(results):
"""Sort by: item type priority → acquired desc → gsd asc."""
priority = {t: i for i, t in enumerate(ITEM_TYPE_PRIORITY)}
def sort_key(r):
acquired = r.get("acquired") or ""
try:
ts = datetime.fromisoformat(acquired.replace("Z", "+00:00")).timestamp()
except (ValueError, AttributeError):
ts = 0.0
return (
priority.get(r.get("item_type"), 99),
-ts,
r.get("gsd") or 9999,
)
return sorted(results, key=sort_key)
def _search_item_type(item_type, geometry, date_from, date_to, cloud_max):
"""Search one item type. Returns list of raw feature dicts."""
payload = {
"item_types": [item_type],
"filter": build_filter(geometry, date_from, date_to, cloud_max, item_type),
}
resp = requests.post(
f"{BASE_URL}/quick-search",
json=payload,
auth=_auth(),
params={"_page_size": 250},
)
resp.raise_for_status()
return resp.json().get("features", [])
def search_all(geometry, item_types, date_from, date_to, cloud_max):
"""Search all requested item types and return merged, sorted results."""
all_results = []
for item_type in item_types:
try:
features = _search_item_type(
item_type, geometry, date_from, date_to, cloud_max
)
for f in features:
props = f.get("properties", {})
all_results.append(
{
"id": f["id"],
"item_type": item_type,
"acquired": props.get("acquired", ""),
"cloud_cover": props.get("cloud_cover"),
"gsd": props.get("gsd"),
"thumbnail_url": f"/api/thumbnail/{item_type}/{f['id']}",
"tile_url": f"/api/tiles/{item_type}/{f['id']}/{{z}}/{{x}}/{{y}}.png",
}
)
except Exception as e:
logging.warning("search failed for %s: %s", item_type, e)
return sort_results(all_results)
def get_thumbnail(item_type, item_id):
"""Fetch thumbnail bytes. Returns (bytes, content_type)."""
url = f"{BASE_URL}/item-types/{item_type}/items/{item_id}/thumb"
resp = requests.get(url, auth=_auth())
resp.raise_for_status()
return resp.content, resp.headers.get("Content-Type", "image/jpeg")
def get_tile(item_type, item_id, z, x, y):
"""Fetch XYZ map tile. Returns (bytes, content_type). API key injected server-side."""
api_key = os.environ["PLANET_API_KEY"]
url = (
f"https://tiles.planet.com/data/v1"
f"/{item_type}/{item_id}/{z}/{x}/{y}.png"
)
resp = requests.get(url, params={"api_key": api_key})
resp.raise_for_status()
return resp.content, resp.headers.get("Content-Type", "image/png")